using System;
using System.Collections.Generic;
using NT8.Core.Common.Models;
using NT8.Core.Logging;
namespace NT8.Core.Intelligence
{
///
/// Coordinates volatility and trend regime detection and stores per-symbol regime state.
/// Thread-safe access to shared regime state and transition history.
///
public class RegimeManager
{
private readonly ILogger _logger;
private readonly VolatilityRegimeDetector _volatilityDetector;
private readonly TrendRegimeDetector _trendDetector;
private readonly object _lock = new object();
private readonly Dictionary _currentStates;
private readonly Dictionary> _barHistory;
private readonly Dictionary> _transitions;
private readonly int _maxBarsPerSymbol;
private readonly int _maxTransitionsPerSymbol;
///
/// Creates a new regime manager.
///
/// Logger instance.
/// Volatility regime detector.
/// Trend regime detector.
/// Maximum bars to keep per symbol.
/// Maximum transitions to keep per symbol.
/// Any required dependency is null.
/// Any max size is not positive.
public RegimeManager(
ILogger logger,
VolatilityRegimeDetector volatilityDetector,
TrendRegimeDetector trendDetector,
int maxBarsPerSymbol,
int maxTransitionsPerSymbol)
{
if (logger == null)
throw new ArgumentNullException("logger");
if (volatilityDetector == null)
throw new ArgumentNullException("volatilityDetector");
if (trendDetector == null)
throw new ArgumentNullException("trendDetector");
if (maxBarsPerSymbol <= 0)
throw new ArgumentException("maxBarsPerSymbol must be greater than zero", "maxBarsPerSymbol");
if (maxTransitionsPerSymbol <= 0)
throw new ArgumentException("maxTransitionsPerSymbol must be greater than zero", "maxTransitionsPerSymbol");
_logger = logger;
_volatilityDetector = volatilityDetector;
_trendDetector = trendDetector;
_maxBarsPerSymbol = maxBarsPerSymbol;
_maxTransitionsPerSymbol = maxTransitionsPerSymbol;
_currentStates = new Dictionary();
_barHistory = new Dictionary>();
_transitions = new Dictionary>();
}
///
/// Updates regime state for a symbol using latest market information.
///
/// Instrument symbol.
/// Latest bar.
/// Current AVWAP value.
/// Current ATR value.
/// Normal ATR baseline value.
public void UpdateRegime(string symbol, BarData bar, double avwap, double atr, double normalAtr)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentNullException("symbol");
if (bar == null)
throw new ArgumentNullException("bar");
if (normalAtr <= 0.0)
throw new ArgumentException("normalAtr must be greater than zero", "normalAtr");
if (atr < 0.0)
throw new ArgumentException("atr must be non-negative", "atr");
try
{
RegimeState previousState = null;
VolatilityRegime volatilityRegime;
TrendRegime trendRegime;
double volatilityScore;
double trendStrength;
TimeSpan duration;
DateTime updateTime = DateTime.UtcNow;
lock (_lock)
{
EnsureCollections(symbol);
AppendBar(symbol, bar);
if (_currentStates.ContainsKey(symbol))
previousState = _currentStates[symbol];
volatilityRegime = _volatilityDetector.DetectRegime(symbol, atr, normalAtr);
volatilityScore = _volatilityDetector.CalculateVolatilityScore(atr, normalAtr);
if (_barHistory[symbol].Count >= 5)
{
trendRegime = _trendDetector.DetectTrend(symbol, _barHistory[symbol], avwap);
trendStrength = _trendDetector.CalculateTrendStrength(_barHistory[symbol], avwap);
}
else
{
trendRegime = TrendRegime.Range;
trendStrength = 0.0;
}
duration = CalculateDuration(previousState, updateTime, volatilityRegime, trendRegime);
var indicators = new Dictionary();
indicators.Add("atr", atr);
indicators.Add("normal_atr", normalAtr);
indicators.Add("volatility_score", volatilityScore);
indicators.Add("avwap", avwap);
indicators.Add("trend_strength", trendStrength);
indicators.Add("bar_count", _barHistory[symbol].Count);
var newState = new RegimeState(
symbol,
volatilityRegime,
trendRegime,
volatilityScore,
trendStrength,
updateTime,
duration,
indicators);
_currentStates[symbol] = newState;
if (HasTransition(previousState, newState))
AddTransition(symbol, previousState, newState, "Regime changed by detector update");
}
_logger.LogDebug(
"Regime updated for {0}: Vol={1}, Trend={2}, VolScore={3:F3}, TrendStrength={4:F3}",
symbol,
volatilityRegime,
trendRegime,
volatilityScore,
trendStrength);
}
catch (Exception ex)
{
_logger.LogError("UpdateRegime failed for {0}: {1}", symbol, ex.Message);
throw;
}
}
///
/// Gets current regime state for a symbol.
///
/// Instrument symbol.
/// Current state, or null if unavailable.
public RegimeState GetCurrentRegime(string symbol)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentNullException("symbol");
lock (_lock)
{
if (!_currentStates.ContainsKey(symbol))
return null;
return _currentStates[symbol];
}
}
///
/// Returns whether strategy behavior should be adjusted for current regime.
///
/// Instrument symbol.
/// Current strategy intent.
/// True when strategy should adjust execution/sizing behavior.
public bool ShouldAdjustStrategy(string symbol, StrategyIntent intent)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentNullException("symbol");
if (intent == null)
throw new ArgumentNullException("intent");
try
{
RegimeState state;
lock (_lock)
{
if (!_currentStates.ContainsKey(symbol))
return false;
state = _currentStates[symbol];
}
if (state.VolatilityRegime == VolatilityRegime.Extreme)
return true;
if (state.VolatilityRegime == VolatilityRegime.High)
return true;
if (state.TrendRegime == TrendRegime.Range)
return true;
if (state.TrendRegime == TrendRegime.StrongDown && intent.Side == OrderSide.Buy)
return true;
if (state.TrendRegime == TrendRegime.StrongUp && intent.Side == OrderSide.Sell)
return true;
return false;
}
catch (Exception ex)
{
_logger.LogError("ShouldAdjustStrategy failed for {0}: {1}", symbol, ex.Message);
throw;
}
}
///
/// Gets transitions for a symbol within a recent time period.
///
/// Instrument symbol.
/// Lookback period.
/// Matching transition events.
public List GetRecentTransitions(string symbol, TimeSpan period)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentNullException("symbol");
if (period < TimeSpan.Zero)
throw new ArgumentException("period must be non-negative", "period");
try
{
lock (_lock)
{
if (!_transitions.ContainsKey(symbol))
return new List();
var cutoff = DateTime.UtcNow - period;
var result = new List();
var list = _transitions[symbol];
for (var i = 0; i < list.Count; i++)
{
if (list[i].TransitionTime >= cutoff)
result.Add(list[i]);
}
return result;
}
}
catch (Exception ex)
{
_logger.LogError("GetRecentTransitions failed for {0}: {1}", symbol, ex.Message);
throw;
}
}
private void EnsureCollections(string symbol)
{
if (!_barHistory.ContainsKey(symbol))
_barHistory.Add(symbol, new List());
if (!_transitions.ContainsKey(symbol))
_transitions.Add(symbol, new List());
}
private void AppendBar(string symbol, BarData bar)
{
_barHistory[symbol].Add(bar);
while (_barHistory[symbol].Count > _maxBarsPerSymbol)
{
_barHistory[symbol].RemoveAt(0);
}
}
private static bool HasTransition(RegimeState previousState, RegimeState newState)
{
if (previousState == null)
return false;
return previousState.VolatilityRegime != newState.VolatilityRegime ||
previousState.TrendRegime != newState.TrendRegime;
}
private static TimeSpan CalculateDuration(
RegimeState previousState,
DateTime updateTime,
VolatilityRegime volatilityRegime,
TrendRegime trendRegime)
{
if (previousState == null)
return TimeSpan.Zero;
if (previousState.VolatilityRegime == volatilityRegime && previousState.TrendRegime == trendRegime)
return updateTime - previousState.LastUpdate + previousState.RegimeDuration;
return TimeSpan.Zero;
}
private void AddTransition(string symbol, RegimeState previousState, RegimeState newState, string reason)
{
var previousVol = previousState != null ? previousState.VolatilityRegime : newState.VolatilityRegime;
var previousTrend = previousState != null ? previousState.TrendRegime : newState.TrendRegime;
var transition = new RegimeTransition(
symbol,
previousVol,
newState.VolatilityRegime,
previousTrend,
newState.TrendRegime,
newState.LastUpdate,
reason);
_transitions[symbol].Add(transition);
while (_transitions[symbol].Count > _maxTransitionsPerSymbol)
{
_transitions[symbol].RemoveAt(0);
}
_logger.LogInformation(
"Regime transition for {0}: Vol {1}->{2}, Trend {3}->{4}",
symbol,
previousVol,
newState.VolatilityRegime,
previousTrend,
newState.TrendRegime);
}
}
}