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;
}
}
}