2025-08-06 15:48:55 -04:00 | | | //+------------------------------------------------------------------+
|
2025-08-10 17:43:21 -04:00 | | | //| OTCLearningEngine.mqh |
|
| | | //| Copyright 2023, OTC Escape EA |
|
| | | //| https://www.otc.com |
|
2025-08-06 15:48:55 -04:00 | | | //+------------------------------------------------------------------+
|
2025-08-10 17:43:21 -04:00 | | | #property copyright "Copyright 2023, OTC"
|
| | | #property link "https://www.otc.com"
|
| | | #property version "2.00"
|
2025-08-06 15:48:55 -04:00 | | | #property strict
|
| | |
|
| | | #include <Arrays\ArrayObj.mqh>
|
| | | #include <Files\File.mqh>
|
| | | #include "OTCTypes.mqh"
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2025-08-10 17:43:21 -04:00 | | | //| Learning Engine for OTC Escape EA |
|
2025-08-06 15:48:55 -04:00 | | | //+------------------------------------------------------------------+
|
2025-08-10 17:43:21 -04:00 | | | class CLearningEngine : public CObject
|
2025-08-06 15:48:55 -04:00 | | | {
|
| | | private:
|
2025-08-10 17:43:21 -04:00 | | | 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;
|
| | |
|
2025-08-06 15:48:55 -04:00 | | | public:
|
2025-08-10 17:43:21 -04:00 | | | 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)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | if(FolderCreate(m_data_path) == false)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | PrintFormat("Failed to create data directory: %s, Error: %d", m_data_path, GetLastError());
|
2025-08-06 15:48:55 -04:00 | | | }
|
| | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | 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))
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | Print("Failed to add trade to history, deleting record.");
|
| | | delete trade;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 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
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | _UpdateMetricSetWithTrade(m_metrics_buy[regime], closed_trade);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | else // Sell
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | _UpdateMetricSetWithTrade(m_metrics_sell[regime], closed_trade);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | _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)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | 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;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | else if(metrics_sell.profit_factor > metrics_buy.profit_factor && metrics_sell.total_trades >= m_min_trades_for_analysis)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | 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;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | 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)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | metrics.total_wins++;
|
| | | metrics.total_profit += trade->profit;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | else
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | metrics.total_losses++;
|
| | | metrics.total_loss += trade->profit;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Finalize calculations for a metric set |
|
| | | //+------------------------------------------------------------------+
|
| | | void CLearningEngine::_FinalizeMetricSet(SMetricSet &metrics)
|
| | | {
|
| | | if(metrics.total_trades > 0)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | 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;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | if(metrics.gross_loss != 0)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | metrics.profit_factor = MathAbs(metrics.gross_profit / metrics.gross_loss);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | else
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | metrics.profit_factor = (metrics.gross_profit > 0) ? 999.0 : 0.0; // High value for no losses
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Calculate performance metrics for all regimes |
|
| | | //+------------------------------------------------------------------+
|
| | | void CLearningEngine::CalculatePerformanceMetrics()
|
| | | {
|
| | | // Reset all metrics
|
| | | _InitMetricSet(m_overall_metrics);
|
| | | for(int i = 0; i < 4; i++)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | _InitMetricSet(m_metrics_buy[i]);
|
| | | _InitMetricSet(m_metrics_sell[i]);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | // Recalculate from history
|
| | | int total = m_trade_history.Total();
|
| | | for(int i = 0; i < total; i++)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | STradeRecord *trade = m_trade_history.At(i);
|
| | | _AnalyzeClosedTrade(trade);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | // Finalize all metrics
|
| | | _FinalizeMetricSet(m_overall_metrics);
|
| | | for(int i = 0; i < 4; i++)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | _FinalizeMetricSet(m_metrics_buy[i]);
|
| | | _FinalizeMetricSet(m_metrics_sell[i]);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 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)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | PrintFormat("Failed to open metrics file for writing: %s", file_path);
|
| | | return false;
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | FileWriteStruct(handle, &m_overall_metrics);
|
| | | for(int i = 0; i < 4; i++)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | FileWriteStruct(handle, &m_metrics_buy[i]);
|
| | | FileWriteStruct(handle, &m_metrics_sell[i]);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | FileClose(handle);
|
| | | return true;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Load metrics from a file |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CLearningEngine::LoadMetrics()
|
| | | {
|
| | | string file_path = _GetMetricsFilePath();
|
| | | if(!FileIsExist(file_path))
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | // Initialize all metrics if file doesn't exist
|
| | | _InitMetricSet(m_overall_metrics);
|
| | | for(int i = 0; i < 4; i++)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | _InitMetricSet(m_metrics_buy[i]);
|
| | | _InitMetricSet(m_metrics_sell[i]);
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | return true; // Not an error, just first run
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | int handle = FileOpen(file_path, FILE_READ | FILE_BIN);
|
| | | if(handle == INVALID_HANDLE)
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | 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)
|
2025-08-06 15:48:55 -04:00 | | | {
|
| | | if(!m_trade_history.Add(trade))
|
| | | {
|
2025-08-10 17:43:21 -04:00 | | | Print("Failed to add loaded trade to history, deleting record.");
|
2025-08-06 15:48:55 -04:00 | | | delete trade;
|
| | | }
|
| | | }
|
2025-08-10 17:43:21 -04:00 | | | else
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | Print("Failed to read trade record from file, deleting placeholder.");
|
| | | delete trade;
|
| | | break; // Stop reading if there's an error
|
2025-08-06 15:48:55 -04:00 | | | }
|
| | | }
|
2025-08-10 17:43:21 -04:00 | | |
|
| | | 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())
|
2025-08-06 15:48:55 -04:00 | | | {
|
2025-08-10 17:43:21 -04:00 | | | 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]);
|
| | | }
|
2025-08-06 15:48:55 -04:00 | | | }
|
2025-08-10 17:43:21 -04:00 | | | _LoadTradeHistory();
|
| | | }
|
| | |
|
| | |
|