// Telemetry.mqh // Buffered JSONL telemetry writer for PaperEA #property copyright "2025, Windsurf" #property link "https://windsurf.ai" #include 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