//+------------------------------------------------------------------+ //| RiskManager.mqh | //| Institutional Grade Risk Management Module | //| Complete Risk Control System | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| RiskManager.mqh | //| Advanced Risk Management System | //+------------------------------------------------------------------+ #ifndef RISK_MANAGER_MQH #define RISK_MANAGER_MQH #include #include "DataTypes.mqh" #include "Utilities.mqh" //+------------------------------------------------------------------+ //| Risk Manager Class | //+------------------------------------------------------------------+ class CRiskManager : public CObject { private: // Dependencies CUtilities* m_utils; // Risk parameters RiskParameters m_params; // Current state double m_daily_loss; double m_max_daily_loss; datetime m_day_start; double m_starting_balance; double m_current_exposure; int m_open_positions; int m_magic_number; // Risk tracking double m_peak_balance; double m_current_drawdown; ENUM_RISK_EVENT m_last_risk_event; // Performance tracking for Kelly/Optimal F double m_win_rate; double m_average_win; double m_average_loss; double m_trade_returns[]; int m_return_count; // Helper methods double CalculateImpliedRisk(const TradeInfo &trade); double CalculateCorrelationRisk(string symbol, const TradeInfo &trade); void UpdateDrawdown(); bool CheckDailyLoss(); bool CheckMaxDrawdown(); double GetSymbolExposure(string symbol); public: // Constructor/Destructor CRiskManager(); ~CRiskManager(); // Initialization bool Initialize(CUtilities* utils, const RiskParameters ¶ms); void SetMagicNumber(int magic) { m_magic_number = magic; } // Core risk functions double CalculatePositionSize(string symbol, double stop_distance, ENUM_POSITION_SIZING method); bool ValidateNewPosition(string symbol, double lot_size, double stop_distance); bool ValidateAndAdjustRisk(TradeInfo &trade); double CalculateTradeRisk(double lot_size, double stop_distance, string symbol); bool EnforceRiskRules(TradeInfo &trade); // Risk status methods bool CanOpenNewPosition(); bool IsDailyLossLimitReached(); bool IsDrawdownLimitReached(); double GetCurrentRiskLevel(); double GetAvailableRisk(); // Position sizing calculations double CalculateKellyCriterion(double win_rate, double avg_win, double avg_loss); double CalculateOptimalF(const double &returns[], int count); double CalculateVolatilityAdjustedSize(string symbol, double base_size); // Risk metrics double GetCurrentExposure() const { return m_current_exposure; } double GetDailyLoss() const { return m_daily_loss; } double GetCurrentDrawdown() const { return m_current_drawdown; } int GetOpenPositions() const { return m_open_positions; } ENUM_RISK_EVENT GetLastRiskEvent() const { return m_last_risk_event; } // Performance updates void UpdatePerformanceMetrics(double win_rate, double avg_win, double avg_loss); void AddTradeReturn(double return_value); // Settings void UpdateParameters(const RiskParameters ¶ms) { m_params = params; } void ResetDailyCounters(); // Correlation and exposure double CalculatePortfolioCorrelation(); bool CheckSymbolConcentration(string symbol); double GetMaxPositionSize(string symbol, double stop_distance); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRiskManager::CRiskManager() { m_utils = NULL; m_daily_loss = 0; m_max_daily_loss = 0; m_day_start = 0; m_starting_balance = 0; m_current_exposure = 0; m_open_positions = 0; m_magic_number = 0; m_peak_balance = 0; m_current_drawdown = 0; m_last_risk_event = RISK_NONE; m_win_rate = 50; m_average_win = 0; m_average_loss = 0; m_return_count = 0; ArrayResize(m_trade_returns, 100); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRiskManager::~CRiskManager() { ArrayFree(m_trade_returns); } //+------------------------------------------------------------------+ //| Initialize Risk Manager | //+------------------------------------------------------------------+ bool CRiskManager::Initialize(CUtilities* utils, const RiskParameters ¶ms) { if(utils == NULL) return false; m_utils = utils; m_params = params; m_starting_balance = AccountInfoDouble(ACCOUNT_BALANCE); m_peak_balance = m_starting_balance; m_day_start = TimeCurrent(); m_max_daily_loss = m_starting_balance * m_params.max_daily_loss / 100; m_utils.Log("Risk Manager initialized", LOG_INFO); m_utils.Log(StringFormat("Max risk per trade: %.2f%%", m_params.max_risk_per_trade), LOG_INFO); m_utils.Log(StringFormat("Max daily loss: %.2f%%", m_params.max_daily_loss), LOG_INFO); m_utils.Log(StringFormat("Max drawdown: %.2f%%", m_params.max_drawdown), LOG_INFO); m_utils.Log(StringFormat("Max positions: %d", (int)m_params.max_positions), LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Calculate Position Size | //+------------------------------------------------------------------+ double CRiskManager::CalculatePositionSize(string symbol, double stop_distance, ENUM_POSITION_SIZING method) { if(stop_distance <= 0) return 0; double balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk_amount = balance * m_params.max_risk_per_trade / 100; double lot_size = 0; switch(method) { case SIZING_FIXED_LOT: lot_size = 0.1; // Default fixed lot break; case SIZING_FIXED_PERCENT: lot_size = m_utils.CalculatePositionSize(risk_amount, stop_distance, symbol); break; case SIZING_ATR_BASED: { double atr = m_utils.GetATR(14); if(atr > 0) { // Size based on ATR volatility double normalized_stop = stop_distance / atr; double adjusted_risk = risk_amount / normalized_stop; lot_size = m_utils.CalculatePositionSize(adjusted_risk, stop_distance, symbol); } } break; case SIZING_KELLY_CRITERION: { double kelly_fraction = CalculateKellyCriterion(m_win_rate, m_average_win, m_average_loss); double kelly_risk = balance * kelly_fraction; lot_size = m_utils.CalculatePositionSize(kelly_risk, stop_distance, symbol); } break; case SIZING_OPTIMAL_F: { if(m_return_count > 10) { double optimal_f = CalculateOptimalF(m_trade_returns, m_return_count); double optimal_risk = balance * optimal_f; lot_size = m_utils.CalculatePositionSize(optimal_risk, stop_distance, symbol); } else { // Fall back to fixed percent if insufficient data lot_size = m_utils.CalculatePositionSize(risk_amount, stop_distance, symbol); } } break; case SIZING_VOLATILITY_ADJUSTED: lot_size = CalculateVolatilityAdjustedSize(symbol, m_utils.CalculatePositionSize(risk_amount, stop_distance, symbol)); break; default: lot_size = m_utils.CalculatePositionSize(risk_amount, stop_distance, symbol); break; } // Apply risk multiplier lot_size *= m_params.risk_multiplier; // Apply scale with equity if enabled if(m_params.scale_with_equity) { double equity = AccountInfoDouble(ACCOUNT_EQUITY); double equity_ratio = equity / m_starting_balance; if(equity_ratio > 1.0) lot_size *= MathSqrt(equity_ratio); // Conservative scaling } // Ensure within broker limits double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(lot_size < min_lot) lot_size = min_lot; if(lot_size > max_lot) lot_size = max_lot; // Final check against maximum position size double max_allowed = GetMaxPositionSize(symbol, stop_distance); if(lot_size > max_allowed) lot_size = max_allowed; return m_utils.NormalizeLot(lot_size, symbol); } //+------------------------------------------------------------------+ //| Validate New Position | //+------------------------------------------------------------------+ bool CRiskManager::ValidateNewPosition(string symbol, double lot_size, double stop_distance) { // Update position count m_open_positions = 0; for(int i = 0; i < PositionsTotal(); i++) { if(PositionGetTicket(i) > 0) { if(m_magic_number == 0 || PositionGetInteger(POSITION_MAGIC) == m_magic_number) m_open_positions++; } } // Check if we can open new positions if(m_open_positions >= m_params.max_positions) { m_utils.Log("Maximum positions reached", LOG_WARNING); m_last_risk_event = RISK_OVERLEVERAGED; return false; } // Check daily loss limit if(IsDailyLossLimitReached()) { m_utils.Log("Daily loss limit reached", LOG_WARNING); m_last_risk_event = RISK_DAILY_LOSS_LIMIT; return false; } // Check drawdown if(IsDrawdownLimitReached()) { m_utils.Log("Drawdown limit reached", LOG_WARNING); m_last_risk_event = RISK_DRAWDOWN_LIMIT; return false; } // Calculate potential risk double risk = CalculateTradeRisk(lot_size, stop_distance, symbol); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk_percent = (risk / balance) * 100; if(risk_percent > m_params.max_risk_per_trade) { m_utils.Log(StringFormat("Risk too high: %.2f%% > %.2f%%", risk_percent, m_params.max_risk_per_trade), LOG_WARNING); m_last_risk_event = RISK_OVERLEVERAGED; return false; } // Check symbol concentration if(!CheckSymbolConcentration(symbol)) { m_utils.Log("Symbol concentration limit reached", LOG_WARNING); m_last_risk_event = RISK_CORRELATION_HIGH; return false; } // Check margin requirements double required_margin = 0; if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, lot_size, SymbolInfoDouble(symbol, SYMBOL_ASK), required_margin)) { m_utils.Log("Failed to calculate margin", LOG_ERROR); return false; } double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if(required_margin > free_margin * 0.8) // Use max 80% of free margin { m_utils.Log("Insufficient margin", LOG_WARNING); m_last_risk_event = RISK_MARGIN_CALL; return false; } // Check volatility conditions double current_spread = m_utils.CalculateSpread(symbol); double max_spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2.0; if(current_spread > max_spread) { m_utils.Log("Spread too high for entry", LOG_WARNING); m_last_risk_event = RISK_VOLATILITY_SPIKE; return false; } return true; } //+------------------------------------------------------------------+ //| Validate and Adjust Risk for Existing Trade | //+------------------------------------------------------------------+ bool CRiskManager::ValidateAndAdjustRisk(TradeInfo &trade) { // Calculate current risk double implied_risk = CalculateImpliedRisk(trade); double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Update trade risk metrics trade.risk_amount = implied_risk; trade.risk_percent = (implied_risk / balance) * 100; // Calculate current R/R if(trade.tp != 0 && trade.sl != 0) { trade.current_rr = m_utils.CalculateRiskReward(trade.entry_price, trade.sl, trade.tp, trade.type); } // Check if trade violates risk rules if(trade.risk_percent > m_params.max_risk_per_trade * 1.5) { m_utils.Log(StringFormat("Trade %d exceeds 150%% risk limit: %.2f%%", trade.ticket, trade.risk_percent), LOG_WARNING); trade.close_requested = true; trade.close_reason = "Risk limit exceeded"; return false; } // Check correlation risk double correlation_risk = CalculateCorrelationRisk(trade.symbol, trade); if(correlation_risk > m_params.max_correlation) { m_utils.Log(StringFormat("High correlation risk: %.2f", correlation_risk), LOG_WARNING); m_last_risk_event = RISK_CORRELATION_HIGH; // Consider partial close trade.partial_close_volume = trade.volume * 0.5; trade.partial_close_reason = "High correlation"; } // Check if R/R has deteriorated if(trade.current_rr < m_params.min_risk_reward * 0.5 && trade.profit < 0) { m_utils.Log(StringFormat("Trade %d R/R deteriorated: %.2f", trade.ticket, trade.current_rr), LOG_WARNING); trade.close_requested = true; trade.close_reason = "Poor risk/reward"; return false; } trade.last_update = TimeCurrent(); return true; } //+------------------------------------------------------------------+ //| Calculate Trade Risk | //+------------------------------------------------------------------+ double CRiskManager::CalculateTradeRisk(double lot_size, double stop_distance, string symbol) { if(stop_distance <= 0) return 0; double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(tick_size == 0) return 0; double risk = (stop_distance / tick_size) * tick_value * lot_size; return risk; } //+------------------------------------------------------------------+ //| Enforce Risk Rules | //+------------------------------------------------------------------+ bool CRiskManager::EnforceRiskRules(TradeInfo &trade) { // Check if emergency close needed if(trade.risk_percent > m_params.max_risk_per_trade * 2.0) { trade.close_requested = true; trade.close_reason = "Emergency: Risk exceeded 200% of limit"; m_last_risk_event = RISK_OVERLEVERAGED; m_utils.LogRiskEvent(RISK_OVERLEVERAGED, StringFormat("Trade %d risk at %.2f%%", trade.ticket, trade.risk_percent)); return false; } // Check drawdown UpdateDrawdown(); if(m_current_drawdown > m_params.max_drawdown) { trade.close_requested = true; trade.close_reason = "Maximum drawdown reached"; m_last_risk_event = RISK_DRAWDOWN_LIMIT; m_utils.LogRiskEvent(RISK_DRAWDOWN_LIMIT, StringFormat("Drawdown at %.2f%%", m_current_drawdown)); return false; } // Check daily loss if(CheckDailyLoss()) { trade.close_requested = true; trade.close_reason = "Daily loss limit reached"; m_last_risk_event = RISK_DAILY_LOSS_LIMIT; m_utils.LogRiskEvent(RISK_DAILY_LOSS_LIMIT, StringFormat("Daily loss at %.2f", m_daily_loss)); return false; } // Check margin level double margin_level = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); if(margin_level > 0 && margin_level < 150) { trade.close_requested = true; trade.close_reason = "Low margin level"; m_last_risk_event = RISK_MARGIN_CALL; m_utils.LogRiskEvent(RISK_MARGIN_CALL, StringFormat("Margin level at %.2f%%", margin_level)); return false; } return true; } //+------------------------------------------------------------------+ //| Calculate Implied Risk (Helper) | //+------------------------------------------------------------------+ double CRiskManager::CalculateImpliedRisk(const TradeInfo &trade) { if(trade.sl == 0) { // Estimate risk based on ATR if no stop loss double atr = m_utils.GetATR(14); double estimated_stop = atr * 2.0; return CalculateTradeRisk(trade.volume, estimated_stop, trade.symbol); } double stop_distance = MathAbs(trade.entry_price - trade.sl); return CalculateTradeRisk(trade.volume, stop_distance, trade.symbol); } //+------------------------------------------------------------------+ //| Calculate Correlation Risk (Helper) | //+------------------------------------------------------------------+ double CRiskManager::CalculateCorrelationRisk(string symbol, const TradeInfo &trade) { double correlation = 0; int same_direction_trades = 0; int same_symbol_trades = 0; double total_exposure = 0; // Analyze existing positions for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0 || ticket == trade.ticket) continue; // Check magic number if set if(m_magic_number != 0 && PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; string pos_symbol = PositionGetString(POSITION_SYMBOL); ENUM_ORDER_TYPE pos_type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); double pos_volume = PositionGetDouble(POSITION_VOLUME); // Count same direction trades if(pos_type == trade.type) same_direction_trades++; // Count same symbol trades if(pos_symbol == symbol) { same_symbol_trades++; total_exposure += pos_volume; } // Check for correlated pairs (simplified) if(StringFind(pos_symbol, StringSubstr(symbol, 0, 3)) >= 0 || StringFind(pos_symbol, StringSubstr(symbol, 3, 3)) >= 0) { correlation += 0.3; // Base correlation for currency pairs } } // Calculate correlation score if(m_open_positions > 0) { correlation += (double)same_direction_trades / m_open_positions * 0.5; correlation += (double)same_symbol_trades / m_open_positions * 0.7; } // Normalize to 0-1 range correlation = MathMin(correlation, 1.0); return correlation; } //+------------------------------------------------------------------+ //| Update Drawdown | //+------------------------------------------------------------------+ void CRiskManager::UpdateDrawdown() { double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); if(current_balance > m_peak_balance) m_peak_balance = current_balance; if(m_peak_balance > 0) m_current_drawdown = ((m_peak_balance - current_balance) / m_peak_balance) * 100; else m_current_drawdown = 0; } //+------------------------------------------------------------------+ //| Check Daily Loss | //+------------------------------------------------------------------+ bool CRiskManager::CheckDailyLoss() { MqlDateTime current_time, start_time; TimeToStruct(TimeCurrent(), current_time); TimeToStruct(m_day_start, start_time); // Reset if new day if(current_time.day != start_time.day) { ResetDailyCounters(); return false; } double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); m_daily_loss = m_starting_balance - current_balance; return (m_daily_loss >= m_max_daily_loss); } //+------------------------------------------------------------------+ //| Can Open New Position | //+------------------------------------------------------------------+ bool CRiskManager::CanOpenNewPosition() { // Update position count m_open_positions = 0; for(int i = 0; i < PositionsTotal(); i++) { if(PositionGetTicket(i) > 0) { if(m_magic_number == 0 || PositionGetInteger(POSITION_MAGIC) == m_magic_number) m_open_positions++; } } if(m_open_positions >= m_params.max_positions) { m_utils.Log(StringFormat("Cannot open: Max positions (%d) reached", (int)m_params.max_positions), LOG_DEBUG); return false; } if(IsDailyLossLimitReached()) { m_utils.Log("Cannot open: Daily loss limit reached", LOG_DEBUG); return false; } if(IsDrawdownLimitReached()) { m_utils.Log("Cannot open: Drawdown limit reached", LOG_DEBUG); return false; } // Check margin level double margin_level = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); if(margin_level > 0 && margin_level < 200) { m_utils.Log(StringFormat("Cannot open: Low margin level (%.2f%%)", margin_level), LOG_DEBUG); return false; } // Check if market is open if(!m_utils.IsMarketOpen()) { m_utils.Log("Cannot open: Market closed", LOG_DEBUG); return false; } return true; } //+------------------------------------------------------------------+ //| Is Daily Loss Limit Reached | //+------------------------------------------------------------------+ bool CRiskManager::IsDailyLossLimitReached() { return CheckDailyLoss(); } //+------------------------------------------------------------------+ //| Is Drawdown Limit Reached | //+------------------------------------------------------------------+ bool CRiskManager::IsDrawdownLimitReached() { UpdateDrawdown(); return (m_current_drawdown >= m_params.max_drawdown); } //+------------------------------------------------------------------+ //| Get Current Risk Level | //+------------------------------------------------------------------+ double CRiskManager::GetCurrentRiskLevel() { double total_risk = 0; m_current_exposure = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; // Check magic number if set if(m_magic_number != 0 && PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; double volume = PositionGetDouble(POSITION_VOLUME); double entry = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); string symbol = PositionGetString(POSITION_SYMBOL); m_current_exposure += volume; if(sl != 0) { double stop_distance = MathAbs(entry - sl); total_risk += CalculateTradeRisk(volume, stop_distance, symbol); } else { // Estimate risk based on ATR if no stop double atr = m_utils.GetATR(14); total_risk += CalculateTradeRisk(volume, atr * 2.0, symbol); } } double balance = AccountInfoDouble(ACCOUNT_BALANCE); return balance > 0 ? (total_risk / balance) * 100 : 0; } //+------------------------------------------------------------------+ //| Get Available Risk | //+------------------------------------------------------------------+ double CRiskManager::GetAvailableRisk() { double current_risk = GetCurrentRiskLevel(); double max_total_risk = m_params.max_risk_per_trade * m_params.max_positions; double available = max_total_risk - current_risk; return (available > 0) ? available : 0; } //+------------------------------------------------------------------+ //| Calculate Kelly Criterion | //+------------------------------------------------------------------+ double CRiskManager::CalculateKellyCriterion(double win_rate, double avg_win, double avg_loss) { if(avg_loss <= 0) return 0.01; // Default 1% if no loss data double b = avg_win / MathAbs(avg_loss); // Win/loss ratio double p = win_rate / 100.0; // Win probability double q = 1.0 - p; // Loss probability if(b <= 0) return 0.01; double kelly = (p * b - q) / b; // Apply Kelly fraction (typically 25% of full Kelly for safety) kelly *= 0.25; // Ensure within reasonable bounds (1% to 25%) if(kelly < 0.01) kelly = 0.01; if(kelly > 0.25) kelly = 0.25; m_utils.Log(StringFormat("Kelly Criterion: %.2f%% (WR=%.1f%%, W/L=%.2f)", kelly * 100, win_rate, b), LOG_DEBUG); return kelly; } //+------------------------------------------------------------------+ //| Calculate Optimal F | //+------------------------------------------------------------------+ double CRiskManager::CalculateOptimalF(const double &returns[], int count) { if(count < 10) return 0.02; // Default 2% if insufficient data // Find largest loss double max_loss = 0; for(int i = 0; i < count; i++) { if(returns[i] < max_loss) max_loss = returns[i]; } if(max_loss >= 0) return 0.02; // No losses, use default max_loss = MathAbs(max_loss); // Test different F values to find optimal double optimal_f = 0.01; double max_twr = 0; for(double f = 0.01; f <= 0.5; f += 0.01) { double twr = 1.0; // Terminal wealth relative for(int i = 0; i < count; i++) { double hpr = 1.0 + f * (returns[i] / max_loss); if(hpr <= 0) { twr = 0; break; } twr *= hpr; } if(twr > max_twr) { max_twr = twr; optimal_f = f; } } // Apply safety factor (50% of optimal) optimal_f *= 0.5; // Ensure within reasonable bounds if(optimal_f < 0.01) optimal_f = 0.01; if(optimal_f > 0.25) optimal_f = 0.25; m_utils.Log(StringFormat("Optimal F: %.2f%% (TWR=%.2f)", optimal_f * 100, max_twr), LOG_DEBUG); return optimal_f; } //+------------------------------------------------------------------+ //| Calculate Volatility Adjusted Size | //+------------------------------------------------------------------+ double CRiskManager::CalculateVolatilityAdjustedSize(string symbol, double base_size) { double current_atr = m_utils.GetATR(14); // Calculate historical average ATR (simplified - use 50 period MA of ATR) int atr_handle = iATR(symbol, PERIOD_CURRENT, 14); if(atr_handle == INVALID_HANDLE) return base_size; double atr_buffer[50]; if(CopyBuffer(atr_handle, 0, 0, 50, atr_buffer) < 50) { IndicatorRelease(atr_handle); return base_size; } IndicatorRelease(atr_handle); double avg_atr = 0; for(int i = 0; i < 50; i++) avg_atr += atr_buffer[i]; avg_atr /= 50; if(avg_atr == 0) return base_size; double volatility_ratio = current_atr / avg_atr; double adjusted_size = base_size; // Adjust size based on volatility if(volatility_ratio > 2.0) adjusted_size = base_size * 0.25; // Very high volatility - reduce to 25% else if(volatility_ratio > 1.5) adjusted_size = base_size * 0.5; // High volatility - reduce to 50% else if(volatility_ratio > 1.2) adjusted_size = base_size * 0.75; // Elevated volatility - reduce to 75% else if(volatility_ratio < 0.5) adjusted_size = base_size * 1.5; // Low volatility - increase to 150% else if(volatility_ratio < 0.8) adjusted_size = base_size * 1.25; // Below average volatility - increase to 125% m_utils.Log(StringFormat("Volatility adjustment: %.2f (ratio=%.2f)", adjusted_size/base_size, volatility_ratio), LOG_DEBUG); return adjusted_size; } //+------------------------------------------------------------------+ //| Update Performance Metrics | //+------------------------------------------------------------------+ void CRiskManager::UpdatePerformanceMetrics(double win_rate, double avg_win, double avg_loss) { m_win_rate = win_rate; m_average_win = avg_win; m_average_loss = avg_loss; } //+------------------------------------------------------------------+ //| Add Trade Return | //+------------------------------------------------------------------+ void CRiskManager::AddTradeReturn(double return_value) { if(m_return_count >= ArraySize(m_trade_returns)) { ArrayResize(m_trade_returns, m_return_count + 100); } m_trade_returns[m_return_count] = return_value; m_return_count++; } //+------------------------------------------------------------------+ //| Reset Daily Counters | //+------------------------------------------------------------------+ void CRiskManager::ResetDailyCounters() { m_day_start = TimeCurrent(); m_starting_balance = AccountInfoDouble(ACCOUNT_BALANCE); m_daily_loss = 0; m_max_daily_loss = m_starting_balance * m_params.max_daily_loss / 100; m_utils.Log("Daily risk counters reset", LOG_INFO); m_utils.Log(StringFormat("New day starting balance: %.2f", m_starting_balance), LOG_INFO); } //+------------------------------------------------------------------+ //| Calculate Portfolio Correlation | //+------------------------------------------------------------------+ double CRiskManager::CalculatePortfolioCorrelation() { if(m_open_positions <= 1) return 0; double correlation = 0; string symbols[]; ENUM_ORDER_TYPE types[]; ArrayResize(symbols, m_open_positions); ArrayResize(types, m_open_positions); int index = 0; for(int i = 0; i < PositionsTotal() && index < m_open_positions; i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(m_magic_number != 0 && PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; symbols[index] = PositionGetString(POSITION_SYMBOL); types[index] = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); index++; } // Calculate pairwise correlations (simplified) for(int i = 0; i < index - 1; i++) { for(int j = i + 1; j < index; j++) { // Same direction trades if(types[i] == types[j]) correlation += 0.3; // Check currency overlap string base1 = StringSubstr(symbols[i], 0, 3); string quote1 = StringSubstr(symbols[i], 3, 3); string base2 = StringSubstr(symbols[j], 0, 3); string quote2 = StringSubstr(symbols[j], 3, 3); if(base1 == base2 || quote1 == quote2) correlation += 0.4; else if(base1 == quote2 || quote1 == base2) correlation += 0.2; } } // Normalize int pair_count = index * (index - 1) / 2; if(pair_count > 0) correlation /= pair_count; return MathMin(correlation, 1.0); } //+------------------------------------------------------------------+ //| Check Symbol Concentration | //+------------------------------------------------------------------+ bool CRiskManager::CheckSymbolConcentration(string symbol) { double symbol_exposure = GetSymbolExposure(symbol); double total_exposure = m_current_exposure; if(total_exposure == 0) return true; double concentration = symbol_exposure / total_exposure; // Don't allow more than 40% in single symbol if(concentration > 0.4) { m_utils.Log(StringFormat("Symbol concentration too high: %.1f%%", concentration * 100), LOG_WARNING); return false; } return true; } //+------------------------------------------------------------------+ //| Get Symbol Exposure | //+------------------------------------------------------------------+ double CRiskManager::GetSymbolExposure(string symbol) { double exposure = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(m_magic_number != 0 && PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; if(PositionGetString(POSITION_SYMBOL) == symbol) exposure += PositionGetDouble(POSITION_VOLUME); } return exposure; } //+------------------------------------------------------------------+ //| Get Maximum Position Size | //+------------------------------------------------------------------+ double CRiskManager::GetMaxPositionSize(string symbol, double stop_distance) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double max_risk = balance * m_params.max_risk_per_trade / 100; // Adjust for current exposure double available_risk = GetAvailableRisk(); if(available_risk < m_params.max_risk_per_trade) max_risk = balance * available_risk / 100; double max_size = m_utils.CalculatePositionSize(max_risk, stop_distance, symbol); return max_size; } #endif // RISK_MANAGER_MQH