using System; using System.Collections.Generic; using NT8.Core.Logging; namespace NT8.Core.Intelligence { /// /// Manages current risk mode with automatic transitions and optional manual override. /// Thread-safe for all shared state operations. /// public class RiskModeManager { private readonly ILogger _logger; private readonly object _lock = new object(); private readonly Dictionary _configs; private RiskModeState _state; /// /// Creates a new risk mode manager with default mode configurations. /// /// Logger instance. public RiskModeManager(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; _configs = new Dictionary(); InitializeDefaultConfigs(); _state = new RiskModeState( RiskMode.PCP, RiskMode.PCP, DateTime.UtcNow, "Initialization", false, TimeSpan.Zero, new Dictionary()); } /// /// Updates risk mode from current performance data. /// /// Current daily PnL. /// Current win streak. /// Current loss streak. public void UpdateRiskMode(double dailyPnL, int winStreak, int lossStreak) { if (winStreak < 0) throw new ArgumentException("winStreak must be non-negative", "winStreak"); if (lossStreak < 0) throw new ArgumentException("lossStreak must be non-negative", "lossStreak"); try { var metrics = new PerformanceMetrics( dailyPnL, winStreak, lossStreak, CalculateSyntheticWinRate(winStreak, lossStreak), 0.7, VolatilityRegime.Normal); lock (_lock) { if (_state.IsManualOverride) { UpdateModeDuration(); return; } var current = _state.CurrentMode; var next = DetermineTargetMode(current, metrics); if (next != current) { TransitionMode(next, "Automatic transition by performance metrics"); } else { UpdateModeDuration(); } } } catch (Exception ex) { _logger.LogError("UpdateRiskMode failed: {0}", ex.Message); throw; } } /// /// Gets current active risk mode. /// /// Current risk mode. public RiskMode GetCurrentMode() { lock (_lock) { return _state.CurrentMode; } } /// /// Gets config for the specified mode. /// /// Risk mode. /// Mode configuration. public RiskModeConfig GetModeConfig(RiskMode mode) { lock (_lock) { if (!_configs.ContainsKey(mode)) throw new ArgumentException("Mode configuration not found", "mode"); return _configs[mode]; } } /// /// Evaluates whether mode should transition based on provided metrics. /// /// Current mode. /// Performance metrics snapshot. /// True when transition should occur. public bool ShouldTransitionMode(RiskMode current, PerformanceMetrics metrics) { if (metrics == null) throw new ArgumentNullException("metrics"); var target = DetermineTargetMode(current, metrics); return target != current; } /// /// Forces a manual mode override. /// /// Target mode. /// Override reason. public void OverrideMode(RiskMode mode, string reason) { if (string.IsNullOrEmpty(reason)) throw new ArgumentNullException("reason"); try { lock (_lock) { var previous = _state.CurrentMode; _state = new RiskModeState( mode, previous, DateTime.UtcNow, reason, true, TimeSpan.Zero, _state.Metadata); } _logger.LogWarning("Risk mode manually overridden to {0}. Reason: {1}", mode, reason); } catch (Exception ex) { _logger.LogError("OverrideMode failed: {0}", ex.Message); throw; } } /// /// Clears manual override and resets mode to default PCP. /// public void ResetToDefault() { try { lock (_lock) { var previous = _state.CurrentMode; _state = new RiskModeState( RiskMode.PCP, previous, DateTime.UtcNow, "Reset to default", false, TimeSpan.Zero, _state.Metadata); } _logger.LogInformation("Risk mode reset to default PCP"); } catch (Exception ex) { _logger.LogError("ResetToDefault failed: {0}", ex.Message); throw; } } /// /// Returns current risk mode state snapshot. /// /// Risk mode state. public RiskModeState GetState() { lock (_lock) { return _state; } } private void InitializeDefaultConfigs() { _configs.Add( RiskMode.ECP, new RiskModeConfig( RiskMode.ECP, 1.5, TradeGrade.B, 1500.0, 4, true, new Dictionary())); _configs.Add( RiskMode.PCP, new RiskModeConfig( RiskMode.PCP, 1.0, TradeGrade.B, 1000.0, 3, false, new Dictionary())); _configs.Add( RiskMode.DCP, new RiskModeConfig( RiskMode.DCP, 0.5, TradeGrade.A, 600.0, 2, false, new Dictionary())); _configs.Add( RiskMode.HR, new RiskModeConfig( RiskMode.HR, 0.0, TradeGrade.APlus, 0.0, 0, false, new Dictionary())); } private RiskMode DetermineTargetMode(RiskMode current, PerformanceMetrics metrics) { if (metrics.LossStreak >= 3) return RiskMode.HR; if (metrics.VolatilityRegime == VolatilityRegime.Extreme) return RiskMode.HR; if (metrics.DailyPnL >= 500.0 && metrics.RecentWinRate >= 0.80 && metrics.LossStreak == 0) return RiskMode.ECP; if (metrics.DailyPnL <= -200.0 || metrics.RecentWinRate < 0.50) return RiskMode.DCP; if (current == RiskMode.DCP && metrics.WinStreak >= 2 && metrics.RecentExecutionQuality >= 0.70) return RiskMode.PCP; if (current == RiskMode.HR) { if (metrics.DailyPnL >= -100.0 && metrics.LossStreak <= 1) return RiskMode.DCP; return RiskMode.HR; } return RiskMode.PCP; } private void TransitionMode(RiskMode nextMode, string reason) { var previous = _state.CurrentMode; _state = new RiskModeState( nextMode, previous, DateTime.UtcNow, reason, false, TimeSpan.Zero, _state.Metadata); _logger.LogInformation("Risk mode transition: {0} -> {1}. Reason: {2}", previous, nextMode, reason); } private void UpdateModeDuration() { _state.ModeDuration = DateTime.UtcNow - _state.LastTransitionAtUtc; } private static double CalculateSyntheticWinRate(int winStreak, int lossStreak) { var denominator = winStreak + lossStreak; if (denominator <= 0) return 0.5; var ratio = (double)winStreak / denominator; if (ratio < 0.0) return 0.0; if (ratio > 1.0) return 1.0; return ratio; } } }