mql5/Include/OTCLearningEngine.mqh
2025-08-16 12:30:04 -04:00

436 lines
15 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <Arrays\ArrayObj.mqh>
#include <Files\File.mqh>
#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();
}