| #include "RuntimeStorage.mqh"
|
| //+------------------------------------------------------------------+
|
| //| 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|DUALEA_FILE_COMMON|FILE_UNICODE);
|
| if(handle == INVALID_HANDLE)
|
| {
|
| // Try without DUALEA_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, DUALEA_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__
|