//+------------------------------------------------------------------+ //| RiskManager.mqh | //| Fixed Risk Management Implementation | //+------------------------------------------------------------------+ #ifndef RISK_MANAGER_MQH #define RISK_MANAGER_MQH #include "DataTypes.mqh" #include "Utilities.mqh" //+------------------------------------------------------------------+ //| Risk Manager Class | //+------------------------------------------------------------------+ class CRiskManager { private: //--- Member Variables - Complete Declaration CUtilities* m_pUtils; // Utilities pointer RiskParameters m_params; // Risk parameters bool m_isInitialized; // Initialization flag //--- Risk Limits double m_maxRiskPerTrade; // Max risk per trade (%) double m_maxDailyLoss; // Max daily loss (%) double m_maxDrawdown; // Max drawdown (%) double m_maxLeverage; // Max leverage int m_maxPositions; // Max concurrent positions double m_maxCorrelation; // Max correlation threshold //--- Current State double m_currentDailyLoss; // Today's loss double m_currentDrawdown; // Current drawdown double m_peakBalance; // Peak balance for DD calc datetime m_lastResetTime; // Last daily reset //--- Position Tracking PositionRisk m_positions[]; // Array of position risks int m_positionCount; // Current position count //--- Risk Metrics Cache RiskMetrics m_currentMetrics; // Current risk metrics datetime m_lastUpdateTime; // Last metrics update //--- Private Methods void UpdateDailyMetrics(); void UpdateDrawdown(); void CalculatePositionRisks(); double CalculateCorrelation(const double &prices1[], const double &prices2[]); bool CheckRiskLimits(double proposedRisk); void LogRiskEvent(ENUM_RISK_EVENT event, string details); double InternalCalculateLotSize(double riskAmount, double stopLossPoints, string symbol); public: //--- Constructor/Destructor CRiskManager(); ~CRiskManager(); //--- Initialization bool Initialize(CUtilities* utils, const RiskParameters ¶ms); void Deinitialize(); //--- Risk Calculation Methods double CalculatePositionSize(double stopLossPoints, string symbol = NULL); double CalculatePositionSizeByMethod(ENUM_POSITION_SIZING method, double balance, double stopLossPoints, string symbol = NULL); double CalculateRiskAmount(double balance, double riskPercent); //--- Risk Assessment bool CanOpenNewPosition(string symbol, double proposedVolume); bool IsRiskLimitExceeded(); RiskMetrics GetCurrentRiskMetrics(); double GetAvailableRisk(); //--- Stop Loss Calculation double CalculateStopLoss(double entryPrice, bool isBuy, ENUM_SL_MODE mode, string symbol = NULL); double CalculateTakeProfit(double entryPrice, double stopLoss, bool isBuy, double riskRewardRatio); //--- Risk Monitoring void UpdateRiskMetrics(); ENUM_RISK_ACTION EvaluateRiskAction(); void ExecuteRiskAction(ENUM_RISK_ACTION action); //--- Utility Methods double GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period); double GetVolatility(string symbol); bool IsHighVolatility(string symbol); //--- Getters bool IsInitialized() { return m_isInitialized; } double GetMaxRiskPerTrade() { return m_maxRiskPerTrade; } double GetCurrentDrawdown() { return m_currentDrawdown; } int GetOpenPositionCount() { return m_positionCount; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRiskManager::CRiskManager() { m_pUtils = NULL; m_isInitialized = false; m_maxRiskPerTrade = 1.0; m_maxDailyLoss = 5.0; m_maxDrawdown = 20.0; m_maxLeverage = 10.0; m_maxPositions = 5; m_maxCorrelation = 0.7; m_currentDailyLoss = 0; m_currentDrawdown = 0; m_peakBalance = 0; m_lastResetTime = 0; m_positionCount = 0; m_lastUpdateTime = 0; ArrayResize(m_positions, 0); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRiskManager::~CRiskManager() { Deinitialize(); } //+------------------------------------------------------------------+ //| Initialize Risk Manager | //+------------------------------------------------------------------+ bool CRiskManager::Initialize(CUtilities* utils, const RiskParameters ¶ms) { if(utils == NULL) { Print("RiskManager: Invalid utilities pointer"); return false; } m_pUtils = utils; m_params = params; // Store parameters m_maxRiskPerTrade = params.maxRiskPerTrade; m_maxDailyLoss = params.maxDailyRisk; m_maxDrawdown = params.maxDrawdown; m_maxLeverage = params.maxLeverage; m_maxPositions = params.maxPositions; m_maxCorrelation = params.maxCorrelation; // Initialize tracking m_peakBalance = AccountInfoDouble(ACCOUNT_BALANCE); m_lastResetTime = TimeCurrent(); m_currentDailyLoss = 0; m_currentDrawdown = 0; // Log initialization CLogger::Log(LOG_INFO, "Risk Manager initialized"); CLogger::Log(LOG_INFO, StringFormat("Max Risk Per Trade: %.2f%%", m_maxRiskPerTrade)); CLogger::Log(LOG_INFO, StringFormat("Max Daily Loss: %.2f%%", m_maxDailyLoss)); CLogger::Log(LOG_INFO, StringFormat("Max Drawdown: %.2f%%", m_maxDrawdown)); CLogger::Log(LOG_INFO, StringFormat("Max Positions: %d", m_maxPositions)); m_isInitialized = true; return true; } //+------------------------------------------------------------------+ //| Deinitialize | //+------------------------------------------------------------------+ void CRiskManager::Deinitialize() { ArrayFree(m_positions); m_isInitialized = false; CLogger::Log(LOG_INFO, "Risk Manager deinitialized"); } //+------------------------------------------------------------------+ //| Calculate Position Size | //+------------------------------------------------------------------+ double CRiskManager::CalculatePositionSize(double stopLossPoints, string symbol) { if(!m_isInitialized) return 0; if(symbol == NULL) symbol = _Symbol; double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = CalculateRiskAmount(balance, m_maxRiskPerTrade); return InternalCalculateLotSize(riskAmount, stopLossPoints, symbol); } //+------------------------------------------------------------------+ //| Internal Calculate Lot Size | //+------------------------------------------------------------------+ double CRiskManager::InternalCalculateLotSize(double riskAmount, double stopLossPoints, string symbol) { if(stopLossPoints <= 0) return 0; double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(tickSize <= 0 || tickValue <= 0) return minLot; double lotSize = (riskAmount * tickSize) / (stopLossPoints * tickValue); // Round to lot step lotSize = MathFloor(lotSize / lotStep) * lotStep; // Apply limits if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; return NormalizeDouble(lotSize, 2); } //+------------------------------------------------------------------+ //| Calculate Position Size by Method | //+------------------------------------------------------------------+ double CRiskManager::CalculatePositionSizeByMethod(ENUM_POSITION_SIZING method, double balance, double stopLossPoints, string symbol) { if(!m_isInitialized) return 0; if(symbol == NULL) symbol = _Symbol; if(stopLossPoints <= 0) return 0; double lotSize = 0; double riskAmount = CalculateRiskAmount(balance, m_maxRiskPerTrade); switch(method) { case POSITION_SIZE_FIXED: lotSize = 0.01; break; case POSITION_SIZE_RISK: lotSize = InternalCalculateLotSize(riskAmount, stopLossPoints, symbol); break; case POSITION_SIZE_VOLATILITY: { double atr = GetATR(symbol, PERIOD_CURRENT, 14); if(atr > 0) { double adjustedStop = atr * 2.0; double adjustedRisk = riskAmount * 0.75; lotSize = InternalCalculateLotSize(adjustedRisk, adjustedStop, symbol); } else { lotSize = 0.01; } } break; case POSITION_SIZE_KELLY: { double winRate = 0.55; double avgWinLoss = 1.5; double kellyPercent = (winRate * avgWinLoss - (1 - winRate)) / avgWinLoss; kellyPercent = MathMax(0, MathMin(kellyPercent, 0.25)); double kellyRisk = balance * kellyPercent; lotSize = InternalCalculateLotSize(kellyRisk, stopLossPoints, symbol); } break; default: lotSize = 0.01; break; } // Apply limits double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; lotSize = MathMax(minLot, MathMin(maxLot, lotSize)); return NormalizeDouble(lotSize, 2); } //+------------------------------------------------------------------+ //| Calculate Risk Amount | //+------------------------------------------------------------------+ double CRiskManager::CalculateRiskAmount(double balance, double riskPercent) { return balance * (riskPercent / 100.0); } //+------------------------------------------------------------------+ //| Check if Can Open New Position | //+------------------------------------------------------------------+ bool CRiskManager::CanOpenNewPosition(string symbol, double proposedVolume) { if(!m_isInitialized) return false; // Check position count if(m_positionCount >= m_maxPositions) { LogRiskEvent(RISK_OVERLEVERAGED, "Max positions reached"); return false; } // Check daily loss if(m_currentDailyLoss >= m_maxDailyLoss) { LogRiskEvent(RISK_DAILY_LOSS_LIMIT, "Daily loss limit reached"); return false; } // Check drawdown if(m_currentDrawdown >= m_maxDrawdown) { LogRiskEvent(RISK_DRAWDOWN_LIMIT, "Drawdown limit reached"); return false; } // Check margin double requiredMargin = 0; if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, proposedVolume, SymbolInfoDouble(symbol, SYMBOL_ASK), requiredMargin)) { return false; } double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if(requiredMargin > freeMargin * 0.9) // Keep 10% buffer { LogRiskEvent(RISK_MARGIN_CALL, "Insufficient margin"); return false; } return true; } //+------------------------------------------------------------------+ //| Check if Risk Limit Exceeded | //+------------------------------------------------------------------+ bool CRiskManager::IsRiskLimitExceeded() { UpdateRiskMetrics(); if(m_currentMetrics.totalRisk > m_maxRiskPerTrade) return true; if(m_currentMetrics.currentDrawdown > m_maxDrawdown) return true; if(m_currentMetrics.dailyLossPercent > m_maxDailyLoss) return true; if(m_currentMetrics.leverage > m_maxLeverage) return true; if(m_currentMetrics.marginLevel < 100 && m_currentMetrics.marginLevel > 0) return true; return false; } //+------------------------------------------------------------------+ //| Get Current Risk Metrics | //+------------------------------------------------------------------+ RiskMetrics CRiskManager::GetCurrentRiskMetrics() { UpdateRiskMetrics(); return m_currentMetrics; } //+------------------------------------------------------------------+ //| Get Available Risk | //+------------------------------------------------------------------+ double CRiskManager::GetAvailableRisk() { double usedRisk = m_currentMetrics.totalRisk; double availableRisk = m_maxRiskPerTrade - usedRisk; return MathMax(0, availableRisk); } //+------------------------------------------------------------------+ //| Calculate Stop Loss | //+------------------------------------------------------------------+ double CRiskManager::CalculateStopLoss(double entryPrice, bool isBuy, ENUM_SL_MODE mode, string symbol) { if(symbol == NULL) symbol = _Symbol; double sl = 0; double point = SymbolInfoDouble(symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); switch(mode) { case SL_FIXED: sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point); break; case SL_ATR: { double atr = GetATR(symbol, PERIOD_CURRENT, 14); if(atr > 0) { sl = isBuy ? entryPrice - (atr * 2.0) : entryPrice + (atr * 2.0); } else { sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point); } } break; case SL_PERCENTAGE: sl = isBuy ? entryPrice * 0.98 : entryPrice * 1.02; break; default: sl = isBuy ? entryPrice - (100 * point) : entryPrice + (100 * point); break; } return NormalizeDouble(sl, digits); } //+------------------------------------------------------------------+ //| Calculate Take Profit | //+------------------------------------------------------------------+ double CRiskManager::CalculateTakeProfit(double entryPrice, double stopLoss, bool isBuy, double riskRewardRatio) { double risk = MathAbs(entryPrice - stopLoss); double reward = risk * riskRewardRatio; double tp = isBuy ? entryPrice + reward : entryPrice - reward; string symbol = _Symbol; int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); return NormalizeDouble(tp, digits); } //+------------------------------------------------------------------+ //| Update Risk Metrics | //+------------------------------------------------------------------+ void CRiskManager::UpdateRiskMetrics() { // Update daily metrics if new day UpdateDailyMetrics(); // Update drawdown UpdateDrawdown(); // Calculate position risks CalculatePositionRisks(); // Calculate total metrics double totalRisk = 0; double totalExposure = 0; for(int i = 0; i < m_positionCount; i++) { totalRisk += m_positions[i].riskPercent; totalExposure += m_positions[i].volume * m_positions[i].marginUsed; } // Update metrics structure m_currentMetrics.totalRisk = totalRisk; m_currentMetrics.maxRisk = m_maxRiskPerTrade; m_currentMetrics.currentDrawdown = m_currentDrawdown; m_currentMetrics.maxDrawdown = m_maxDrawdown; m_currentMetrics.dailyLoss = m_currentDailyLoss; m_currentMetrics.dailyLossPercent = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ? (m_currentDailyLoss / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0; m_currentMetrics.marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); m_currentMetrics.freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); m_currentMetrics.leverage = (AccountInfoDouble(ACCOUNT_EQUITY) > 0) ? totalExposure / AccountInfoDouble(ACCOUNT_EQUITY) : 0; m_currentMetrics.openPositions = m_positionCount; m_lastUpdateTime = TimeCurrent(); } //+------------------------------------------------------------------+ //| Update Daily Metrics | //+------------------------------------------------------------------+ void CRiskManager::UpdateDailyMetrics() { MqlDateTime current, last; TimeToStruct(TimeCurrent(), current); TimeToStruct(m_lastResetTime, last); // Reset daily metrics if new day if(current.day != last.day || current.mon != last.mon || current.year != last.year) { m_currentDailyLoss = 0; m_lastResetTime = TimeCurrent(); CLogger::Log(LOG_INFO, "Daily risk metrics reset"); } } //+------------------------------------------------------------------+ //| Update Drawdown | //+------------------------------------------------------------------+ void CRiskManager::UpdateDrawdown() { double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(currentBalance > m_peakBalance) { m_peakBalance = currentBalance; } if(m_peakBalance > 0) { m_currentDrawdown = ((m_peakBalance - currentBalance) / m_peakBalance) * 100; } else { m_currentDrawdown = 0; } } //+------------------------------------------------------------------+ //| Calculate Position Risks | //+------------------------------------------------------------------+ void CRiskManager::CalculatePositionRisks() { ArrayResize(m_positions, 0); m_positionCount = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelectByTicket(ticket)) { PositionRisk risk; risk.ticket = ticket; risk.symbol = PositionGetString(POSITION_SYMBOL); risk.volume = PositionGetDouble(POSITION_VOLUME); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); double stopLoss = PositionGetDouble(POSITION_SL); // Calculate risk amount if(stopLoss > 0) { double riskPoints = MathAbs(openPrice - stopLoss); double tickValue = SymbolInfoDouble(risk.symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(risk.symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize > 0) { risk.riskAmount = (riskPoints / tickSize) * tickValue * risk.volume; } } risk.riskPercent = (AccountInfoDouble(ACCOUNT_BALANCE) > 0) ? (risk.riskAmount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0; risk.currentLoss = PositionGetDouble(POSITION_PROFIT); // Calculate margin used (MQL5 doesn't have POSITION_MARGIN directly) double marginInit = 0; double marginMaint = 0; if(OrderCalcMargin(ORDER_TYPE_BUY, risk.symbol, risk.volume, openPrice, marginInit)) { risk.marginUsed = marginInit; } else { risk.marginUsed = 0; } ArrayResize(m_positions, m_positionCount + 1); m_positions[m_positionCount] = risk; m_positionCount++; } } } //+------------------------------------------------------------------+ //| Evaluate Risk Action | //+------------------------------------------------------------------+ ENUM_RISK_ACTION CRiskManager::EvaluateRiskAction() { if(!m_isInitialized) return ACTION_NONE; UpdateRiskMetrics(); // Critical - close all if(m_currentMetrics.marginLevel < 50 && m_currentMetrics.marginLevel > 0) return ACTION_CLOSE_ALL; if(m_currentMetrics.currentDrawdown > m_maxDrawdown) return ACTION_CLOSE_ALL; // High risk - close partial if(m_currentMetrics.dailyLossPercent > m_maxDailyLoss * 0.8) return ACTION_CLOSE_PARTIAL; if(m_currentMetrics.totalRisk > m_maxRiskPerTrade * 0.9) return ACTION_REDUCE_SIZE; // Warning only if(m_currentMetrics.leverage > m_maxLeverage * 0.8) return ACTION_WARN; return ACTION_NONE; } //+------------------------------------------------------------------+ //| Execute Risk Action | //+------------------------------------------------------------------+ void CRiskManager::ExecuteRiskAction(ENUM_RISK_ACTION action) { switch(action) { case ACTION_WARN: Alert("Risk Warning: Approaching risk limits"); break; case ACTION_REDUCE_SIZE: CLogger::Log(LOG_WARNING, "Reducing position sizes due to high risk"); break; case ACTION_CLOSE_PARTIAL: CLogger::Log(LOG_WARNING, "Closing partial positions due to risk limits"); break; case ACTION_CLOSE_ALL: CLogger::Log(LOG_ERROR, "EMERGENCY: Closing all positions"); break; case ACTION_STOP_TRADING: CLogger::Log(LOG_ERROR, "Trading suspended due to risk limits"); break; default: break; } } //+------------------------------------------------------------------+ //| Get ATR Value | //+------------------------------------------------------------------+ double CRiskManager::GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period) { int atrHandle = iATR(symbol, timeframe, period); if(atrHandle == INVALID_HANDLE) return 0; double atr[]; ArraySetAsSeries(atr, true); if(CopyBuffer(atrHandle, 0, 0, 1, atr) <= 0) { IndicatorRelease(atrHandle); return 0; } IndicatorRelease(atrHandle); return atr[0]; } //+------------------------------------------------------------------+ //| Get Volatility | //+------------------------------------------------------------------+ double CRiskManager::GetVolatility(string symbol) { return GetATR(symbol, PERIOD_H1, 14); } //+------------------------------------------------------------------+ //| Check if High Volatility | //+------------------------------------------------------------------+ bool CRiskManager::IsHighVolatility(string symbol) { double currentVol = GetVolatility(symbol); double avgVol = GetATR(symbol, PERIOD_D1, 20); return (avgVol > 0 && currentVol > avgVol * 1.5); } //+------------------------------------------------------------------+ //| Calculate Correlation | //+------------------------------------------------------------------+ double CRiskManager::CalculateCorrelation(const double &prices1[], const double &prices2[]) { int n = MathMin(ArraySize(prices1), ArraySize(prices2)); if(n < 2) return 0; double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0; for(int i = 0; i < n; i++) { sumX += prices1[i]; sumY += prices2[i]; sumXY += prices1[i] * prices2[i]; sumX2 += prices1[i] * prices1[i]; sumY2 += prices2[i] * prices2[i]; } double numerator = n * sumXY - sumX * sumY; double denominator = MathSqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)); if(denominator == 0) return 0; return numerator / denominator; } //+------------------------------------------------------------------+ //| Check Risk Limits | //+------------------------------------------------------------------+ bool CRiskManager::CheckRiskLimits(double proposedRisk) { double totalRisk = m_currentMetrics.totalRisk + proposedRisk; return (totalRisk <= m_maxRiskPerTrade); } //+------------------------------------------------------------------+ //| Log Risk Event | //+------------------------------------------------------------------+ void CRiskManager::LogRiskEvent(ENUM_RISK_EVENT event, string details) { // Log the risk event directly using CUtilities static method string eventName = ""; switch(event) { case RISK_OVERLEVERAGED: eventName = "OVERLEVERAGED"; break; case RISK_CORRELATION_HIGH: eventName = "HIGH_CORRELATION"; break; case RISK_DRAWDOWN_LIMIT: eventName = "DRAWDOWN_LIMIT"; break; case RISK_DAILY_LOSS_LIMIT: eventName = "DAILY_LOSS_LIMIT"; break; case RISK_MARGIN_CALL: eventName = "MARGIN_CALL"; break; case RISK_VOLATILITY_SPIKE: eventName = "VOLATILITY_SPIKE"; break; case RISK_LIQUIDITY_ISSUE: eventName = "LIQUIDITY_ISSUE"; break; default: eventName = "UNKNOWN"; break; } // Log the event CLogger::Log(LOG_WARNING, "Risk Event: " + eventName); // Log details if provided if(details != "") { CLogger::Log(LOG_WARNING, "Risk Event Details: " + details); } // Also call the static CUtilities method if you want to use it // Note: CUtilities::LogRiskEvent is a static method that takes only the event parameter CUtilities::LogRiskEvent(event); } #endif // RISK_MANAGER_MQH