436 Zeilen
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();
| |||
}
| |||
| |||
|