//+------------------------------------------------------------------+ //| OTCLearningEngine.mqh | //| Copyright 2023, OTC Escape EA | //| https://www.otc.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, OTC" #property link "https://www.otc.com" #property version "2.00" #property strict #include #include #include "OTCTypes.mqh" //+------------------------------------------------------------------+ //| Learning Engine for OTC Escape EA | //+------------------------------------------------------------------+ class CLearningEngine : public CObject { private: CArrayObj m_trade_history; // Holds pointers to STradeRecord objects SMetricSet m_overall_metrics; SMetricSet m_metrics_buy[4]; SMetricSet m_metrics_sell[4]; double m_volatility_threshold; double m_trend_threshold; int m_min_trades_for_analysis; string m_data_path; // Path for saving/loading data string m_symbol; ENUM_TIMEFRAMES m_period; public: CLearningEngine(double volatility_thresh, double trend_thresh); ~CLearningEngine(); void Init(string symbol, ENUM_TIMEFRAMES period); void SetMinTradesForAnalysis(int min_trades); void AddTrade(STradeRecord *trade_record); void CalculatePerformanceMetrics(); int GetTotalTrades() const; E_MARKET_REGIME GetCurrentMarketRegime(double volatility, double trend_strength); bool GetOptimizedParameters(SMarketState &state); private: // Private helper methods for metric calculations void _InitMetricSet(SMetricSet &metrics); void _UpdateMetricSetWithTrade(SMetricSet &metrics, const STradeRecord *trade); void _FinalizeMetricSet(SMetricSet &metrics); void _AnalyzeClosedTrade(const STradeRecord *closed_trade); // Private helper methods for data persistence bool SaveMetrics(); bool LoadMetrics(); bool SaveTradeHistory(); void _LoadTradeHistory(); string _GetMetricsFilePath(); string _GetTradeHistoryFilePath(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLearningEngine::CLearningEngine(double volatility_thresh, double trend_thresh) { m_volatility_threshold = volatility_thresh; m_trend_threshold = trend_thresh; m_min_trades_for_analysis = 10; // Default value m_data_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\OTC_Escape_Data"; if(FileIsExist(m_data_path) == false) { if(FolderCreate(m_data_path) == false) { PrintFormat("Failed to create data directory: %s, Error: %d", m_data_path, GetLastError()); } } m_trade_history.FreeMode(true); // CArrayObj will delete the STradeRecord pointers it holds } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLearningEngine::~CLearningEngine() { SaveMetrics(); SaveTradeHistory(); } //+------------------------------------------------------------------+ //| Add a newly opened trade to the history | //+------------------------------------------------------------------+ void CLearningEngine::SetMinTradesForAnalysis(int min_trades) { m_min_trades_for_analysis = min_trades; } //+------------------------------------------------------------------+ //| Add a newly opened trade to the history | //+------------------------------------------------------------------+ void CLearningEngine::AddTrade(STradeRecord *trade) { if(CheckPointer(trade) == POINTER_INVALID) return; if(!m_trade_history.Add(trade)) { Print("Failed to add trade to history, deleting record."); delete trade; } } //+------------------------------------------------------------------+ //| Analyze a closed trade and update metrics | //+------------------------------------------------------------------+ void CLearningEngine::_AnalyzeClosedTrade(const STradeRecord *closed_trade) { if(CheckPointer(closed_trade) == POINTER_INVALID) return; E_MARKET_REGIME regime = closed_trade->regime; if(closed_trade->trade_direction == 0) // Buy { _UpdateMetricSetWithTrade(m_metrics_buy[regime], closed_trade); } else // Sell { _UpdateMetricSetWithTrade(m_metrics_sell[regime], closed_trade); } _UpdateMetricSetWithTrade(m_overall_metrics, closed_trade); } //+------------------------------------------------------------------+ //| Get current market regime | //+------------------------------------------------------------------+ E_MARKET_REGIME CLearningEngine::GetCurrentMarketRegime(double volatility, double trend_strength) { bool is_volatile = volatility > m_volatility_threshold; bool is_trending = trend_strength > m_trend_threshold; if(is_volatile && is_trending) return REGIME_VOLATILE_TRENDING; if(is_volatile && !is_trending) return REGIME_VOLATILE_RANGING; if(!is_volatile && is_trending) return REGIME_QUIET_TRENDING; // Default to quiet ranging return REGIME_QUIET_RANGING; } //+------------------------------------------------------------------+ //| Get optimized parameters for the current market state | //+------------------------------------------------------------------+ bool CLearningEngine::GetOptimizedParameters(SMarketState &market_state) { E_MARKET_REGIME regime = market_state.regime; SMetricSet &metrics_buy = m_metrics_buy[regime]; SMetricSet &metrics_sell = m_metrics_sell[regime]; _FinalizeMetricSet(metrics_buy); _FinalizeMetricSet(metrics_sell); // Simple logic: choose the direction with better profit factor if(metrics_buy.profit_factor > metrics_sell.profit_factor && metrics_buy.total_trades >= m_min_trades_for_analysis) { market_state.trade_direction = ORDER_TYPE_BUY; market_state.optimal_sl = metrics_buy.avg_sl; market_state.optimal_tp = metrics_buy.avg_tp; return true; } else if(metrics_sell.profit_factor > metrics_buy.profit_factor && metrics_sell.total_trades >= m_min_trades_for_analysis) { market_state.trade_direction = ORDER_TYPE_SELL; market_state.optimal_sl = metrics_sell.avg_sl; market_state.optimal_tp = metrics_sell.avg_tp; return true; } return false; } //+------------------------------------------------------------------+ //| Initialize a metric set | //+------------------------------------------------------------------+ void CLearningEngine::_InitMetricSet(SMetricSet &metrics) { metrics.Reset(); } //+------------------------------------------------------------------+ //| Update a metric set with a single trade's data | //+------------------------------------------------------------------+ void CLearningEngine::_UpdateMetricSetWithTrade(SMetricSet &metrics, const STradeRecord *trade) { if(CheckPointer(trade) == POINTER_INVALID) return; metrics.total_trades++; metrics.total_sl_pips += (trade->stop_loss > 0 && trade->entry_price > 0) ? MathAbs(trade->entry_price - trade->stop_loss) / _Point : 0; metrics.total_tp_pips += (trade->take_profit > 0 && trade->entry_price > 0) ? MathAbs(trade->entry_price - trade->take_profit) / _Point : 0; if(trade->profit > 0) { metrics.total_wins++; metrics.total_profit += trade->profit; } else { metrics.total_losses++; metrics.total_loss += trade->profit; } } //+------------------------------------------------------------------+ //| Finalize calculations for a metric set | //+------------------------------------------------------------------+ void CLearningEngine::_FinalizeMetricSet(SMetricSet &metrics) { if(metrics.total_trades > 0) { metrics.win_rate = (double)metrics.total_wins / metrics.total_trades; metrics.avg_sl = (metrics.total_sl_pips > 0) ? metrics.total_sl_pips / metrics.total_trades : 0; metrics.avg_tp = (metrics.total_tp_pips > 0) ? metrics.total_tp_pips / metrics.total_trades : 0; } if(metrics.gross_loss != 0) { metrics.profit_factor = MathAbs(metrics.gross_profit / metrics.gross_loss); } else { metrics.profit_factor = (metrics.gross_profit > 0) ? 999.0 : 0.0; // High value for no losses } } //+------------------------------------------------------------------+ //| Calculate performance metrics for all regimes | //+------------------------------------------------------------------+ void CLearningEngine::CalculatePerformanceMetrics() { // Reset all metrics _InitMetricSet(m_overall_metrics); for(int i = 0; i < 4; i++) { _InitMetricSet(m_metrics_buy[i]); _InitMetricSet(m_metrics_sell[i]); } // Recalculate from history int total = m_trade_history.Total(); for(int i = 0; i < total; i++) { STradeRecord *trade = m_trade_history.At(i); _AnalyzeClosedTrade(trade); } // Finalize all metrics _FinalizeMetricSet(m_overall_metrics); for(int i = 0; i < 4; i++) { _FinalizeMetricSet(m_metrics_buy[i]); _FinalizeMetricSet(m_metrics_sell[i]); } } //+------------------------------------------------------------------+ //| Get total trades from history | //+------------------------------------------------------------------+ int CLearningEngine::GetTotalTrades() const { return m_trade_history.Total(); } //+------------------------------------------------------------------+ //| Get file path for metrics | //+------------------------------------------------------------------+ string CLearningEngine::_GetMetricsFilePath() { return TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\" + m_symbol + "_" + EnumToString(m_period) + "_metrics.bin"; } //+------------------------------------------------------------------+ //| Get file path for trade history | //+------------------------------------------------------------------+ string CLearningEngine::_GetTradeHistoryFilePath() { return StringFormat("%s\\%s_%s_trades.bin", m_data_path, m_symbol, EnumToString(m_period)); } //+------------------------------------------------------------------+ //| Save metrics to a file | //+------------------------------------------------------------------+ bool CLearningEngine::SaveMetrics() { string file_path = _GetMetricsFilePath(); int handle = FileOpen(file_path, FILE_WRITE | FILE_BIN); if(handle == INVALID_HANDLE) { PrintFormat("Failed to open metrics file for writing: %s", file_path); return false; } FileWriteStruct(handle, &m_overall_metrics); for(int i = 0; i < 4; i++) { FileWriteStruct(handle, &m_metrics_buy[i]); FileWriteStruct(handle, &m_metrics_sell[i]); } FileClose(handle); return true; } //+------------------------------------------------------------------+ //| Load metrics from a file | //+------------------------------------------------------------------+ bool CLearningEngine::LoadMetrics() { string file_path = _GetMetricsFilePath(); if(!FileIsExist(file_path)) { // Initialize all metrics if file doesn't exist _InitMetricSet(m_overall_metrics); for(int i = 0; i < 4; i++) { _InitMetricSet(m_metrics_buy[i]); _InitMetricSet(m_metrics_sell[i]); } return true; // Not an error, just first run } int handle = FileOpen(file_path, FILE_READ | FILE_BIN); if(handle == INVALID_HANDLE) { PrintFormat("Failed to open metrics file for reading: %s", file_path); return false; } FileReadStruct(handle, &m_overall_metrics); for(int i = 0; i < 4; i++) { FileReadStruct(handle, &m_metrics_buy[i]); FileReadStruct(handle, &m_metrics_sell[i]); } FileClose(handle); return true; } //+------------------------------------------------------------------+ //| Save trade history to a file | //+------------------------------------------------------------------+ bool CLearningEngine::SaveTradeHistory() { string file_path = _GetTradeHistoryFilePath(); int handle = FileOpen(file_path, FILE_WRITE | FILE_BIN); if(handle == INVALID_HANDLE) { PrintFormat("Failed to open trade history file for writing: %s", file_path); return false; } int total = m_trade_history.Total(); FileWriteInteger(handle, total, INT_VALUE); for(int i = 0; i < total; i++) { STradeRecord *trade = m_trade_history.At(i); if(CheckPointer(trade) == POINTER_INVALID) continue; FileWriteStruct(handle, trade); } FileClose(handle); return true; } //+------------------------------------------------------------------+ //| Load trade history from a file | //+------------------------------------------------------------------+ void CLearningEngine::_LoadTradeHistory() { string file_path = _GetTradeHistoryFilePath(); if(!FileIsExist(file_path)) return; int handle = FileOpen(file_path, FILE_READ | FILE_BIN); if(handle == INVALID_HANDLE) { PrintFormat("Failed to open trade history file for reading: %s", file_path); return; } m_trade_history.Clear(); int total = (int)FileReadInteger(handle, INT_VALUE); for(int i = 0; i < total; i++) { STradeRecord *trade = new STradeRecord(); if(FileReadStruct(handle, trade) > 0) { if(!m_trade_history.Add(trade)) { Print("Failed to add loaded trade to history, deleting record."); delete trade; } } else { Print("Failed to read trade record from file, deleting placeholder."); delete trade; break; // Stop reading if there's an error } } FileClose(handle); } //+------------------------------------------------------------------+ //| Initialize the learning engine with symbol and period | //+------------------------------------------------------------------+ void CLearningEngine::Init(string symbol, ENUM_TIMEFRAMES period) { m_symbol = symbol; m_period = period; // Now that we have symbol and period, load data if(!LoadMetrics()) { Print("No metrics file found. Initializing new metrics."); // Initialize all metrics if file doesn't exist _InitMetricSet(m_overall_metrics); for(int i = 0; i < 4; i++) { _InitMetricSet(m_metrics_buy[i]); _InitMetricSet(m_metrics_sell[i]); } } _LoadTradeHistory(); }