2
0
포크 0
VizionAI-Trading-EA/NNDataLogger.mqh

165 lines
5.8 KiB
MQL5

2026-03-06 18:00:10 +00:00
#ifndef NN_DATA_LOGGER_MQH
#define NN_DATA_LOGGER_MQH
//+------------------------------------------------------------------+
//| 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. |
//+------------------------------------------------------------------+
#property strict
#include <Files\File.mqh>
#include "NNFeatures.mqh"
#include "InputParams.mqh"
// ─── Pending-entry store ─────────────────────────────────────────────────────
struct NNPendingEntry
{
ulong position_id;
bool is_buy;
double entry_price;
datetime entry_time;
string setup_name;
double cluster_strength;
double conflict_score;
int category; // 0 = continuation, 1 = counter
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,
double cluster_strength,
double conflict_score,
int category)
{
if(!NN_LogData) return;
if(conflict_score > NN_MaxConflictToLog) return;
if(g_nn_pending_count >= NN_MAX_PENDING) return;
double feat[];
int n = BuildNNFeatures(feat);
if(n <= 0) return;
int idx = g_nn_pending_count;
g_nn_pending[idx].position_id = position_id;
g_nn_pending[idx].is_buy = is_buy;
g_nn_pending[idx].entry_price = is_buy
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
g_nn_pending[idx].entry_time = TimeCurrent();
g_nn_pending[idx].setup_name = setup;
g_nn_pending[idx].cluster_strength = cluster_strength;
g_nn_pending[idx].conflict_score = conflict_score;
g_nn_pending[idx].category = category;
g_nn_pending[idx].n_features = MathMin(n, 12);
for(int i = 0; i < g_nn_pending[idx].n_features; i++)
g_nn_pending[idx].features[i] = feat[i];
g_nn_pending_count++;
}
// ─── 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)
{
if(!NN_LogData) return;
ulong pos_id = (ulong)HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
// 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)
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
// Write CSV row
int h = FileOpen(NN_LogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI);
if(h != INVALID_HANDLE)
{
FileSeek(h, 0, SEEK_END);
if(FileSize(h) == 0)
{
FileWrite(h,
"entry_time", "exit_time", "symbol", "tf",
"is_buy", "category", "setup",
"cluster_strength", "conflict_score",
"entry_price", "exit_price",
"profit", "label", "duration_min",
"n_features", "features_json"
);
}
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 += "]";
FileWrite(h,
(string)g_nn_pending[idx].entry_time,
(string)exit_time,
_Symbol,
(string)Period(),
(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),
DoubleToString(profit, 2),
label,
dur_min,
g_nn_pending[idx].n_features,
fjson
);
FileClose(h);
}
// 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--;
}
#endif