//+------------------------------------------------------------------+ //| Utilities.mqh | //| Utility Functions Module 2.3 | //| Fixed: Remove Duplicates & Add Missing Members | //+------------------------------------------------------------------+ #ifndef UTILITIES_MQH #define UTILITIES_MQH #include #include #include #include "DataTypes.mqh" //+------------------------------------------------------------------+ //| Log Levels | //+------------------------------------------------------------------+ enum ENUM_LOG_LEVEL { LOG_DEBUG = 0, LOG_INFO = 1, LOG_WARNING = 2, LOG_ERROR = 3, LOG_CRITICAL = 4 }; //+------------------------------------------------------------------+ //| Utilities Class | //+------------------------------------------------------------------+ class CUtilities : public CObject { private: // Logging ENUM_LOG_LEVEL m_log_level; string m_log_prefix; int m_log_handle; bool m_save_log; string m_log_filename; // Performance tracking ulong m_last_tick; double m_tick_duration; // ATR Indicator int m_atr_handle; int m_atr_period; public: // Constructor/Destructor CUtilities(); ~CUtilities(); // Initialization bool Initialize(ENUM_LOG_LEVEL level = LOG_INFO); void SetLogLevel(ENUM_LOG_LEVEL level) { m_log_level = level; } void EnableFileLogging(bool enable) { m_save_log = enable; } // Logging methods void Log(string message, ENUM_LOG_LEVEL level = LOG_INFO); void LogTrade(const TradeInfo &trade); void LogExternalTrade(ulong ticket, string action); void LogRiskEvent(ENUM_RISK_EVENT event, string details); void LogPerformance(const PerformanceMetrics &metrics); // Report generation string GenerateReport(const PerformanceMetrics &metrics); string GenerateReportEnhanced(const PerformanceMetrics &metrics, int external_count, double external_profit); void SaveTradeHistory(const TradeInfo &trades[], int count); // Technical indicators double GetATR(int period); double GetRSI(string symbol, ENUM_TIMEFRAMES timeframe, int period); double GetMA(string symbol, ENUM_TIMEFRAMES timeframe, int period, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price); double GetBollingerBand(string symbol, ENUM_TIMEFRAMES timeframe, int period, double deviation, int band_type); // Price calculations double NormalizePrice(double price, string symbol); double NormalizeLot(double lot, string symbol); double CalculatePipValue(string symbol, double lot_size); double CalculateSpread(string symbol); int GetDigits(string symbol) { return (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); } // Risk calculations double CalculatePositionSize(double risk_amount, double stop_distance, string symbol); double CalculateRiskAmount(double balance, double risk_percent); double CalculateRiskReward(double entry, double sl, double tp, ENUM_ORDER_TYPE type); // Time functions bool IsMarketOpen(); bool IsSessionActive(string session_name); datetime GetBarTime(string symbol, ENUM_TIMEFRAMES timeframe, int shift); int GetDayOfWeek() { MqlDateTime dt; TimeCurrent(dt); return dt.day_of_week; } // String utilities string FormatMoney(double amount); string FormatPercent(double percent); string OrderTypeToString(ENUM_ORDER_TYPE type); string TimeframeToString(ENUM_TIMEFRAMES tf); // File operations bool SaveToFile(string filename, string content); string LoadFromFile(string filename); void CloseExternalLog(); // Performance monitoring void StartTimer() { m_last_tick = GetMicrosecondCount(); } double GetElapsedTime() { return (GetMicrosecondCount() - m_last_tick) / 1000.0; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CUtilities::CUtilities() { m_log_level = LOG_INFO; m_log_prefix = "EA"; m_log_handle = INVALID_HANDLE; m_save_log = false; m_log_filename = ""; m_last_tick = 0; m_tick_duration = 0; m_atr_handle = INVALID_HANDLE; m_atr_period = 14; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CUtilities::~CUtilities() { if(m_log_handle != INVALID_HANDLE) FileClose(m_log_handle); if(m_atr_handle != INVALID_HANDLE) IndicatorRelease(m_atr_handle); } //+------------------------------------------------------------------+ //| Initialize | //+------------------------------------------------------------------+ bool CUtilities::Initialize(ENUM_LOG_LEVEL level) { m_log_level = level; // Create log file if enabled if(m_save_log) { m_log_filename = StringFormat("EA_Log_%s.txt", TimeToString(TimeCurrent(), TIME_DATE)); m_log_handle = FileOpen(m_log_filename, FILE_WRITE|FILE_TXT|FILE_ANSI); if(m_log_handle == INVALID_HANDLE) { m_save_log = false; Print("Failed to create log file"); } } // Initialize ATR indicator m_atr_handle = iATR(_Symbol, PERIOD_CURRENT, m_atr_period); if(m_atr_handle == INVALID_HANDLE) { Log("Failed to initialize ATR indicator", LOG_ERROR); return false; } Log("Utilities initialized successfully", LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Main Logging Function | //+------------------------------------------------------------------+ void CUtilities::Log(string message, ENUM_LOG_LEVEL level) { if(level < m_log_level) return; string level_str = ""; switch(level) { case LOG_DEBUG: level_str = "DEBUG"; break; case LOG_INFO: level_str = "INFO"; break; case LOG_WARNING: level_str = "WARN"; break; case LOG_ERROR: level_str = "ERROR"; break; case LOG_CRITICAL: level_str = "CRITICAL"; break; } string full_message = StringFormat("[%s] [%s] %s: %s", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), level_str, m_log_prefix, message); // Print to terminal Print(full_message); // Save to file if enabled if(m_save_log && m_log_handle != INVALID_HANDLE) { FileWriteString(m_log_handle, full_message + "\n"); FileFlush(m_log_handle); } } //+------------------------------------------------------------------+ //| Log Trade Information | //+------------------------------------------------------------------+ void CUtilities::LogTrade(const TradeInfo &trade) { string message = StringFormat("Trade %d: %s %s %.2f @ %.5f, SL=%.5f, TP=%.5f, P/L=%.2f", trade.ticket, OrderTypeToString(trade.type), trade.symbol, trade.volume, trade.entry_price, trade.sl, trade.tp, trade.profit); Log(message, LOG_INFO); } //+------------------------------------------------------------------+ //| Log External Trade | //+------------------------------------------------------------------+ void CUtilities::LogExternalTrade(ulong ticket, string action) { string message = StringFormat("External Trade %d: %s", ticket, action); Log(message, LOG_INFO); } //+------------------------------------------------------------------+ //| Log Risk Event | //+------------------------------------------------------------------+ void CUtilities::LogRiskEvent(ENUM_RISK_EVENT event, string details) { string event_str = ""; switch(event) { case RISK_OVERLEVERAGED: event_str = "Overleveraged"; break; case RISK_CORRELATION_HIGH: event_str = "High Correlation"; break; case RISK_DRAWDOWN_LIMIT: event_str = "Drawdown Limit"; break; case RISK_DAILY_LOSS_LIMIT: event_str = "Daily Loss Limit"; break; case RISK_MARGIN_CALL: event_str = "Margin Call Risk"; break; case RISK_VOLATILITY_SPIKE: event_str = "Volatility Spike"; break; case RISK_LIQUIDITY_ISSUE: event_str = "Liquidity Issue"; break; default: event_str = "Unknown Risk"; break; } string message = StringFormat("Risk Event: %s - %s", event_str, details); Log(message, LOG_WARNING); } //+------------------------------------------------------------------+ //| Log Performance Metrics | //+------------------------------------------------------------------+ void CUtilities::LogPerformance(const PerformanceMetrics &metrics) { string message = StringFormat( "Performance: Trades=%d, Win Rate=%.1f%%, PF=%.2f, DD=%.2f%%", metrics.total_trades, metrics.win_rate, metrics.profit_factor, metrics.max_drawdown ); Log(message, LOG_INFO); } //+------------------------------------------------------------------+ //| Generate Performance Report | //+------------------------------------------------------------------+ string CUtilities::GenerateReport(const PerformanceMetrics &metrics) { string report = "\n========== PERFORMANCE REPORT ==========\n"; report += StringFormat("Total Profit/Loss: %s\n", FormatMoney(metrics.total_profit)); report += StringFormat("Total Trades: %d\n", metrics.total_trades); report += StringFormat("Winning Trades: %d (%.1f%%)\n", metrics.winning_trades, metrics.win_rate); report += StringFormat("Losing Trades: %d\n", metrics.losing_trades); report += StringFormat("Average Win: %s\n", FormatMoney(metrics.average_win)); report += StringFormat("Average Loss: %s\n", FormatMoney(metrics.average_loss)); report += StringFormat("Profit Factor: %.2f\n", metrics.profit_factor); report += StringFormat("Max Drawdown: %.2f%%\n", metrics.max_drawdown); report += StringFormat("Current Drawdown: %.2f%%\n", metrics.current_drawdown); report += StringFormat("Sharpe Ratio: %.2f\n", metrics.sharpe_ratio); report += StringFormat("Expected Value: %s\n", FormatMoney(metrics.expected_value)); report += "==========================================\n"; return report; } //+------------------------------------------------------------------+ //| Generate Enhanced Report with External Trades | //+------------------------------------------------------------------+ string CUtilities::GenerateReportEnhanced(const PerformanceMetrics &metrics, int external_count, double external_profit) { string report = GenerateReport(metrics); // Add external trade information report += "\n========== EXTERNAL TRADES ==========\n"; report += StringFormat("External Positions: %d\n", external_count); report += StringFormat("External P/L: %s\n", FormatMoney(external_profit)); report += StringFormat("Total Combined P/L: %s\n", FormatMoney(metrics.total_profit + external_profit)); report += "======================================\n"; return report; } //+------------------------------------------------------------------+ //| Save Trade History to File | //+------------------------------------------------------------------+ void CUtilities::SaveTradeHistory(const TradeInfo &trades[], int count) { string filename = StringFormat("TradeHistory_%s.csv", TimeToString(TimeCurrent(), TIME_DATE)); int handle = FileOpen(filename, FILE_WRITE|FILE_CSV); if(handle == INVALID_HANDLE) { Log("Failed to create trade history file", LOG_ERROR); return; } // Write header FileWrite(handle, "Ticket", "Symbol", "Type", "Volume", "Entry", "Exit", "SL", "TP", "Profit", "Open Time", "Close Time"); // Write trade data for(int i = 0; i < count; i++) { FileWrite(handle, trades[i].ticket, trades[i].symbol, OrderTypeToString(trades[i].type), trades[i].volume, trades[i].entry_price, trades[i].current_price, trades[i].sl, trades[i].tp, trades[i].profit, TimeToString(trades[i].open_time), TimeToString(trades[i].last_update)); } FileClose(handle); Log(StringFormat("Trade history saved to %s", filename), LOG_INFO); } //+------------------------------------------------------------------+ //| Get ATR Value | //+------------------------------------------------------------------+ double CUtilities::GetATR(int period) { if(period != m_atr_period) { // Reinitialize with new period if(m_atr_handle != INVALID_HANDLE) IndicatorRelease(m_atr_handle); m_atr_period = period; m_atr_handle = iATR(_Symbol, PERIOD_CURRENT, m_atr_period); } if(m_atr_handle == INVALID_HANDLE) return 0; double atr_buffer[1]; if(CopyBuffer(m_atr_handle, 0, 0, 1, atr_buffer) <= 0) return 0; return atr_buffer[0]; } //+------------------------------------------------------------------+ //| Get RSI Value | //+------------------------------------------------------------------+ double CUtilities::GetRSI(string symbol, ENUM_TIMEFRAMES timeframe, int period) { int handle = iRSI(symbol, timeframe, period, PRICE_CLOSE); if(handle == INVALID_HANDLE) return 50; // Return neutral value double rsi_buffer[1]; if(CopyBuffer(handle, 0, 0, 1, rsi_buffer) <= 0) { IndicatorRelease(handle); return 50; } IndicatorRelease(handle); return rsi_buffer[0]; } //+------------------------------------------------------------------+ //| Get Moving Average Value | //+------------------------------------------------------------------+ double CUtilities::GetMA(string symbol, ENUM_TIMEFRAMES timeframe, int period, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price) { int handle = iMA(symbol, timeframe, period, 0, method, price); if(handle == INVALID_HANDLE) return 0; double ma_buffer[1]; if(CopyBuffer(handle, 0, 0, 1, ma_buffer) <= 0) { IndicatorRelease(handle); return 0; } IndicatorRelease(handle); return ma_buffer[0]; } //+------------------------------------------------------------------+ //| Get Bollinger Band Value | //+------------------------------------------------------------------+ double CUtilities::GetBollingerBand(string symbol, ENUM_TIMEFRAMES timeframe, int period, double deviation, int band_type) { int handle = iBands(symbol, timeframe, period, 0, deviation, PRICE_CLOSE); if(handle == INVALID_HANDLE) return 0; double band_buffer[1]; // band_type: 0=middle, 1=upper, 2=lower if(CopyBuffer(handle, band_type, 0, 1, band_buffer) <= 0) { IndicatorRelease(handle); return 0; } IndicatorRelease(handle); return band_buffer[0]; } //+------------------------------------------------------------------+ //| Calculate Position Size | //+------------------------------------------------------------------+ double CUtilities::CalculatePositionSize(double risk_amount, 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_value == 0 || tick_size == 0) return 0; double lot_size = (risk_amount * tick_size) / (stop_distance * tick_value); return NormalizeLot(lot_size, symbol); } //+------------------------------------------------------------------+ //| Calculate Risk Amount | //+------------------------------------------------------------------+ double CUtilities::CalculateRiskAmount(double balance, double risk_percent) { return balance * risk_percent / 100; } //+------------------------------------------------------------------+ //| Calculate Risk Reward Ratio | //+------------------------------------------------------------------+ double CUtilities::CalculateRiskReward(double entry, double sl, double tp, ENUM_ORDER_TYPE type) { if(sl == 0 || tp == 0) return 0; double risk = MathAbs(entry - sl); double reward = MathAbs(tp - entry); if(risk == 0) return 0; return reward / risk; } //+------------------------------------------------------------------+ //| Normalize Price | //+------------------------------------------------------------------+ double CUtilities::NormalizePrice(double price, string symbol) { int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); return NormalizeDouble(price, digits); } //+------------------------------------------------------------------+ //| Normalize Lot Size | //+------------------------------------------------------------------+ double CUtilities::NormalizeLot(double lot, string symbol) { double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(lot < min_lot) lot = min_lot; if(lot > max_lot) lot = max_lot; int steps = (int)MathRound(lot / lot_step); return steps * lot_step; } //+------------------------------------------------------------------+ //| Calculate Pip Value | //+------------------------------------------------------------------+ double CUtilities::CalculatePipValue(string symbol, double lot_size) { double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); double pip_size = (digits == 3 || digits == 5) ? tick_size * 10 : tick_size; double pip_value = (tick_value * pip_size) / tick_size * lot_size; return pip_value; } //+------------------------------------------------------------------+ //| Calculate Spread | //+------------------------------------------------------------------+ double CUtilities::CalculateSpread(string symbol) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(point == 0) return 0; return (ask - bid) / point; } //+------------------------------------------------------------------+ //| Check if Market is Open | //+------------------------------------------------------------------+ bool CUtilities::IsMarketOpen() { datetime current = TimeCurrent(); MqlDateTime dt; TimeToStruct(current, dt); // Skip weekends if(dt.day_of_week == 0 || dt.day_of_week == 6) return false; // Check session times long session_deals = SymbolInfoInteger(_Symbol, SYMBOL_SESSION_DEALS); if(session_deals == 0) return false; return true; } //+------------------------------------------------------------------+ //| Check if Session is Active | //+------------------------------------------------------------------+ bool CUtilities::IsSessionActive(string session_name) { MqlDateTime current; TimeToStruct(TimeCurrent(), current); int hour = current.hour; // Define session times (simplified) if(session_name == "ASIAN") return (hour >= 0 && hour < 9); else if(session_name == "EUROPEAN") return (hour >= 8 && hour < 17); else if(session_name == "AMERICAN") return (hour >= 13 && hour < 22); else if(session_name == "PACIFIC") return (hour >= 21 || hour < 6); return false; } //+------------------------------------------------------------------+ //| Get Bar Time | //+------------------------------------------------------------------+ datetime CUtilities::GetBarTime(string symbol, ENUM_TIMEFRAMES timeframe, int shift) { datetime time_array[1]; if(CopyTime(symbol, timeframe, shift, 1, time_array) > 0) return time_array[0]; return 0; } //+------------------------------------------------------------------+ //| Format Money | //+------------------------------------------------------------------+ string CUtilities::FormatMoney(double amount) { string sign = amount < 0 ? "-" : ""; amount = MathAbs(amount); return StringFormat("%s$%.2f", sign, amount); } //+------------------------------------------------------------------+ //| Format Percent | //+------------------------------------------------------------------+ string CUtilities::FormatPercent(double percent) { return StringFormat("%.2f%%", percent); } //+------------------------------------------------------------------+ //| Order Type to String | //+------------------------------------------------------------------+ string CUtilities::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"; default: return "UNKNOWN"; } } //+------------------------------------------------------------------+ //| Timeframe to String | //+------------------------------------------------------------------+ string CUtilities::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 "Current"; } } //+------------------------------------------------------------------+ //| Save Content to File | //+------------------------------------------------------------------+ bool CUtilities::SaveToFile(string filename, string content) { int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_ANSI); if(handle == INVALID_HANDLE) { Log(StringFormat("Failed to open file %s for writing", filename), LOG_ERROR); return false; } FileWriteString(handle, content); FileClose(handle); Log(StringFormat("Content saved to %s", filename), LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Load Content from File | //+------------------------------------------------------------------+ string CUtilities::LoadFromFile(string filename) { int handle = FileOpen(filename, FILE_READ|FILE_TXT|FILE_ANSI); if(handle == INVALID_HANDLE) { Log(StringFormat("Failed to open file %s for reading", filename), LOG_ERROR); return ""; } string content = ""; while(!FileIsEnding(handle)) { content += FileReadString(handle) + "\n"; } FileClose(handle); return content; } //+------------------------------------------------------------------+ //| Close External Log File | //+------------------------------------------------------------------+ void CUtilities::CloseExternalLog() { if(m_log_handle != INVALID_HANDLE) { FileClose(m_log_handle); m_log_handle = INVALID_HANDLE; Log("External log file closed", LOG_INFO); } } #endif // UTILITIES_MQH