//+------------------------------------------------------------------+ //| Utilities.mqh | //| Utility Functions Module 2.4 | //| Fixed: Remove Duplicates & Add Missing Members | //+------------------------------------------------------------------+ #ifndef UTILITIES_MQH #define UTILITIES_MQH #include "DataTypes.mqh" // Include DataTypes first for all type definitions //+------------------------------------------------------------------+ //| Logger Class | //+------------------------------------------------------------------+ class CLogger { private: static ENUM_LOG_LEVEL m_LogLevel; static string m_LogFile; static bool m_FileLogging; static bool m_TerminalLogging; static int m_FileHandle; public: //+------------------------------------------------------------------+ //| Initialize logger | //+------------------------------------------------------------------+ static void Init(ENUM_LOG_LEVEL level = LOG_INFO, bool fileLogging = true, bool terminalLogging = true) { m_LogLevel = level; m_FileLogging = fileLogging; m_TerminalLogging = terminalLogging; if(m_FileLogging) { string filename = "ERMT_" + TimeToString(TimeCurrent(), TIME_DATE) + ".log"; m_LogFile = filename; m_FileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); } } //+------------------------------------------------------------------+ //| Deinitialize logger | //+------------------------------------------------------------------+ static void Deinit() { if(m_FileHandle != INVALID_HANDLE) { FileClose(m_FileHandle); m_FileHandle = INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Log message | //+------------------------------------------------------------------+ static void Log(ENUM_LOG_LEVEL level, string message) { if(level < m_LogLevel) return; string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS); string levelStr = LogLevelToString(level); string logMessage = StringFormat("[%s] [%s] %s", timestamp, levelStr, message); if(m_TerminalLogging) Print(logMessage); if(m_FileLogging && m_FileHandle != INVALID_HANDLE) { FileWriteString(m_FileHandle, logMessage + "\n"); FileFlush(m_FileHandle); } } private: //+------------------------------------------------------------------+ //| Convert log level to string | //+------------------------------------------------------------------+ static string LogLevelToString(ENUM_LOG_LEVEL level) { switch(level) { case LOG_ERROR: return "ERROR"; case LOG_WARNING: return "WARNING"; case LOG_INFO: return "INFO"; case LOG_DEBUG: return "DEBUG"; default: return "UNKNOWN"; } } }; // Static member initialization ENUM_LOG_LEVEL CLogger::m_LogLevel = LOG_INFO; string CLogger::m_LogFile = ""; bool CLogger::m_FileLogging = false; bool CLogger::m_TerminalLogging = true; int CLogger::m_FileHandle = INVALID_HANDLE; //+------------------------------------------------------------------+ //| Utilities Class | //+------------------------------------------------------------------+ class CUtilities { public: //+------------------------------------------------------------------+ //| Convert order type to string | //+------------------------------------------------------------------+ static string OrderTypeToString(ENUM_ORDER_TYPE type) { switch(type) { case ORDER_TYPE_BUY: return "BUY"; case ORDER_TYPE_SELL: return "SELL"; case ORDER_TYPE_BUY_LIMIT: return "BUY_LIMIT"; case ORDER_TYPE_SELL_LIMIT: return "SELL_LIMIT"; case ORDER_TYPE_BUY_STOP: return "BUY_STOP"; case ORDER_TYPE_SELL_STOP: return "SELL_STOP"; case ORDER_TYPE_BUY_STOP_LIMIT: return "BUY_STOP_LIMIT"; case ORDER_TYPE_SELL_STOP_LIMIT: return "SELL_STOP_LIMIT"; default: return "UNKNOWN"; } } //+------------------------------------------------------------------+ //| Format currency value | //+------------------------------------------------------------------+ static string FormatCurrency(double value, string currency = "") { if(currency == "") currency = AccountInfoString(ACCOUNT_CURRENCY); return StringFormat("%s %.2f", currency, value); } //+------------------------------------------------------------------+ //| Format percentage | //+------------------------------------------------------------------+ static string FormatPercent(double value) { return StringFormat("%.2f%%", value); } //+------------------------------------------------------------------+ //| Get time frame string | //+------------------------------------------------------------------+ static string TimeFrameToString(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return "UNKNOWN"; } } //+------------------------------------------------------------------+ //| Calculate lot size based on risk | //+------------------------------------------------------------------+ static double CalculateLotSize(double riskAmount, double stopLossPoints, string symbol = NULL) { if(symbol == NULL) symbol = _Symbol; 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(stopLossPoints <= 0 || tickSize <= 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); } //+------------------------------------------------------------------+ //| Log trade info | //+------------------------------------------------------------------+ static void LogTradeInfo(const TradeInfo &trade) { string message = StringFormat( "Trade: Ticket=%d, Symbol=%s, Type=%s, Volume=%.2f, OpenPrice=%.5f, SL=%.5f, TP=%.5f, Profit=%.2f", trade.ticket, trade.symbol, OrderTypeToString((ENUM_ORDER_TYPE)trade.type), trade.volume, trade.openPrice, trade.stopLoss, trade.takeProfit, trade.profit ); CLogger::Log(LOG_INFO, message); } //+------------------------------------------------------------------+ //| Log risk event | //+------------------------------------------------------------------+ static void LogRiskEvent(ENUM_RISK_EVENT event) { 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; } CLogger::Log(LOG_WARNING, "Risk Event: " + eventName); } //+------------------------------------------------------------------+ //| Calculate performance metrics | //+------------------------------------------------------------------+ static void CalculatePerformanceMetrics( int totalTrades, int winningTrades, double totalProfit, double totalLoss, double &winRate, double &profitFactor, double &expectedValue ) { if(totalTrades == 0) { winRate = 0; profitFactor = 0; expectedValue = 0; return; } winRate = (double)winningTrades / totalTrades * 100; if(totalLoss != 0) profitFactor = MathAbs(totalProfit / totalLoss); else profitFactor = (totalProfit > 0) ? 999.99 : 0; double avgWin = (winningTrades > 0) ? totalProfit / winningTrades : 0; double avgLoss = ((totalTrades - winningTrades) > 0) ? totalLoss / (totalTrades - winningTrades) : 0; expectedValue = (winRate / 100 * avgWin) - ((100 - winRate) / 100 * MathAbs(avgLoss)); } //+------------------------------------------------------------------+ //| Format trade array for logging | //+------------------------------------------------------------------+ static string FormatTradeArray(const TradeInfo &trades[], int count) { if(count == 0) return "No trades"; string result = StringFormat("\n=== Trade List (%d trades) ===\n", count); for(int i = 0; i < count && i < ArraySize(trades); i++) { result += StringFormat( "#%d: Ticket=%d, %s %s, Vol=%.2f, Price=%.5f, SL=%.5f, TP=%.5f, P/L=%.2f\n", i + 1, trades[i].ticket, trades[i].symbol, OrderTypeToString((ENUM_ORDER_TYPE)trades[i].type), trades[i].volume, trades[i].openPrice, trades[i].stopLoss, trades[i].takeProfit, trades[i].profit ); } result += "=========================\n"; return result; } //+------------------------------------------------------------------+ //| Check if market is open | //+------------------------------------------------------------------+ static bool IsMarketOpen(string symbol = NULL) { if(symbol == NULL) symbol = _Symbol; datetime serverTime = TimeCurrent(); MqlDateTime dt; TimeToStruct(serverTime, dt); // Basic check - skip weekends if(dt.day_of_week == 0 || dt.day_of_week == 6) return false; // Check session times datetime from, to; if(SymbolInfoSessionTrade(symbol, (ENUM_DAY_OF_WEEK)dt.day_of_week, 0, from, to)) { datetime currentTime = serverTime % 86400; // Time of day in seconds datetime sessionStart = from % 86400; datetime sessionEnd = to % 86400; if(sessionEnd < sessionStart) // Session crosses midnight { return (currentTime >= sessionStart || currentTime <= sessionEnd); } else { return (currentTime >= sessionStart && currentTime <= sessionEnd); } } return true; // Default to open if can't determine } //+------------------------------------------------------------------+ //| Calculate correlation coefficient | //+------------------------------------------------------------------+ static double CalculateCorrelation(const double &x[], const double &y[]) { int n = MathMin(ArraySize(x), ArraySize(y)); if(n < 2) return 0; double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0; for(int i = 0; i < n; i++) { sumX += x[i]; sumY += y[i]; sumXY += x[i] * y[i]; sumX2 += x[i] * x[i]; sumY2 += y[i] * y[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; } //+------------------------------------------------------------------+ //| Get volatility (ATR-based) | //+------------------------------------------------------------------+ static double GetVolatility(string symbol, ENUM_TIMEFRAMES timeframe, int period = 14) { 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]; } }; #endif // UTILITIES_MQH