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