//+------------------------------------------------------------------+ //| RiskManager.mqh | //| Copyright 2025, EscapeEA | //| https://www.escapeea.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, EscapeEA" #property link "https://www.escapeea.com" #property version "2.00" #property strict #include #include #include #include #include // Forward declarations to avoid circular dependencies class CSecurityEnhancements; class CTradeRecord; // Risk limits and constraints #define MAX_RISK_PER_TRADE 5.0 // Maximum allowed risk per trade (%) #define MAX_DAILY_RISK 10.0 // Maximum daily risk limit (%) #define MAX_POSITIONS 20 // Maximum open positions #define MAX_ORDERS_PER_DAY 100 // Maximum orders per day #define VOLATILITY_LOOKBACK 14 // Period for volatility calculation #define MIN_ACCOUNT_BALANCE 100.0 // Minimum account balance to trade //+------------------------------------------------------------------+ //| Risk Profile Configuration | //+------------------------------------------------------------------+ input group "=== Risk Management Settings ===" input double InpRiskPerTrade = 1.0; // Risk per trade (% of balance, 0.1-5.0) input double InpDailyLossLimit = 5.0; // Max daily loss (% of balance, 1.0-10.0) input int InpMaxOpenTrades = 5; // Max open trades (1-20) input int InpMaxOrdersPerDay = 20; // Max orders per day (1-100) input bool InpUseVolatilityAdjustment = true; // Adjust position size by volatility input int InpAtrPeriod = 14; // ATR period for volatility (5-50) // Max spread is now defined in SecurityEnhancements.mqh input bool InpEnableCircuitBreaker = true; // Enable circuit breaker for extreme conditions input double InpCircuitBreakerThreshold = 3.0; // % drawdown to trigger circuit breaker // Anomaly detection is now handled by SecurityEnhancements input int InpMaxLeverage = 30; // Maximum allowed leverage (1:1 to 1:500) //+------------------------------------------------------------------+ //| Trade Risk Record | //+------------------------------------------------------------------+ class CTradeRiskRecord : public CObject { public: ulong ticket; // Trade ticket string symbol; // Symbol double volume; // Position volume double entry; // Entry price double sl; // Stop loss double tp; // Take profit double risk; // Risk amount datetime openTime; // Open time CTradeRiskRecord() {} ~CTradeRiskRecord() {} void Set(ulong t, string s, double v, double e, double sll, double tpp, double r, datetime ot) { ticket = t; symbol = s; volume = v; entry = e; sl = sll; tp = tpp; risk = r; openTime = ot; } bool AddTrade(ulong tradeTicket, const string tradeSymbol, double tradeVolume, double tradeEntry, double tradeSL, double tradeTP, double tradeRisk, datetime tradeOpenTime) { // Check if trade is valid if(!IsMarketOpen(tradeSymbol) || tradeVolume <= 0 || tradeEntry <= 0 || tradeRisk < 0) { return false; } // Create a new trade record CTradeRiskRecord *newRecord = new CTradeRiskRecord(); newRecord.Set(tradeTicket, tradeSymbol, tradeVolume, tradeEntry, tradeSL, tradeTP, tradeRisk, tradeOpenTime); // Add to open trades if(m_openTrades == NULL) { m_openTrades = new CArrayObj(); } m_openTrades.Add(newRecord); return true; } }; //+------------------------------------------------------------------+ //| Risk Manager Class | //+------------------------------------------------------------------+ class CRiskManager : public CObject { private: // Risk parameters double m_riskPerTrade; // Risk per trade as percentage of balance double m_dailyLossLimit; // Maximum daily loss percentage int m_maxOpenTrades; // Maximum number of open trades int m_maxOrdersPerDay; // Maximum orders per day bool m_useVolatilityAdjustment; // Use volatility-based position sizing double m_maxDailyLoss; // Maximum daily loss amount bool m_circuitBreakerActive; // Circuit breaker state int m_atrPeriod; // Period for ATR calculation int m_maxSpread; // Maximum allowed spread in points double m_circuitBreakerLevel; // Drawdown level for circuit breaker bool m_anomalyDetection; // Enable anomaly detection int m_maxLeverage; // Maximum allowed leverage // State tracking double m_dailyPnL; // Today's PnL int m_todayOrders; // Number of orders placed today int m_dailyOrderCount; // Daily order counter double m_dailyPL; // Daily profit/loss datetime m_lastTradeDay; // Last trading day string m_lastError; // Last error message double m_equityHigh; // Highest equity level double m_maxDrawdown; // Maximum drawdown percentage bool m_isTradingAllowed; // Flag to enable/disable trading bool m_logToFile; // Enable logging to file int m_consecutiveErrors; // Count of consecutive errors bool m_useCircuitBreaker; // Enable circuit breaker int m_maxConsecutiveErrors; // Max allowed consecutive errors datetime m_circuitBreakerTime; // When circuit breaker was activated int m_circuitBreakTime; // Duration of circuit break in seconds CTrade* m_trade; // Trade execution object // News filtering bool m_useNewsFilter; // Enable news filtering datetime m_newsStartTime; // Start time of news event datetime m_newsEndTime; // End time of news event CArrayString* m_affectedSymbols; // Symbols affected by news // Collections CArrayObj m_openTrades; // Currently open trades CArrayObj m_closedTrades; // Recently closed trades CArrayObj m_tradeHistory; // Historical record of all trades // Volatility data int m_atrHandle; // ATR indicator handle double m_atrBuffer[]; // ATR buffer // Security - Using pointer to avoid circular dependency CSecurityEnhancements *m_security; // Security enhancements // Private methods // Update daily trading metrics void UpdateDailyMetrics() { MqlDateTime time; TimeCurrent(time); // Check if it's a new day if(m_lastTradeDay != time.day) { // Reset daily counters if it's a new day if(m_lastTradeDay != 0) { // Don't reset on the first run m_dailyPnL = 0.0; m_todayOrders = 0; m_dailyOrderCount = 0; // Log end of day report string report = "=== End of Day Report ===\n"; report += "Date: " + TimeToString(TimeCurrent(), TIME_DATE) + "\n"; report += "Daily P/L: " + DoubleToString(m_dailyPnL, 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n"; report += "Orders Today: " + IntegerToString(m_todayOrders) + "\n"; report += "Open Positions: " + IntegerToString(PositionsTotal()) + "\n"; report += "Account Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n"; report += "Account Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n"; report += "======================\n"; Print(report); // Save to file if needed if(m_logToFile) { int file_handle = FileOpen("DailyReport_" + TimeToString(TimeCurrent(), TIME_DATE) + ".txt", FILE_WRITE|FILE_TXT); if(file_handle != INVALID_HANDLE) { FileWriteString(file_handle, report); FileClose(file_handle); } } } // Update last trade day m_lastTradeDay = time.day; } // Update current day's P/L double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); m_dailyPnL = currentEquity - currentBalance; } // Log trade metrics for analysis void LogTradeMetrics(const ulong ticket, const string symbol, const ENUM_ORDER_TYPE type, const double volume, const double openPrice, const double sl, const double tp, const string comment = "") { // Create a trade record CTradeRecord *record = new CTradeRecord(); record.Set(ticket, symbol, type, volume, openPrice, sl, tp, 0.0, comment); // Add to trade history m_tradeHistory.Add(record); // Prepare log message string logMsg = StringFormat("[%s] %s %s %.2f lots @ %.5f", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), EnumToString(type), symbol, volume, openPrice); if(sl > 0) logMsg += StringFormat(" SL:%.5f", sl); if(tp > 0) logMsg += StringFormat(" TP:%.5f", tp); if(comment != "") logMsg += " " + comment; // Print to log Print(logMsg); // Log to file if enabled if(m_logToFile) { int file_handle = FileOpen("TradeLog_" + symbol + "_" + TimeToString(TimeCurrent(), TIME_DATE) + ".csv", FILE_WRITE|FILE_CSV|FILE_READ, ","); if(file_handle != INVALID_HANDLE) { // Write header if file is empty if(FileSize(file_handle) == 0) { FileWrite(file_handle, "DateTime","Type","Symbol","Volume","Price","SL","TP","Comment"); } // Write trade data FileWrite(file_handle, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), EnumToString(type), symbol, volume, openPrice, sl, tp, comment); FileClose(file_handle); } } // Update metrics m_todayOrders++; m_dailyOrderCount++; UpdateDailyMetrics(); } // Check if there's sufficient margin for the trade bool CheckMargin(const string symbol, const double volume, const double price, const ENUM_ORDER_TYPE orderType) { // Get order type direction bool isBuy = (orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_BUY_STOP); // Calculate required margin double margin = 0.0; if(!OrderCalcMargin((ENUM_ORDER_TYPE)orderType, symbol, volume, price, margin)) { m_lastError = "Failed to calculate margin for " + symbol; return false; } // Get free margin and account balance double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Check if we have enough margin if(margin > freeMargin) { m_lastError = StringFormat("Insufficient margin. Required: %.2f, Available: %.2f", margin, freeMargin); return false; } // Check margin level (if position would be opened) double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); if(marginLevel > 0 && marginLevel < 100.0) { // 100% margin level is critical m_lastError = StringFormat("Margin level too low: %.2f%%", marginLevel); return false; } return true; } // Check if the current spread is acceptable for trading bool CheckSpread(const string symbol) { // Get current spread long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD); // Get average spread for the current timeframe double avgSpread = GetAverageSpread(symbol, PERIOD_CURRENT, 20); // Check if spread is within acceptable limits (2x average or 20 points, whichever is higher) double maxSpread = MathMax(avgSpread * 2.0, 20.0); if(spread > maxSpread) { m_lastError = StringFormat("Spread too high: %d points (max: %.1f)", spread, maxSpread); return false; } return true; } // Check if the market is open for trading bool CheckMarketHours(const string symbol) { // Check if the symbol is selected and visible in Market Watch if(!SymbolSelect(symbol, true)) { m_lastError = "Symbol " + symbol + " is not available"; return false; } // Check if the symbol is enabled for trading if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { m_lastError = "Trading is disabled for " + symbol; return false; } // Check if we're outside of market hours (if defined) MqlDateTime time; TimeCurrent(time); int dayOfWeek = time.day_of_week; int hour = time.hour; // Example: Don't trade on weekends if(dayOfWeek == 0 || dayOfWeek == 6) { // Sunday or Saturday m_lastError = "Market is closed on weekends"; return false; } // Example: Only trade during specific hours (e.g., London + NY session) // Adjust these values based on your trading strategy if(hour < 8 || hour >= 22) { // 8 AM to 10 PM server time m_lastError = "Outside of trading hours (8:00-22:00 server time)"; return false; } // Check for market holidays (you would implement this based on your needs) if(IsMarketHoliday(time)) { m_lastError = "Market holiday"; return false; } return true; } // Helper function to get average spread over a period double GetAverageSpread(const string symbol, const ENUM_TIMEFRAMES timeframe, const int periods) { double totalSpread = 0; int count = 0; for(int i = 0; i < periods; i++) { long spread = (long)SymbolInfoInteger(symbol, SYMBOL_SPREAD); if(spread > 0) { totalSpread += spread; count++; } // Move to previous bar (this is simplified, in real code you'd use CopySpread) Sleep(10); // Small delay to avoid flooding } return (count > 0) ? (totalSpread / count) : 0; } // Check if today is a market holiday (example implementation) bool IsMarketHoliday(const MqlDateTime &time) { // This is a simplified example - you'd want to implement a proper holiday calendar // based on the market you're trading // Example: New Year's Day if(time.mon == 1 && time.day == 1) return true; // Example: Christmas if(time.mon == 12 && time.day == 25) return true; // Add other major holidays as needed return false; } // Execute a trading order with risk management bool ExecuteOrder(const string symbol, const ENUM_ORDER_TYPE orderType, const double volume, const double price, const double sl, const double tp, const string comment = "") { // Validate order parameters if(!ValidateOrder(symbol, orderType, volume, price, sl, tp)) { return false; } // Check if trading is allowed if(!IsTradingAllowed()) { m_lastError = "Trading is not allowed: " + m_lastError; return false; } // Execute the order bool result = false; m_trade.SetExpertMagicNumber(m_magicNumber); switch(orderType) { case ORDER_TYPE_BUY: result = m_trade.Buy(volume, symbol, price, sl, tp, comment); break; case ORDER_TYPE_SELL: result = m_trade.Sell(volume, symbol, price, sl, tp, comment); break; case ORDER_TYPE_BUY_LIMIT: result = m_trade.BuyLimit(volume, price, symbol, sl, tp, 0, 0, comment); break; case ORDER_TYPE_SELL_LIMIT: result = m_trade.SellLimit(volume, price, symbol, sl, tp, 0, 0, comment); break; case ORDER_TYPE_BUY_STOP: result = m_trade.BuyStop(volume, price, symbol, sl, tp, ORDER_TIME_GTC, 0, comment); break; case ORDER_TYPE_SELL_STOP: result = m_trade.SellStop(volume, price, symbol, sl, tp, ORDER_TIME_GTC, 0, comment); break; default: m_lastError = "Unsupported order type: " + EnumToString(orderType); return false; } // Handle the result if(!result) { HandleError(m_trade.ResultRetcode(), "Order execution failed"); return false; } // Update metrics and reset error counter on success UpdateDailyMetrics(); ResetErrorCounter(); return true; } // Handle trading errors and update error counter void HandleError(const int errorCode, const string context = "") { // Log the error string errorMsg = "Error " + IntegerToString(errorCode) + ": " + ErrorDescription(errorCode); if(context != "") { errorMsg = context + " - " + errorMsg; } Print(errorMsg); // Update error counter and check circuit breaker m_consecutiveErrors++; if(m_useCircuitBreaker && m_consecutiveErrors >= m_maxConsecutiveErrors) { m_circuitBreakerActive = true; m_circuitBreakerTime = TimeCurrent(); Print("Circuit breaker activated! Trading suspended until ", TimeToString(m_circuitBreakerTime + m_circuitBreakTime, TIME_MINUTES|TIME_SECONDS)); } // Update last error message m_lastError = errorMsg; } // Reset the error counter and circuit breaker if needed void ResetErrorCounter() { if(m_consecutiveErrors > 0) { m_consecutiveErrors = 0; if(m_circuitBreakerActive) { m_circuitBreakerActive = false; Print("Circuit breaker reset. Trading resumed."); } } } // Get current market volatility using ATR double GetCurrentVolatility(const string symbol, const ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT) { // If ATR handle is not valid, try to create it if(m_atrHandle == INVALID_HANDLE) { m_atrHandle = iATR(symbol, timeframe, m_atrPeriod); if(m_atrHandle == INVALID_HANDLE) { m_lastError = "Failed to create ATR indicator for " + symbol; return 0.0; } } // Copy ATR values double atrValues[1]; if(CopyBuffer(m_atrHandle, 0, 0, 1, atrValues) <= 0) { m_lastError = "Failed to get ATR values for " + symbol; return 0.0; } // Get the current ATR value double currentATR = atrValues[0]; // Normalize ATR to percentage of current price for consistent comparison double currentPrice = SymbolInfoDouble(symbol, SYMBOL_ASK); if(currentPrice <= 0) { currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); } if(currentPrice > 0) { return (currentATR / currentPrice) * 100.0; // Return as percentage } return currentATR; // Fallback to raw ATR value if price is 0 } // Calculate position size based on risk parameters double CalculatePositionSize(const string symbol, const double entryPrice, const double stopLoss, const double riskPercent) { if(riskPercent <= 0 || entryPrice <= 0 || stopLoss <= 0) { m_lastError = "Invalid parameters for position sizing"; return 0.0; } // Get account currency and symbol information string accountCurrency = AccountInfoString(ACCOUNT_CURRENCY); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // Calculate risk amount in account currency double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = accountBalance * (riskPercent / 100.0); // Calculate position size based on stop loss double stopLossDistance = MathAbs(entryPrice - stopLoss) / point; double tickValueAdjusted = tickValue * (point / tickSize); double positionSize = riskAmount / (stopLossDistance * tickValueAdjusted); // Adjust position size to lot step positionSize = MathFloor(positionSize / lotStep) * lotStep; // Ensure position size is within symbol limits positionSize = MathMin(MathMax(positionSize, minLot), maxLot); return positionSize; } bool CheckDailyLimits() { // Update daily metrics first UpdateDailyMetrics(); // Check if we've exceeded daily order limit if(m_todayOrders >= m_maxOrdersPerDay) { m_lastError = StringFormat("Daily order limit reached (%d/%d)", m_todayOrders, m_maxOrdersPerDay); return false; } // Check if we've exceeded maximum open trades if(GetOpenPositions() >= m_maxOpenTrades) { m_lastError = "Maximum open trades limit reached"; return false; } // Check daily loss limit if(m_dailyPnL < -m_dailyLossLimit) { m_lastError = "Daily loss limit reached"; return false; } return true; } bool CheckPositionSize(const string symbol, const double volume, const double price) { if(volume <= 0 || price <= 0) { m_lastError = "Invalid position parameters"; return false; } double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // Check against symbol's lot size constraints if(volume < minLot || volume > maxLot) { m_lastError = "Volume out of allowed range"; return false; } // Check if volume is a multiple of lot step if(MathMod(volume, lotStep) != 0) { m_lastError = "Volume must be a multiple of " + DoubleToString(lotStep, 2); return false; } return true; } bool CheckMarketSpread(const string symbol) { if(!SymbolSelect(symbol, true)) { m_lastError = "Symbol " + symbol + " is not selected"; return false; } long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD); if(spread > (long)m_maxSpread) { m_lastError = "Spread too wide: " + IntegerToString((int)spread) + " points"; return false; } return true; } bool CheckVolatility(const string symbol) { if(!m_useVolatilityAdjustment) { return true; // Volatility check disabled } double volatility = GetCurrentVolatility(symbol, PERIOD_CURRENT, m_atrPeriod); double maxVolatility = GetMaxVolatility(symbol); if(volatility > maxVolatility) { m_lastError = symbol + " volatility too high: " + DoubleToString(volatility, 2) + " (max: " + DoubleToString(maxVolatility, 2) + ")"; return false; } return true; } double CalculateVolatilityFactor(const string symbol) { if(!m_useVolatilityAdjustment) { return 1.0; // No adjustment } double volatility = GetCurrentVolatility(symbol, PERIOD_CURRENT, m_atrPeriod); if(volatility <= 0) { return 1.0; // Fallback to no adjustment } // Normalize ATR to a factor between 0.5 and 1.5 double avgAtr = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) * 10; // Example average ATR double factor = avgAtr / volatility; return MathMin(1.5, MathMax(0.5, factor)); } void UpdateTradeHistory(const CTradeRiskRecord *record) { if(record == NULL) return; // Add to open trades CTradeRiskRecord *newRecord = new CTradeRiskRecord(); *newRecord = *record; m_openTrades.Add(newRecord); // Update daily stats m_todayOrders++; // Update equity high water mark double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(equity > m_equityHigh) { m_equityHigh = equity; } // Update drawdown UpdateDrawdown(); } double GetRecentProfitFactor(const string symbol, const int periods) { // Implementation depends on your trading history tracking // This is a simplified version double profit = 0; double loss = 0; // In a real implementation, you would iterate through recent trades // and calculate profit factor based on wins/losses if(loss == 0) return 10.0; // Avoid division by zero return profit / loss; } bool CheckMarginRequirements(const string symbol, const double volume) { double margin = 0; double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, volume, 0, margin)) { m_lastError = "Failed to calculate margin requirements"; return false; } // Check if we have enough margin if(margin > freeMargin) { m_lastError = "Insufficient margin"; return false; } // Check leverage double leverage = AccountInfoInteger(ACCOUNT_LEVERAGE); if(leverage > m_maxLeverage) { m_lastError = "Account leverage too high"; return false; } return true; } bool CheckCorrelatedPositions(const string symbol, const ENUM_ORDER_TYPE type) { // In a real implementation, you would check for correlated positions // This is a simplified version that just checks for opposite positions on the same symbol for(int i = 0; i < m_openTrades.Total(); i++) { CTradeRiskRecord *record = m_openTrades.At(i); if(record != NULL && record.symbol == symbol) { // Check for opposite position type if((type == ORDER_TYPE_BUY && record.type == ORDER_TYPE_SELL) || (type == ORDER_TYPE_SELL && record.type == ORDER_TYPE_BUY)) { m_lastError = "Opposite position exists"; return false; } } } return true; } void UpdateDrawdown() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance > 0) { double drawdown = (balance - equity) / balance * 100; if(drawdown > m_maxDrawdown) { m_maxDrawdown = drawdown; } } } void CheckCircuitBreaker() { if(!InpEnableCircuitBreaker) return; double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance > 0) { double drawdown = (balance - equity) / balance * 100; if(drawdown >= m_circuitBreakerLevel) { m_circuitBreakerActive = true; LogAnomaly("Circuit breaker triggered: Drawdown " + DoubleToString(drawdown, 2) + "%"); } } } void LogAnomaly(const string message) { // In a real implementation, you would log to a file or send an alert Print("[ANOMALY] ", message); } public: // Constructor/Destructor CRiskManager(); ~CRiskManager(); // Initialization bool Initialize(); void Deinitialize(); // Configuration with validation void SetRiskPerTrade(double risk) { m_riskPerTrade = MathMin(MathMax(risk, 0.1), MAX_RISK_PER_TRADE); } void SetDailyLossLimit(double limit) { m_dailyLossLimit = MathMin(MathMax(limit, 0.5), MAX_DAILY_RISK); } void SetMaxOpenTrades(int maxTrades) { m_maxOpenTrades = MathMax(1, MathMin(maxTrades, MAX_POSITIONS)); } void SetMaxOrdersPerDay(int maxOrders) { m_maxOrdersPerDay = MathMax(1, MathMin(maxOrders, MAX_ORDERS_PER_DAY)); } void UseVolatilityAdjustment(bool enable) { m_useVolatilityAdjustment = enable; } void SetAtrPeriod(int period) { m_atrPeriod = MathMax(5, MathMin(period, 50)); } void SetMaxSpread(int maxSpread) { m_maxSpread = MathMax(1, MathMin(maxSpread, 100)); } void SetMaxLeverage(int leverage) { m_maxLeverage = MathMax(1, MathMin(leverage, 500)); } // Risk validation with enhanced security bool ValidateTrade(const string symbol, const ENUM_ORDER_TYPE type, const double volume, const double price, const double sl, const double tp) { // Reset last error m_lastError = ""; // Basic parameter validation if(volume <= 0 || price <= 0) { m_lastError = "Invalid trade parameters"; return false; } // Check circuit breaker first if(m_circuitBreakerActive) { m_lastError = "Trading suspended by circuit breaker"; return false; } // Check daily limits if(!CheckDailyLimits()) { return false; } // Check position size if(!CheckPositionSize(symbol, volume, price)) { return false; } // Check market spread if(!CheckMarketSpread(symbol)) { return false; } // Check volatility if(!CheckVolatility(symbol)) { return false; } // Check margin requirements if(!CheckMarginRequirements(symbol, volume)) { return false; } // Check for correlated positions if(!CheckCorrelatedPositions(symbol, type)) { return false; } // Additional security checks if security manager is available if(m_security != NULL && !m_security.ValidateTrade(symbol, type, volume, price, sl, tp)) { m_lastError = m_security.GetLastError(); return false; } return true; } // Position sizing with risk controls double CalculatePositionSize(const string symbol, const double price, const int slPoints) { if(price <= 0 || slPoints <= 0) { m_lastError = "Invalid parameters for position sizing"; return 0.0; } // Get account balance and calculate risk amount double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = balance * (m_riskPerTrade / 100.0); // Calculate tick value and tick size double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(tickSize <= 0 || tickValue <= 0 || point <= 0) { m_lastError = "Failed to get symbol information"; return 0.0; } // Calculate position size double riskMoney = riskAmount; double lossInQuoteCurrency = slPoints * point * tickValue / tickSize; if(lossInQuoteCurrency <= 0) { m_lastError = "Invalid stop loss for position sizing"; return 0.0; } double volume = riskMoney / lossInQuoteCurrency; // Apply volatility adjustment if enabled if(m_useVolatilityAdjustment) { double volatilityFactor = CalculateVolatilityFactor(symbol); volume *= volatilityFactor; } // Get symbol's volume constraints double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // Ensure volume is within allowed range and step volume = MathMax(minLot, MathMin(volume, maxLot)); volume = MathFloor(volume / lotStep) * lotStep; return volume; } // Check if market is open for a symbol bool IsMarketOpen(const string symbol) { // Check if the symbol is selected if(!SymbolSelect(symbol, true)) { m_lastError = "Failed to select symbol " + symbol; return false; } // Check if the market is open now datetime current = TimeCurrent(); if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { // Check trading session times MqlDateTime time; TimeToStruct(current, time); // Skip weekend check if symbol trades on weekends if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_CALENDAR)) { if(time.day_of_week == 0 || time.day_of_week == 6) { // Sunday or Saturday m_lastError = "Market is closed (weekend)"; return false; } } // Check if current time is within trading hours // This is a simplified check - adjust according to your broker's trading hours int dayOfWeek = time.day_of_week; if(dayOfWeek >= 1 && dayOfWeek <= 5) { // Monday to Friday int hour = time.hour; // Example: Check if current time is within 0:00-23:59 (24/5 market) if(hour >= 0 && hour < 24) { return true; } } } m_lastError = "Market is currently closed"; return false; } // Market conditions with anomaly detection bool CheckMarketConditions(const string symbol) { // Basic symbol check if(!SymbolSelect(symbol, true)) { m_lastError = "Symbol " + symbol + " is not available"; return false; } // Check if market is open if(!IsMarketOpen(symbol)) { m_lastError = symbol + " market is closed"; return false; } // Check spread if(!CheckMarketSpread(symbol)) { return false; } // Check volatility if enabled if(m_useVolatilityAdjustment && !CheckVolatility(symbol)) { return false; } return true; } // Getters with validation string GetLastError() const { return m_lastError; } double GetDailyPnL() const { return m_dailyPnL; } int GetTodayOrders() const { return m_todayOrders; } double GetMaxDrawdown() const { return m_maxDrawdown; } bool IsCircuitBreakerActive() const { return m_circuitBreakerActive; } // State management void ResetDailyStats(); // Advanced risk metrics double CalculateDrawdown(const string symbol = NULL); double CalculateRiskRewardRatio(const double entry, const double sl, const double tp); double GetVolatility(const string symbol); double GetRiskOfRuin() const; // Position management int GetOpenPositions(const string symbol = "") { int count = 0; if(symbol == "") { // Count all open positions for(int i = 0; i < m_openTrades.Total(); i++) { CTradeRiskRecord *record = m_openTrades.At(i); if(record != NULL && !record.isClosed) { count++; } } } else { // Count positions for specific symbol for(int i = 0; i < m_openTrades.Total(); i++) { CTradeRiskRecord *record = m_openTrades.At(i); if(record != NULL && record.symbol == symbol && !record.isClosed) { count++; } } } return count; } double GetTotalRisk() { double totalRisk = 0; for(int i = 0; i < m_openTrades.Total(); i++) { CTradeRiskRecord *record = m_openTrades.At(i); if(record != NULL && !record.isClosed) { totalRisk += record.risk; } } return totalRisk; } double GetPortfolioRisk() const { double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance <= 0) return 0; double equity = AccountInfoDouble(ACCOUNT_EQUITY); return ((balance - equity) / balance) * 100.0; } // Risk controls bool IsTradingAllowed() { // Check circuit breaker first if(m_circuitBreakerActive) { m_lastError = "Trading suspended by circuit breaker"; return false; } // Check if market is open for trading if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { m_lastError = "Trading is not allowed by the terminal"; return false; } // Check if expert is allowed to trade if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { m_lastError = "Trading is not allowed for this expert"; return false; } // Check if account allows trading if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) { m_lastError = "Trading is not allowed for this account"; return false; } // Check if account is in profit/loss limits double profit = AccountInfoDouble(ACCOUNT_PROFIT); if(profit < -m_dailyLossLimit) { m_lastError = "Daily loss limit reached"; return false; } return true; } void SuspendTrading(const string reason) { m_circuitBreakerActive = true; LogAnomaly("Trading suspended: " + reason); } void ResumeTrading() { if(m_circuitBreakerActive) { m_circuitBreakerActive = false; LogAnomaly("Trading resumed"); } } // Monitoring void MonitorPositions() { // Update all open positions for(int i = m_openTrades.Total() - 1; i >= 0; i--) { CTradeRiskRecord *record = m_openTrades.At(i); if(record == NULL) continue; // Check if position is still open if(!PositionSelectByTicket(record.ticket)) { // Position was closed, move to closed trades record.isClosed = true; m_closedTrades.Add(m_openTrades.Detach(i)); // Update daily PnL double profit = record.volume * (record.price - record.sl); // Simplified PnL calculation m_dailyPnL += profit; // Update drawdown UpdateDrawdown(); } } // Check circuit breaker CheckCircuitBreaker(); } void CheckMarginLevels() { double margin = AccountInfoDouble(ACCOUNT_MARGIN); double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); // Log warning if margin level is low if(marginLevel > 0 && marginLevel < 100.0) { // 100% margin level is critical LogAnomaly("Low margin level: " + DoubleToString(marginLevel, 2) + "%"); // Trigger circuit breaker if margin level is critically low if(marginLevel < 50.0) { // 50% margin level is emergency SuspendTrading("Critically low margin level: " + DoubleToString(marginLevel, 2) + "%"); } } } void CheckNewsImpact() { // This would integrate with a news feed or economic calendar // For now, just a placeholder if(m_security != NULL) { string symbol = Symbol(); if(m_security.IsNewsEvent(symbol)) { LogAnomaly("High impact news detected for " + symbol); } } } // Recovery void RecoverFromDrawdown() { // Simple recovery strategy: reduce position sizes after drawdown double drawdown = GetPortfolioRisk(); if(drawdown > 5.0) { // If drawdown > 5% // Reduce risk per trade by 50% m_riskPerTrade *= 0.5; LogAnomaly("Reducing risk per trade to " + DoubleToString(m_riskPerTrade, 2) + "% due to drawdown"); } } // Logging and reporting void LogRiskMetrics() { string logEntry = ""; logEntry += "=== Risk Metrics ===\n"; logEntry += "Account Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n"; logEntry += "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n"; logEntry += "Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + "\n"; logEntry += "Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "\n"; logEntry += "Margin Level: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "%\n"; logEntry += "Open Trades: " + IntegerToString(GetOpenPositions()) + "\n"; logEntry += "Daily PnL: " + DoubleToString(m_dailyPnL, 2) + "\n"; logEntry += "Max Drawdown: " + DoubleToString(m_maxDrawdown, 2) + "%\n"; logEntry += "Circuit Breaker: " + string(m_circuitBreakerActive ? "ACTIVE" : "Inactive") + "\n"; Print(logEntry); } string GenerateRiskReport() const { string report = ""; report += "=== Risk Management Report ===\n\n"; report += "Account Information:\n"; report += "- Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n"; report += "- Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n"; report += "- Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + "\n"; report += "- Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "\n"; report += "- Margin Level: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "%\n\n"; report += "Risk Parameters:\n"; report += "- Risk per Trade: " + DoubleToString(m_riskPerTrade, 2) + "%\n"; report += "- Daily Loss Limit: " + DoubleToString(m_dailyLossLimit, 2) + "%\n"; report += "- Max Open Trades: " + IntegerToString(m_maxOpenTrades) + "\n"; report += "- Max Orders per Day: " + IntegerToString(m_maxOrdersPerDay) + "\n"; report += "- Current Daily Orders: " + IntegerToString(m_todayOrders) + "\n\n"; report += "Portfolio Metrics:\n"; report += "- Open Positions: " + IntegerToString(GetOpenPositions()) + "\n"; report += "- Daily PnL: " + DoubleToString(m_dailyPnL, 2) + "\n"; report += "- Max Drawdown: " + DoubleToString(m_maxDrawdown, 2) + "%\n"; report += "- Circuit Breaker: " + string(m_circuitBreakerActive ? "ACTIVE" : "Inactive") + "\n"; return report; } // Event handlers void OnTick() { // Update market data if(m_atrHandle != INVALID_HANDLE) { if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0) { m_lastError = "Failed to update ATR data"; return; } } // Monitor open positions MonitorPositions(); // Check margin levels periodically static datetime lastCheck = 0; if(TimeCurrent() - lastCheck > 60) { // Check every minute CheckMarginLevels(); CheckNewsImpact(); lastCheck = TimeCurrent(); } // Log metrics every 15 minutes static datetime lastLog = 0; if(TimeCurrent() - lastLog > 900) { // 15 minutes = 900 seconds LogRiskMetrics(); lastLog = TimeCurrent(); } } void OnTrade() { // Update trade history and statistics UpdateDrawdown(); // Check if we need to recover from drawdown if(GetPortfolioRisk() > 5.0) { // If drawdown > 5% RecoverFromDrawdown(); } // Log the trade LogRiskMetrics(); } void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { // Handle different transaction types switch(trans.type) { case TRADE_TRANSACTION_DEAL_ADD: { // A new deal was added if(HistoryDealSelect(trans.deal)) { ulong ticket = HistoryDealGetInteger(trans.deal, DEAL_TICKET); string symbol = HistoryDealGetString(trans.deal, DEAL_SYMBOL); double volume = HistoryDealGetDouble(trans.deal, DEAL_VOLUME); double price = HistoryDealGetDouble(trans.deal, DEAL_PRICE); double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT); // Update daily PnL m_dailyPnL += profit; // Log the deal PrintFormat("Deal #%d: %s %s %.2f lots @ %s (Profit: %.2f)", ticket, symbol, EnumToString((ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE)), volume, DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), profit); // Update drawdown UpdateDrawdown(); } break; } case TRADE_TRANSACTION_ORDER_ADD: { // A new order was added if(HistoryOrderSelect(trans.order)) { ulong ticket = HistoryOrderGetInteger(trans.order, ORDER_TICKET); string symbol = HistoryOrderGetString(trans.order, ORDER_SYMBOL); double volume = HistoryOrderGetDouble(trans.order, ORDER_VOLUME_CURRENT); double price = HistoryOrderGetDouble(trans.order, ORDER_PRICE_OPEN); // Log the order PrintFormat("Order #%d: %s %s %.2f lots @ %s", ticket, symbol, EnumToString((ENUM_ORDER_TYPE)HistoryOrderGetInteger(trans.order, ORDER_TYPE)), volume, DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS))); } break; } case TRADE_TRANSACTION_HISTORY_ADD: { // New history record added break; } default: break; } } // Stress testing void RunStressTest(int iterations = 1000) { Print("Starting stress test..."); // Simulate various market conditions SimulateMarketCrash(); SimulateSlippage(); SimulateNetworkLatency(); Print("Stress test completed"); } void SimulateMarketCrash() { Print("Simulating market crash..."); // Save current state bool wasActive = m_circuitBreakerActive; // Simulate a 10% drawdown double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double newEquity = currentEquity * 0.9; // 10% drawdown // Force update of equity (simulation only) // In a real test, you would mock the account info PrintFormat("Simulating drawdown: Equity from %.2f to %.2f (%.2f%%)", currentEquity, newEquity, ((currentEquity - newEquity) / currentEquity) * 100.0); // Check if circuit breaker would trigger double drawdown = ((AccountInfoDouble(ACCOUNT_BALANCE) - newEquity) / AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0; if(drawdown >= m_circuitBreakerLevel) { Print("Circuit breaker would trigger at ", DoubleToString(drawdown, 2), "% drawdown"); } else { Print("Circuit breaker would NOT trigger at ", DoubleToString(drawdown, 2), "% drawdown"); } // Restore state m_circuitBreakerActive = wasActive; } void SimulateSlippage() { Print("Simulating slippage..."); // Simulate a trade with slippage string symbol = Symbol(); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT); // Simulate 2x spread slippage double slippage = spread * 2; PrintFormat("Simulated slippage: %.5f pips (%.5f %s)", slippage / SymbolInfoDouble(symbol, SYMBOL_POINT), slippage, SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT)); } void SimulateNetworkLatency() { Print("Simulating network latency..."); // Simulate network latency by measuring order execution time ulong startTime = GetTickCount64(); // In a real test, you would measure actual order execution time // This is just a simulation Sleep(100); // Simulate 100ms network latency ulong endTime = GetTickCount64(); ulong latency = endTime - startTime; Print("Simulated network latency: ", latency, "ms"); if(latency > 500) { // 500ms threshold LogAnomaly("High network latency detected: " + IntegerToString(latency) + "ms"); } } // Serialization virtual bool Save(const int handle) { // Serialize risk manager state if(!FileWrite(handle, m_riskPerTrade)) return false; if(!FileWrite(handle, m_dailyLossLimit)) return false; if(!FileWrite(handle, m_maxOpenTrades)) return false; if(!FileWrite(handle, m_maxOrdersPerDay)) return false; if(!FileWrite(handle, m_useVolatilityAdjustment)) return false; if(!FileWrite(handle, m_atrPeriod)) return false; if(!FileWrite(handle, m_maxSpread)) return false; if(!FileWrite(handle, m_circuitBreakerActive)) return false; if(!FileWrite(handle, m_circuitBreakerLevel)) return false; if(!FileWrite(handle, m_anomalyDetection)) return false; if(!FileWrite(handle, m_maxLeverage)) return false; if(!FileWrite(handle, m_dailyPnL)) return false; if(!FileWrite(handle, m_todayOrders)) return false; if(!FileWrite(handle, m_lastTradeDay)) return false; if(!FileWrite(handle, m_equityHigh)) return false; if(!FileWrite(handle, m_maxDrawdown)) return false; return true; } virtual bool Load(const int handle) { // Deserialize risk manager state if(!FileRead(handle, m_riskPerTrade)) return false; if(!FileRead(handle, m_dailyLossLimit)) return false; if(!FileRead(handle, m_maxOpenTrades)) return false; if(!FileRead(handle, m_maxOrdersPerDay)) return false; if(!FileRead(handle, m_useVolatilityAdjustment)) return false; if(!FileRead(handle, m_atrPeriod)) return false; if(!FileRead(handle, m_maxSpread)) return false; if(!FileRead(handle, m_circuitBreakerActive)) return false; if(!FileRead(handle, m_circuitBreakerLevel)) return false; if(!FileRead(handle, m_anomalyDetection)) return false; if(!FileRead(handle, m_maxLeverage)) return false; if(!FileRead(handle, m_dailyPnL)) return false; if(!FileRead(handle, m_todayOrders)) return false; if(!FileRead(handle, m_lastTradeDay)) return false; if(!FileRead(handle, m_equityHigh)) return false; if(!FileRead(handle, m_maxDrawdown)) return false; return true; } // Adversarial testing hooks #ifdef ADVERSARIAL_TESTING void InjectFault(const string component, const string type) { // Inject faults into the risk manager for adversarial testing } #endif }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRiskManager::CRiskManager(void) : m_riskPerTrade(1.0), m_dailyLossLimit(5.0), m_maxOpenTrades(5), m_useVolatilityAdjustment(true), m_maxDailyLoss(0.0), m_circuitBreakerActive(false), m_atrPeriod(14), m_maxSpread(20), m_circuitBreakerLevel(10.0), m_anomalyDetection(true), m_maxLeverage(30.0), m_dailyPnL(0.0), m_todayOrders(0), m_dailyOrderCount(0), m_dailyPL(0.0), m_lastTradeDay(0), m_lastError(""), m_equityHigh(0.0), m_maxDrawdown(0.0), m_isTradingAllowed(true), m_logToFile(false), m_consecutiveErrors(0), m_useCircuitBreaker(true), m_maxConsecutiveErrors(5), m_circuitBreakerTime(0), m_circuitBreakTime(300), m_trade(NULL), m_useNewsFilter(false), m_newsStartTime(0), m_newsEndTime(0), m_affectedSymbols(NULL), m_atrHandle(INVALID_HANDLE), m_security(NULL) { ArraySetAsSeries(m_atrBuffer, true); m_security = new CSecurityEnhancements(); m_security->Initialize(); m_trade = new CTrade(); m_trade->SetExpertMagicNumber(12345); m_trade->SetDeviationInPoints(10); m_trade->SetTypeFilling(ORDER_FILLING_FOK); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRiskManager::~CRiskManager(void) { // Clean up indicator handles if(m_atrHandle != INVALID_HANDLE) { IndicatorRelease(m_atrHandle); } // Clean up collections m_openTrades.Clear(); m_closedTrades.Clear(); // Clean up security manager if(CheckPointer(m_security) == POINTER_DYNAMIC) { delete m_security; m_security = NULL; } // Clean up trade object if(CheckPointer(m_trade) == POINTER_DYNAMIC) { delete m_trade; m_trade = NULL; } // Clean up affected symbols array if(CheckPointer(m_affectedSymbols) == POINTER_DYNAMIC) { delete m_affectedSymbols; m_affectedSymbols = NULL; } Deinitialize(); } //+------------------------------------------------------------------+ //| Initialize the risk manager | //+------------------------------------------------------------------+ bool CRiskManager::Initialize() { // Initialize ATR indicator for volatility measurement m_atrHandle = iATR(Symbol(), PERIOD_CURRENT, m_atrPeriod); if(m_atrHandle == INVALID_HANDLE) { m_lastError = "Failed to create ATR indicator"; Print("Failed to create ATR handle"); return false; } // Initialize ATR buffer ArraySetAsSeries(m_atrBuffer, true); // Load historical data if needed if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0) { m_lastError = "Failed to copy ATR buffer"; Print("Failed to copy ATR buffer: ", GetLastError()); return false; } // Update daily metrics UpdateDailyMetrics(); return true; } //+------------------------------------------------------------------+ //| Deinitialize risk manager | //+------------------------------------------------------------------+ void CRiskManager::Deinitialize(void) { if(m_atrHandle != INVALID_HANDLE) { IndicatorRelease(m_atrHandle); m_atrHandle = INVALID_HANDLE; } ArrayFree(m_atrBuffer); } //+------------------------------------------------------------------+ //| Validate order parameters | //+------------------------------------------------------------------+ bool CRiskManager::ValidateOrder(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp) { // Basic parameter validation if(volume <= 0 || price <= 0) { m_lastError = "Invalid order parameters"; return false; } // Check if trading is allowed if(!m_isTradingAllowed) { m_lastError = "Trading is currently suspended"; return false; } // Check daily limits if(!CheckDailyLimits()) { return false; } // Check market conditions if(!CheckMarketConditions(symbol)) { return false; } // Check margin requirements if(!CheckMargin(symbol, volume, price)) { return false; } // Check spread if(!CheckSpread(symbol)) { return false; } // Check market hours if(!CheckMarketHours()) { return false; } // Check news impact if(!CheckNewsImpact(symbol)) { return false; } // Check position size limits double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(volume < minLot || volume > maxLot) { m_lastError = StringFormat("Volume %.2f is outside allowed range [%.2f, %.2f]", volume, minLot, maxLot); return false; } // Round volume to nearest lot step volume = MathFloor(volume / lotStep) * lotStep; // Check stop loss and take profit levels if(sl > 0 && tp > 0) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; if(type == ORDER_TYPE_BUY) { if((price - sl) < minStopLevel || (tp - price) < minStopLevel) { m_lastError = StringFormat("Stop levels too close to price. Min distance: %.1f pips", minStopLevel/point); return false; } } else if(type == ORDER_TYPE_SELL) { if((sl - price) < minStopLevel || (price - tp) < minStopLevel) { m_lastError = StringFormat("Stop levels too close to price. Min distance: %.1f pips", minStopLevel/point); return false; } } } return true; } //+------------------------------------------------------------------+ //| Check daily trading limits (consolidated implementation) | //+------------------------------------------------------------------+ bool CRiskManager::CheckDailyLimits() { m_lastError = StringFormat("Daily loss limit reached (%.2f%%)", m_dailyPL); return false; } return true; } //+------------------------------------------------------------------+ //| Calculate position size based on risk | //+------------------------------------------------------------------+ double CRiskManager::CalculatePositionSize(string symbol, double stopLoss, double riskPercent) { if(stopLoss <= 0 || riskPercent <= 0) return 0.0; double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // Calculate position size based on risk double riskAmount = accountBalance * (riskPercent / 100.0); double stopLossPoints = stopLoss / tickSize; double lotSize = riskAmount / (stopLossPoints * tickValue); // Round to nearest lot step lotSize = MathFloor(lotSize / lotStep) * lotStep; // Apply position size limits lotSize = MathMin(MathMax(lotSize, minLot), maxLot); return lotSize; } //+------------------------------------------------------------------+ //| Get current market volatility | //+------------------------------------------------------------------+ double CRiskManager::GetCurrentVolatility(string symbol, ENUM_TIMEFRAMES timeframe, int period) { if(m_atrHandle == INVALID_HANDLE) return 0.0; if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0) return 0.0; return m_atrBuffer[0]; } //+------------------------------------------------------------------+ //| Handle trading errors | //+------------------------------------------------------------------+ void CRiskManager::HandleError(int error, string context) { string errorDesc = ""; bool isCritical = false; switch(error) { case 4756: errorDesc = "Invalid stops"; break; case 130: errorDesc = "Invalid stops"; isCritical = true; break; case 131: errorDesc = "Invalid volume"; isCritical = true; break; case 134: errorDesc = "Not enough money"; isCritical = true; break; case 10013: errorDesc = "Invalid request"; isCritical = true; break; default: errorDesc = "Error " + IntegerToString(error); } m_lastError = context + ": " + errorDesc; Print(m_lastError); // Update error tracking m_consecutiveErrors++; m_lastErrorTime = TimeCurrent(); // If too many errors, temporarily disable trading if(m_consecutiveErrors >= 5) { m_isTradingAllowed = false; Print("Trading temporarily disabled due to multiple errors"); } } //+------------------------------------------------------------------+ //| Reset error counter | //+------------------------------------------------------------------+ void CRiskManager::ResetErrorCounter(void) { m_consecutiveErrors = 0; m_lastError = ""; m_isTradingAllowed = true; } //+------------------------------------------------------------------+ //| Execute order with risk management | //+------------------------------------------------------------------+ bool CRiskManager::ExecuteOrder(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp, string comment) { // Validate order parameters if(!ValidateOrder(symbol, type, volume, price, sl, tp)) { Print("Order validation failed: ", m_lastError); return false; } // Execute the order bool result = false; if(type == ORDER_TYPE_BUY) result = m_trade.Buy(volume, symbol, price, sl, tp, comment); else if(type == ORDER_TYPE_SELL) result = m_trade.Sell(volume, symbol, price, sl, tp, comment); // Check result if(!result) { HandleError(GetLastError(), "Order execution failed"); return false; } // Update metrics m_dailyOrderCount++; LogTradeMetrics(symbol, type, volume, price, sl, tp); return true; } //+------------------------------------------------------------------+ //| Check margin requirements | //+------------------------------------------------------------------+ bool CRiskManager::CheckMargin(string symbol, double volume, double price) { double margin = 0.0; if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, volume, price, margin)) { m_lastError = "Failed to calculate margin"; return false; } double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if(margin > freeMargin) { m_lastError = StringFormat("Insufficient margin. Required: %.2f, Available: %.2f", margin, freeMargin); return false; } return true; } //+------------------------------------------------------------------+ //| Check current spread | //+------------------------------------------------------------------+ bool CRiskManager::CheckSpread(string symbol) { double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT); double maxSpread = SymbolInfoDouble(symbol, SYMBOL_ASK) * 0.001; // 0.1% of ask price if(spread > maxSpread) { m_lastError = StringFormat("Spread too high: %.1f pips (max: %.1f)", spread, maxSpread); return false; } return true; } //+------------------------------------------------------------------+ //| Check if current time is within trading hours | //+------------------------------------------------------------------+ bool CRiskManager::CheckMarketHours(void) { // Implement market hours check based on your trading strategy // This is a placeholder - adjust according to your needs MqlDateTime time; TimeToStruct(TimeCurrent(), time); // Example: Don't trade during major news events (first Friday of the month) if(time.day_of_week == 5 && time.day <= 7) { m_lastError = "Avoiding trading during major news events"; return false; } return true; } //+------------------------------------------------------------------+ //| Check for high-impact news events | //+------------------------------------------------------------------+ bool CRiskManager::CheckNewsImpact(string symbol) { // Implement news impact check based on your strategy // This is a placeholder - integrate with your news API or calendar return true; } //+------------------------------------------------------------------+ //| Update daily metrics | //+------------------------------------------------------------------+ void CRiskManager::UpdateDailyMetrics(void) { MqlDateTime time; TimeToStruct(TimeCurrent(), time); // Reset daily metrics if it's a new day if(time.day != m_lastTradeDay) { m_dailyPL = 0.0; m_dailyOrderCount = 0; m_lastTradeDay = time.day; } } //+------------------------------------------------------------------+ //| Log trade metrics | //+------------------------------------------------------------------+ void CRiskManager::LogTradeMetrics(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp) { // Log trade details for analysis string logEntry = StringFormat("%s %s %.2f lots @ %.5f, SL: %.5f, TP: %.5f", type == ORDER_TYPE_BUY ? "BUY" : "SELL", symbol, volume, price, sl, tp); Print(logEntry); // Here you could also write to a file or database for long-term analysis } //+------------------------------------------------------------------+