2025-08-13 19:40:28 -04:00 | | | // Telemetry.mqh
|
| | | // Buffered JSONL telemetry writer for PaperEA
|
2025-09-10 13:27:03 -04:00 | | | #ifndef __DUALEA_TELEMETRY_MQH__
|
| | | #define __DUALEA_TELEMETRY_MQH__
|
2025-08-13 19:40:28 -04:00 | | | #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:
|
2026-02-04 14:28:59 -05:00 | | | string JsonQuote(const string s) { return Quote(s); }
|
| | |
|
2025-08-13 19:40:28 -04:00 | | | 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);
|
| | | }
|
| | |
|
2026-02-04 14:28:59 -05:00 | | | void LogEventJson(const string symbol, const int timeframe, const string strategy, const string event, const string json_fields)
|
| | | {
|
| | | if(m_level<=0) return;
|
| | | string fields = json_fields;
|
| | | StringTrimLeft(fields);
|
| | | StringTrimRight(fields);
|
| | | string suffix = (StringLen(fields) > 0 ? ("," + fields) : "");
|
| | | string line = StringFormat("{\"ts\":%s,\"symbol\":%s,\"timeframe\":%d,\"strategy\":%s,\"event\":%s%s}",
|
| | | Quote(NowTs()), Quote(symbol), timeframe, Quote(strategy), Quote(event), suffix);
|
| | | AppendLine(line);
|
| | | }
|
| | |
|
2025-08-13 19:40:28 -04:00 | | | // 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);
|
| | | }
|
| | | };
|
2025-09-10 13:27:03 -04:00 | | |
|
| | | #endif // __DUALEA_TELEMETRY_MQH__
|