2026-10-28 00:30:38 -04:00 | | | #include "RuntimeStorage.mqh"
|
2026-02-24 12:47:37 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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;
|
| | |
|
2026-10-28 00:30:38 -04:00 | | | int handle = FileOpen(filepath, FILE_WRITE|FILE_TXT|DUALEA_FILE_COMMON|FILE_UNICODE);
|
2026-02-24 12:47:37 -05:00 | | | if(handle == INVALID_HANDLE)
|
| | | {
|
2026-10-28 00:30:38 -04:00 | | | // Try without DUALEA_FILE_COMMON
|
2026-02-24 12:47:37 -05:00 | | | 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";
|
2026-10-28 00:30:38 -04:00 | | | if(!FolderCreate(path, DUALEA_FILE_COMMON))
|
2026-02-24 12:47:37 -05:00 | | | {
|
| | | // 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__
|