forked from Princeec13/mql5
436 lines
15 KiB
MQL5
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();
|
|
}
|
|
|
|
|