forked from Princeec13/mql5
139 lines
5.6 KiB
MQL5
139 lines
5.6 KiB
MQL5
// Telemetry.mqh
|
|
// Buffered JSONL telemetry writer for PaperEA
|
|
#property copyright "2025, Windsurf"
|
|
#property link "https://windsurf.ai"
|
|
|
|
#include <Files/File.mqh>
|
|
|
|
class CTelemetry
|
|
{
|
|
private:
|
|
string m_base_dir; // e.g. "DualEA\\telemetry"
|
|
string m_experiment; // optional tag
|
|
int m_level; // 0..2
|
|
int m_max_buf; // flush threshold
|
|
string m_buffer[]; // pending JSONL lines (without trailing \n)
|
|
|
|
string JsonEscape(const string s)
|
|
{
|
|
string out = s;
|
|
StringReplace(out, "\\", "\\\\");
|
|
StringReplace(out, "\"", "\\\"");
|
|
StringReplace(out, "\r", "\\r");
|
|
StringReplace(out, "\n", "\\n");
|
|
return out;
|
|
}
|
|
|
|
string Quote(const string s) { return "\"" + JsonEscape(s) + "\""; }
|
|
|
|
string TodaySuffix()
|
|
{
|
|
datetime now = TimeCurrent();
|
|
string d = TimeToString(now, TIME_DATE);
|
|
// d format: YYYY.MM.DD -> YYYYMMDD
|
|
StringReplace(d, ".", "");
|
|
return d; // YYYYMMDD
|
|
}
|
|
|
|
string ResolveFilePath()
|
|
{
|
|
string prefix = (m_experiment==""? "paper" : ("paper_" + m_experiment));
|
|
string file = m_base_dir + "\\" + prefix + "_" + TodaySuffix() + ".jsonl";
|
|
return file;
|
|
}
|
|
|
|
void EnsureFolders()
|
|
{
|
|
// Ensure common folder structure exists
|
|
FolderCreate("DualEA", FILE_COMMON);
|
|
FolderCreate(m_base_dir, FILE_COMMON);
|
|
}
|
|
|
|
void AppendLine(const string line)
|
|
{
|
|
int n = ArraySize(m_buffer);
|
|
ArrayResize(m_buffer, n+1);
|
|
m_buffer[n] = line;
|
|
if(n+1 >= m_max_buf)
|
|
Flush();
|
|
}
|
|
|
|
string NowTs() { return TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS); }
|
|
|
|
public:
|
|
CTelemetry(string base_dir="DualEA\\telemetry", string experiment="", int level=1, int max_buf=256)
|
|
{
|
|
m_base_dir = base_dir;
|
|
m_experiment = experiment;
|
|
m_level = level;
|
|
m_max_buf = (max_buf>0? max_buf : 64);
|
|
ArrayResize(m_buffer, 0);
|
|
EnsureFolders();
|
|
string full = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files\\" + ResolveFilePath();
|
|
PrintFormat("Telemetry: writing JSONL to %s (level=%d, max_buf=%d)", full, m_level, m_max_buf);
|
|
}
|
|
|
|
~CTelemetry(){ Flush(); }
|
|
|
|
void Flush()
|
|
{
|
|
if(ArraySize(m_buffer)==0) return;
|
|
EnsureFolders();
|
|
string path = ResolveFilePath();
|
|
int h = FileOpen(path, FILE_READ|FILE_WRITE|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI|FILE_COMMON);
|
|
if(h==INVALID_HANDLE)
|
|
{
|
|
PrintFormat("Telemetry: cannot open %s, err=%d", path, GetLastError());
|
|
return;
|
|
}
|
|
FileSeek(h, 0, SEEK_END);
|
|
for(int i=0;i<ArraySize(m_buffer);++i)
|
|
FileWriteString(h, m_buffer[i] + "\n");
|
|
FileClose(h);
|
|
ArrayResize(m_buffer, 0);
|
|
}
|
|
|
|
// Generic event logger
|
|
void LogEvent(const string symbol, const int timeframe, const string strategy, const string event, const string details)
|
|
{
|
|
if(m_level<=0) return;
|
|
string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s,\"details\":%s}",
|
|
Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote(event), Quote(details));
|
|
AppendLine(line);
|
|
}
|
|
|
|
// Gating decision (selector/insights/shadow)
|
|
void LogGating(const string symbol, const int timeframe, const string strategy, const string stage, const bool allowed, const string reason)
|
|
{
|
|
if(m_level<=0) return;
|
|
string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s,\"stage\":%s,\"allowed\":%s,\"reason\":%s}",
|
|
Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote("gating"), Quote(stage), (allowed?"true":"false"), Quote(reason));
|
|
AppendLine(line);
|
|
}
|
|
|
|
// Shadow gating (evaluated even when NoConstraintsMode is active)
|
|
void LogGatingShadow(const string strategy, const string symbol, const int timeframe, const string stage, const bool allowed, const string reason, const bool no_constraints_active)
|
|
{
|
|
if(m_level<=0) return;
|
|
string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s,\"stage\":%s,\"mode\":%s,\"allowed\":%s,\"reason\":%s}",
|
|
Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote("shadow_gating"), Quote(stage), Quote(no_constraints_active?"no_constraints":"normal"), (allowed?"true":"false"), Quote(reason));
|
|
AppendLine(line);
|
|
}
|
|
|
|
// Trade execution event
|
|
void LogTradeExecuted(const string strategy, const string symbol, const int timeframe, const int retcode, const ulong deal, const ulong order_id)
|
|
{
|
|
if(m_level<=0) return;
|
|
string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s,\"retcode\":%d,\"deal\":%I64u,\"order\":%I64u}",
|
|
Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote("trade_executed"), retcode, deal, order_id);
|
|
AppendLine(line);
|
|
}
|
|
|
|
void LogTradeFailed(const string strategy, const string symbol, const int timeframe, const int error_code)
|
|
{
|
|
if(m_level<=0) return;
|
|
string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s,\"error\":%d}",
|
|
Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote("trade_failed"), error_code);
|
|
AppendLine(line);
|
|
}
|
|
};
|