2026-02-27 21:49:28 -06:00
|
|
|
#ifndef NN_DATA_LOGGER_MQH
|
|
|
|
|
#define NN_DATA_LOGGER_MQH
|
2026-02-15 05:13:55 +00:00
|
|
|
//+------------------------------------------------------------------+
|
2026-02-27 21:49:28 -06:00
|
|
|
//| NNDataLogger.mqh |
|
|
|
|
|
//| Real-trade labeling via MT5 deal history. |
|
|
|
|
|
//| Phase 1 — NN_CaptureEntry(): snapshot features at trade open. |
|
|
|
|
|
//| Phase 2 — NN_LabelFromDeal(): write labeled row when trade closes.|
|
|
|
|
|
//| |
|
|
|
|
|
//| Replaces the old speculative 180-bar lookahead approach. |
|
2026-02-15 05:13:55 +00:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#property strict
|
|
|
|
|
|
|
|
|
|
#include <Files\File.mqh>
|
|
|
|
|
#include "NNFeatures.mqh"
|
|
|
|
|
#include "InputParams.mqh"
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
// ─── Pending-entry store ─────────────────────────────────────────────────────
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
struct NNPendingEntry
|
2026-02-15 05:13:55 +00:00
|
|
|
{
|
2026-02-27 21:49:28 -06:00
|
|
|
ulong position_id;
|
|
|
|
|
bool is_buy;
|
|
|
|
|
double entry_price;
|
|
|
|
|
datetime entry_time;
|
|
|
|
|
string setup_name;
|
2026-03-31 02:57:33 +00:00
|
|
|
string session_name;
|
|
|
|
|
string session_phase;
|
|
|
|
|
string symbol_profile;
|
2026-02-27 21:49:28 -06:00
|
|
|
double cluster_strength;
|
|
|
|
|
double conflict_score;
|
|
|
|
|
int category; // 0 = continuation, 1 = counter
|
2026-03-31 02:57:33 +00:00
|
|
|
double spread_points;
|
|
|
|
|
double free_margin;
|
|
|
|
|
double equity;
|
|
|
|
|
double balance;
|
|
|
|
|
double margin_required;
|
|
|
|
|
double risk_amount;
|
2026-02-27 21:49:28 -06:00
|
|
|
double features[12];
|
|
|
|
|
int n_features;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define NN_MAX_PENDING 60 // safely above Max_Open_Trades (20) with headroom
|
|
|
|
|
|
|
|
|
|
NNPendingEntry g_nn_pending[NN_MAX_PENDING];
|
|
|
|
|
int g_nn_pending_count = 0;
|
|
|
|
|
|
|
|
|
|
// ─── Phase 1: capture at trade open ─────────────────────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// Call this immediately after a successful Trade.Buy() / Trade.Sell().
|
|
|
|
|
// position_id — from HistoryDealGetInteger(entry_deal, DEAL_POSITION_ID)
|
|
|
|
|
// is_buy — true for long, false for short
|
|
|
|
|
// setup — reason string passed to Trade.Buy/Sell
|
|
|
|
|
// cluster_strength, conflict_score — from Coordinator globals
|
|
|
|
|
// category — 0 = continuation, 1 = counter
|
|
|
|
|
|
|
|
|
|
void NN_CaptureEntry(ulong position_id,
|
|
|
|
|
bool is_buy,
|
|
|
|
|
const string setup,
|
2026-03-31 02:57:33 +00:00
|
|
|
const double entry_price,
|
|
|
|
|
const string session_name,
|
|
|
|
|
const string session_phase,
|
|
|
|
|
const string symbol_profile,
|
2026-02-27 21:49:28 -06:00
|
|
|
double cluster_strength,
|
|
|
|
|
double conflict_score,
|
2026-03-31 02:57:33 +00:00
|
|
|
int category,
|
|
|
|
|
double spread_points,
|
|
|
|
|
double free_margin,
|
|
|
|
|
double equity,
|
|
|
|
|
double balance,
|
|
|
|
|
double margin_required,
|
|
|
|
|
double risk_amount)
|
2026-02-27 21:49:28 -06:00
|
|
|
{
|
|
|
|
|
if(!NN_LogData) return;
|
|
|
|
|
if(conflict_score > NN_MaxConflictToLog) return;
|
|
|
|
|
if(g_nn_pending_count >= NN_MAX_PENDING) return;
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
double feat[];
|
|
|
|
|
int n = BuildNNFeatures(feat);
|
|
|
|
|
if(n <= 0) return;
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
int idx = g_nn_pending_count;
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
g_nn_pending[idx].position_id = position_id;
|
|
|
|
|
g_nn_pending[idx].is_buy = is_buy;
|
2026-03-31 02:57:33 +00:00
|
|
|
g_nn_pending[idx].entry_price = entry_price;
|
2026-02-27 21:49:28 -06:00
|
|
|
g_nn_pending[idx].entry_time = TimeCurrent();
|
|
|
|
|
g_nn_pending[idx].setup_name = setup;
|
2026-03-31 02:57:33 +00:00
|
|
|
g_nn_pending[idx].session_name = session_name;
|
|
|
|
|
g_nn_pending[idx].session_phase = session_phase;
|
|
|
|
|
g_nn_pending[idx].symbol_profile = symbol_profile;
|
2026-02-27 21:49:28 -06:00
|
|
|
g_nn_pending[idx].cluster_strength = cluster_strength;
|
|
|
|
|
g_nn_pending[idx].conflict_score = conflict_score;
|
|
|
|
|
g_nn_pending[idx].category = category;
|
2026-03-31 02:57:33 +00:00
|
|
|
g_nn_pending[idx].spread_points = spread_points;
|
|
|
|
|
g_nn_pending[idx].free_margin = free_margin;
|
|
|
|
|
g_nn_pending[idx].equity = equity;
|
|
|
|
|
g_nn_pending[idx].balance = balance;
|
|
|
|
|
g_nn_pending[idx].margin_required = margin_required;
|
|
|
|
|
g_nn_pending[idx].risk_amount = risk_amount;
|
2026-02-27 21:49:28 -06:00
|
|
|
g_nn_pending[idx].n_features = MathMin(n, 12);
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
for(int i = 0; i < g_nn_pending[idx].n_features; i++)
|
|
|
|
|
g_nn_pending[idx].features[i] = feat[i];
|
|
|
|
|
|
|
|
|
|
g_nn_pending_count++;
|
2026-02-15 05:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 02:57:33 +00:00
|
|
|
void NN_LogRejectedDecision(const bool is_buy,
|
|
|
|
|
const string setup,
|
|
|
|
|
const string reason,
|
|
|
|
|
const double lots,
|
|
|
|
|
const double entry_price,
|
|
|
|
|
const string session_name,
|
|
|
|
|
const string session_phase,
|
|
|
|
|
const string symbol_profile,
|
|
|
|
|
const double cluster_strength,
|
|
|
|
|
const double conflict_score,
|
|
|
|
|
const int category,
|
|
|
|
|
const double spread_points,
|
|
|
|
|
const double free_margin,
|
|
|
|
|
const double equity,
|
|
|
|
|
const double balance,
|
|
|
|
|
const double margin_required,
|
|
|
|
|
const double risk_amount)
|
|
|
|
|
{
|
|
|
|
|
if(!NN_LogData) return;
|
|
|
|
|
|
|
|
|
|
double feat[];
|
|
|
|
|
int n = BuildNNFeatures(feat);
|
|
|
|
|
if(n <= 0) return;
|
|
|
|
|
|
|
|
|
|
int h = FileOpen(NN_LogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI);
|
|
|
|
|
if(h == INVALID_HANDLE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
FileSeek(h, 0, SEEK_END);
|
|
|
|
|
if(FileSize(h) == 0)
|
|
|
|
|
{
|
|
|
|
|
FileWrite(h,
|
|
|
|
|
"status", "entry_time", "exit_time", "symbol", "tf",
|
|
|
|
|
"session", "phase", "symbol_profile",
|
|
|
|
|
"is_buy", "category", "setup", "reason",
|
|
|
|
|
"cluster_strength", "conflict_score",
|
|
|
|
|
"entry_price", "exit_price",
|
|
|
|
|
"spread_points", "free_margin", "equity", "balance",
|
|
|
|
|
"margin_required", "risk_amount", "lots",
|
|
|
|
|
"label", "duration_min", "n_features", "features_json"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string fjson = "[";
|
|
|
|
|
for(int k = 0; k < MathMin(n, 12); k++)
|
|
|
|
|
{
|
|
|
|
|
fjson += DoubleToString(feat[k], 8);
|
|
|
|
|
if(k < MathMin(n, 12) - 1) fjson += ",";
|
|
|
|
|
}
|
|
|
|
|
fjson += "]";
|
|
|
|
|
|
|
|
|
|
FileWrite(h,
|
|
|
|
|
"rejected",
|
|
|
|
|
(string)TimeCurrent(),
|
|
|
|
|
"",
|
|
|
|
|
_Symbol,
|
|
|
|
|
(string)Period(),
|
|
|
|
|
session_name,
|
|
|
|
|
session_phase,
|
|
|
|
|
symbol_profile,
|
|
|
|
|
(is_buy ? 1 : 0),
|
|
|
|
|
category,
|
|
|
|
|
setup,
|
|
|
|
|
reason,
|
|
|
|
|
DoubleToString(cluster_strength, 2),
|
|
|
|
|
DoubleToString(conflict_score, 2),
|
|
|
|
|
DoubleToString(entry_price, _Digits),
|
|
|
|
|
"",
|
|
|
|
|
DoubleToString(spread_points, 1),
|
|
|
|
|
DoubleToString(free_margin, 2),
|
|
|
|
|
DoubleToString(equity, 2),
|
|
|
|
|
DoubleToString(balance, 2),
|
|
|
|
|
DoubleToString(margin_required, 2),
|
|
|
|
|
DoubleToString(risk_amount, 2),
|
|
|
|
|
DoubleToString(lots, 2),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
MathMin(n, 12),
|
|
|
|
|
fjson
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileClose(h);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
// ─── Phase 2: label and write at trade close ─────────────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// Call from OnTradeTransaction when entry == DEAL_ENTRY_OUT.
|
|
|
|
|
// deal_ticket must already be selected with HistoryDealSelect().
|
|
|
|
|
|
|
|
|
|
void NN_LabelFromDeal(ulong deal_ticket)
|
2026-02-15 05:13:55 +00:00
|
|
|
{
|
|
|
|
|
if(!NN_LogData) return;
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
ulong pos_id = (ulong)HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
// Find the matching pending entry
|
|
|
|
|
int idx = -1;
|
|
|
|
|
for(int i = 0; i < g_nn_pending_count; i++)
|
|
|
|
|
{
|
|
|
|
|
if(g_nn_pending[i].position_id == pos_id)
|
|
|
|
|
{
|
|
|
|
|
idx = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(idx < 0) return; // trade wasn't captured (manual trade or already labeled)
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
|
|
|
|
|
double exit_price = HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
|
|
|
|
|
datetime exit_time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
|
|
|
|
|
int dur_min = (int)((exit_time - g_nn_pending[idx].entry_time) / 60);
|
|
|
|
|
int label = (profit > 0.0) ? 1 : 0; // 1 = win, 0 = loss/breakeven
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
// Write CSV row
|
2026-02-15 05:13:55 +00:00
|
|
|
int h = FileOpen(NN_LogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI);
|
2026-02-27 21:49:28 -06:00
|
|
|
if(h != INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
FileSeek(h, 0, SEEK_END);
|
|
|
|
|
|
|
|
|
|
if(FileSize(h) == 0)
|
|
|
|
|
{
|
|
|
|
|
FileWrite(h,
|
2026-03-31 02:57:33 +00:00
|
|
|
"status", "entry_time", "exit_time", "symbol", "tf",
|
|
|
|
|
"session", "phase", "symbol_profile",
|
2026-02-27 21:49:28 -06:00
|
|
|
"is_buy", "category", "setup",
|
|
|
|
|
"cluster_strength", "conflict_score",
|
|
|
|
|
"entry_price", "exit_price",
|
2026-03-31 02:57:33 +00:00
|
|
|
"spread_points", "free_margin", "equity", "balance",
|
|
|
|
|
"margin_required", "risk_amount", "profit", "label", "duration_min",
|
2026-02-27 21:49:28 -06:00
|
|
|
"n_features", "features_json"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-02-15 05:13:55 +00:00
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
string fjson = "[";
|
|
|
|
|
for(int k = 0; k < g_nn_pending[idx].n_features; k++)
|
|
|
|
|
{
|
|
|
|
|
fjson += DoubleToString(g_nn_pending[idx].features[k], 8);
|
|
|
|
|
if(k < g_nn_pending[idx].n_features - 1) fjson += ",";
|
|
|
|
|
}
|
|
|
|
|
fjson += "]";
|
2026-02-15 05:13:55 +00:00
|
|
|
|
|
|
|
|
FileWrite(h,
|
2026-03-31 02:57:33 +00:00
|
|
|
"closed",
|
2026-02-27 21:49:28 -06:00
|
|
|
(string)g_nn_pending[idx].entry_time,
|
|
|
|
|
(string)exit_time,
|
|
|
|
|
_Symbol,
|
|
|
|
|
(string)Period(),
|
2026-03-31 02:57:33 +00:00
|
|
|
g_nn_pending[idx].session_name,
|
|
|
|
|
g_nn_pending[idx].session_phase,
|
|
|
|
|
g_nn_pending[idx].symbol_profile,
|
2026-02-27 21:49:28 -06:00
|
|
|
(g_nn_pending[idx].is_buy ? 1 : 0),
|
|
|
|
|
g_nn_pending[idx].category,
|
|
|
|
|
g_nn_pending[idx].setup_name,
|
|
|
|
|
DoubleToString(g_nn_pending[idx].cluster_strength, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].conflict_score, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].entry_price, _Digits),
|
|
|
|
|
DoubleToString(exit_price, _Digits),
|
2026-03-31 02:57:33 +00:00
|
|
|
DoubleToString(g_nn_pending[idx].spread_points, 1),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].free_margin, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].equity, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].balance, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].margin_required, 2),
|
|
|
|
|
DoubleToString(g_nn_pending[idx].risk_amount, 2),
|
2026-02-27 21:49:28 -06:00
|
|
|
DoubleToString(profit, 2),
|
|
|
|
|
label,
|
|
|
|
|
dur_min,
|
|
|
|
|
g_nn_pending[idx].n_features,
|
|
|
|
|
fjson
|
2026-02-15 05:13:55 +00:00
|
|
|
);
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
FileClose(h);
|
2026-02-15 05:13:55 +00:00
|
|
|
}
|
2026-02-27 21:49:28 -06:00
|
|
|
|
2026-03-31 02:57:33 +00:00
|
|
|
UpdateSymbolPolicyFeedback(g_nn_pending[idx].symbol_profile,
|
|
|
|
|
profit,
|
|
|
|
|
g_nn_pending[idx].spread_points,
|
|
|
|
|
(g_nn_pending[idx].equity > 0.0)
|
|
|
|
|
? ((g_nn_pending[idx].free_margin / g_nn_pending[idx].equity) * 100.0)
|
|
|
|
|
: 0.0,
|
|
|
|
|
(g_nn_pending[idx].equity > 0.0)
|
|
|
|
|
? ((g_nn_pending[idx].risk_amount / g_nn_pending[idx].equity) * 100.0)
|
|
|
|
|
: 0.0,
|
|
|
|
|
g_nn_pending[idx].category);
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
// Remove from pending array (shift remaining entries down)
|
|
|
|
|
for(int i = idx; i < g_nn_pending_count - 1; i++)
|
|
|
|
|
g_nn_pending[i] = g_nn_pending[i + 1];
|
|
|
|
|
g_nn_pending_count--;
|
2026-02-15 05:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 21:49:28 -06:00
|
|
|
#endif
|