294 lines
10 KiB
MQL5
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__
|