using System; using System.Collections.Generic; using NT8.Core.Common.Models; namespace NT8.Core.Indicators { /// /// Anchor mode for AVWAP reset behavior. /// public enum AVWAPAnchorMode { /// /// Reset at session/day start. /// Day = 0, /// /// Reset at week start. /// Week = 1, /// /// Reset at custom provided anchor time. /// Custom = 2 } /// /// Anchored VWAP calculator with rolling updates and slope estimation. /// Thread-safe for live multi-caller usage. /// public class AVWAPCalculator { private readonly object _lock = new object(); private readonly List _vwapHistory; private DateTime _anchorTime; private double _sumPriceVolume; private double _sumVolume; private AVWAPAnchorMode _anchorMode; /// /// Creates a new AVWAP calculator. /// /// Anchor mode. /// Initial anchor time. public AVWAPCalculator(AVWAPAnchorMode anchorMode, DateTime anchorTime) { _anchorMode = anchorMode; _anchorTime = anchorTime; _sumPriceVolume = 0.0; _sumVolume = 0.0; _vwapHistory = new List(); } /// /// Calculates anchored VWAP from bars starting at anchor time. /// /// Source bars in chronological order. /// Anchor start time. /// Calculated AVWAP value or 0.0 if no eligible bars. public double Calculate(List bars, DateTime anchorTime) { if (bars == null) throw new ArgumentNullException("bars"); lock (_lock) { _anchorTime = anchorTime; _sumPriceVolume = 0.0; _sumVolume = 0.0; _vwapHistory.Clear(); for (var i = 0; i < bars.Count; i++) { var bar = bars[i]; if (bar == null) continue; if (bar.Time < anchorTime) continue; var price = GetTypicalPrice(bar); var volume = Math.Max(0L, bar.Volume); _sumPriceVolume += price * volume; _sumVolume += volume; var vwap = _sumVolume > 0.0 ? _sumPriceVolume / _sumVolume : 0.0; _vwapHistory.Add(vwap); } if (_sumVolume <= 0.0) return 0.0; return _sumPriceVolume / _sumVolume; } } /// /// Updates AVWAP state with one new trade/bar observation. /// /// Current price. /// Current volume. public void Update(double price, long volume) { if (volume < 0) throw new ArgumentException("volume must be non-negative", "volume"); lock (_lock) { _sumPriceVolume += price * volume; _sumVolume += volume; var vwap = _sumVolume > 0.0 ? _sumPriceVolume / _sumVolume : 0.0; _vwapHistory.Add(vwap); if (_vwapHistory.Count > 2000) _vwapHistory.RemoveAt(0); } } /// /// Returns AVWAP slope over lookback bars. /// /// Lookback bars. /// Slope per bar. public double GetSlope(int lookback) { if (lookback <= 0) throw new ArgumentException("lookback must be greater than zero", "lookback"); lock (_lock) { if (_vwapHistory.Count <= lookback) return 0.0; var lastIndex = _vwapHistory.Count - 1; var current = _vwapHistory[lastIndex]; var prior = _vwapHistory[lastIndex - lookback]; return (current - prior) / lookback; } } /// /// Resets AVWAP accumulation to a new anchor. /// /// New anchor time. public void ResetAnchor(DateTime newAnchor) { lock (_lock) { _anchorTime = newAnchor; _sumPriceVolume = 0.0; _sumVolume = 0.0; _vwapHistory.Clear(); } } /// /// Gets current AVWAP from rolling state. /// /// Current AVWAP. public double GetCurrentValue() { lock (_lock) { return _sumVolume > 0.0 ? _sumPriceVolume / _sumVolume : 0.0; } } /// /// Gets current anchor mode. /// /// Anchor mode. public AVWAPAnchorMode GetAnchorMode() { lock (_lock) { return _anchorMode; } } /// /// Sets anchor mode. /// /// Anchor mode. public void SetAnchorMode(AVWAPAnchorMode mode) { lock (_lock) { _anchorMode = mode; } } private static double GetTypicalPrice(BarData bar) { return (bar.High + bar.Low + bar.Close) / 3.0; } } }