mql5/Experts/Advisors/DualEA/Include/MLFeedbackIntegration.mqh
2026-02-24 12:47:37 -05:00

294 lines
10 KiB
MQL5

//+------------------------------------------------------------------+
//| MLFeedbackIntegration.mqh |
//| Purpose: Bridge MQL5 trades to Python ML feedback loop |
//| This file writes trade outcomes to DualEA\feedback\immediate\ |
//+------------------------------------------------------------------+
#ifndef __MLFEEDBACKINTEGRATION_MQH__
#define __MLFEEDBACKINTEGRATION_MQH__
#include <Files/File.mqh>
#include "IStrategy.mqh"
//+------------------------------------------------------------------+
//| Trade Feedback Structure |
//+------------------------------------------------------------------+
struct STradeFeedback
{
string symbol;
string strategy;
int timeframe;
ulong ticket;
datetime entry_time;
datetime exit_time;
double entry_price;
double exit_price;
double volume;
double pnl;
double r_multiple;
bool won;
string regime;
double atr_at_entry;
double rsi_at_entry;
double gate_confidences[8]; // G1-G8 confidence scores
void Init()
{
symbol = "";
strategy = "";
timeframe = 0;
ticket = 0;
entry_time = 0;
exit_time = 0;
entry_price = 0.0;
exit_price = 0.0;
volume = 0.0;
pnl = 0.0;
r_multiple = 0.0;
won = false;
regime = "";
atr_at_entry = 0.0;
rsi_at_entry = 0.0;
ArrayInitialize(gate_confidences, 0.0);
}
};
//+------------------------------------------------------------------+
//| Global State for ML Feedback |
//+------------------------------------------------------------------+
string g_ml_feedback_dir = "DualEA\\feedback\\immediate\\";
int g_ml_write_count = 0;
//+------------------------------------------------------------------+
//| Helper: Escape JSON String |
//+------------------------------------------------------------------+
string EscapeJsonString(const string text)
{
string result = text;
StringReplace(result, "\\", "\\\\");
StringReplace(result, "\"", "\\\"");
StringReplace(result, "\n", "\\n");
StringReplace(result, "\r", "\\r");
StringReplace(result, "\t", "\\t");
return result;
}
//+------------------------------------------------------------------+
//| Write Trade Feedback to JSON File |
//+------------------------------------------------------------------+
bool WriteTradeFeedback(const STradeFeedback &feedback)
{
// Generate unique filename
string filename = StringFormat("feedback_%I64d_%s_%s.json",
feedback.ticket, feedback.symbol,
IntegerToString((int)TimeCurrent()));
string filepath = g_ml_feedback_dir + filename;
int handle = FileOpen(filepath, FILE_WRITE|FILE_TXT|FILE_COMMON|FILE_UNICODE);
if(handle == INVALID_HANDLE)
{
// Try without FILE_COMMON
handle = FileOpen(filepath, FILE_WRITE|FILE_TXT|FILE_UNICODE);
if(handle == INVALID_HANDLE)
{
PrintFormat("[MLFeedback] ERROR: Cannot open feedback file %s (Error %d)",
filepath, GetLastError());
return false;
}
}
// Build JSON manually (MQL5 doesn't have built-in JSON)
string json = "{\n";
json += StringFormat(" \"symbol\": \"%s\",\n", EscapeJsonString(feedback.symbol));
json += StringFormat(" \"strategy\": \"%s\",\n", EscapeJsonString(feedback.strategy));
json += StringFormat(" \"timeframe\": %d,\n", feedback.timeframe);
json += StringFormat(" \"ticket\": %I64d,\n", feedback.ticket);
json += StringFormat(" \"entry_time\": \"%s\",\n", TimeToString(feedback.entry_time, TIME_DATE|TIME_SECONDS));
json += StringFormat(" \"exit_time\": \"%s\",\n", TimeToString(feedback.exit_time, TIME_DATE|TIME_SECONDS));
json += StringFormat(" \"entry_price\": %.5f,\n", feedback.entry_price);
json += StringFormat(" \"exit_price\": %.5f,\n", feedback.exit_price);
json += StringFormat(" \"volume\": %.2f,\n", feedback.volume);
json += StringFormat(" \"pnl\": %.2f,\n", feedback.pnl);
json += StringFormat(" \"r_multiple\": %.2f,\n", feedback.r_multiple);
json += StringFormat(" \"won\": %s,\n", feedback.won ? "true" : "false");
json += StringFormat(" \"regime\": \"%s\",\n", EscapeJsonString(feedback.regime));
// Features at entry
json += " \"features\": {\n";
json += StringFormat(" \"atr_14\": %.5f,\n", feedback.atr_at_entry);
json += StringFormat(" \"rsi_14\": %.2f\n", feedback.rsi_at_entry);
json += " },\n";
// Gate confidences
json += " \"gate_confidences\": {\n";
for(int i = 0; i < 8; i++)
{
json += StringFormat(" \"G%d\": %.3f%s\n", i+1, feedback.gate_confidences[i],
(i < 7) ? "," : "");
}
json += " },\n";
// Policy slice identifier
string policy_slice = StringFormat("%s_%s_%d", feedback.strategy, feedback.symbol, feedback.timeframe);
json += StringFormat(" \"policy_slice\": \"%s\"\n", EscapeJsonString(policy_slice));
json += "}\n";
// Write to file
FileWriteString(handle, json);
FileClose(handle);
g_ml_write_count = g_ml_write_count + 1;
int mod_check = g_ml_write_count % 10;
if(mod_check == 0)
PrintFormat("[MLFeedback] Written %d feedback files", g_ml_write_count);
return true;
}
//+------------------------------------------------------------------+
//| Helper: Get current market regime |
//+------------------------------------------------------------------+
string GetCurrentMarketRegime(const string symbol, const ENUM_TIMEFRAMES timeframe)
{
// Simple regime detection using ADX
int adx_handle = iADX(symbol, timeframe, 14);
if(adx_handle == INVALID_HANDLE) return "unknown";
double adx_buffer[1], di_plus[1], di_minus[1];
bool copied = CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) > 0 &&
CopyBuffer(adx_handle, 1, 0, 1, di_plus) > 0 &&
CopyBuffer(adx_handle, 2, 0, 1, di_minus) > 0;
IndicatorRelease(adx_handle);
if(!copied) return "unknown";
double adx = adx_buffer[0];
double dip = di_plus[0];
double dim = di_minus[0];
if(adx > 25)
{
if(dip > dim)
return "trending_up";
else
return "trending_down";
}
else if(adx < 20)
{
return "ranging";
}
else
{
return "transitional";
}
}
//+------------------------------------------------------------------+
//| Helper: Calculate R-Multiple for a trade |
//+------------------------------------------------------------------+
double CalculateRMultiple(const double pnl, const double risk_amount)
{
if(risk_amount == 0) return 0.0;
return pnl / risk_amount;
}
//+------------------------------------------------------------------+
//| Initialize feedback system |
//+------------------------------------------------------------------+
void InitializeMLFeedback()
{
// Ensure directory exists (best effort)
long dummy = 0;
string path = "DualEA";
if(!FolderCreate(path, FILE_COMMON))
{
// Try alternate path
path = "\\\" + path;
}
g_ml_write_count = 0;
Print("[MLFeedback] Feedback system initialized");
}
//+------------------------------------------------------------------+
//| Shutdown feedback system |
//+------------------------------------------------------------------+
void ShutdownMLFeedback()
{
PrintFormat("[MLFeedback] Total feedback files written: %d", g_ml_write_count);
}
//+------------------------------------------------------------------+
//| Record trade closure for ML feedback |
//| Call this from OnTrade or position close handler |
//+------------------------------------------------------------------+
void RecordTradeForML(
const ulong ticket,
const string strategy,
const double entry_price,
const double exit_price,
const double volume,
const double pnl,
const double sl_distance,
const double gate_confidences[]
)
{
// Get position details
if(!PositionSelectByTicket(ticket))
return;
string symbol = PositionGetString(POSITION_SYMBOL);
datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME);
// Build feedback structure
STradeFeedback feedback;
feedback.Init();
feedback.symbol = symbol;
feedback.strategy = strategy;
feedback.timeframe = (int)_Period;
feedback.ticket = ticket;
feedback.entry_time = entry_time;
feedback.exit_time = TimeCurrent();
feedback.entry_price = entry_price;
feedback.exit_price = exit_price;
feedback.volume = volume;
feedback.pnl = pnl;
feedback.won = (pnl > 0);
// Calculate R-Multiple
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double risk_amount = sl_distance * volume * tick_value;
feedback.r_multiple = CalculateRMultiple(pnl, risk_amount);
// Get regime
feedback.regime = GetCurrentMarketRegime(symbol, _Period);
// Get features at entry (current values as proxy)
feedback.atr_at_entry = GetATR(14);
int rsi_handle = iRSI(symbol, _Period, 14, PRICE_CLOSE);
if(rsi_handle != INVALID_HANDLE)
{
double rsi_buffer[1];
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) > 0)
feedback.rsi_at_entry = rsi_buffer[0];
IndicatorRelease(rsi_handle);
}
// Copy gate confidences
for(int i = 0; i < 8 && i < ArraySize(gate_confidences); i++)
feedback.gate_confidences[i] = gate_confidences[i];
// Write to file
if(WriteTradeFeedback(feedback))
{
// Also update policy immediately
if(CheckPointer(g_learning_bridge) != POINTER_INVALID)
{
g_learning_bridge.RecordTradeOutcome(strategy, symbol, (int)_Period, feedback.won, pnl);
}
}
}
#endif // __MLFEEDBACKINTEGRATION_MQH__