#property strict #include "../Include/UnifiedTradeLogger.mqh" int checks = 0; int failures = 0; void Check(bool passed, string label) { checks++; if(!passed) { failures++; Print("[LOGGER FAIL] ", label); } } string ReadLog(string path) { int file = FileOpen(path, FILE_READ|FILE_TXT|FILE_ANSI|DUALEA_FILE_COMMON, 0, CP_UTF8); if(file == INVALID_HANDLE) return ""; string result = ""; while(!FileIsEnding(file)) result += FileReadString(file) + "\n"; FileClose(file); return result; } int Occurrences(string text, string token) { int count=0, pos=0; while((pos=StringFind(text, token, pos)) >= 0) { count++; pos+=StringLen(token); } return count; } bool BalancedJSON(string text) { bool quoted=false, escaped=false; string stack=""; for(int i=0; i1 && StringGetCharacter(text,0)==123 && StringGetCharacter(text,StringLen(text)-1)==125; } int OnInit() { if(!MQLInfoInteger(MQL_TESTER)) return INIT_FAILED; // Each run uses its own tester-local directory, never the live trade log. string directory="DualEA\\logger_contract_"+IntegerToString((long)GetTickCount64()); CUnifiedTradeLogger logger(directory); UnifiedTradeRecord record; Check(record.journey_length == 0, "new record has an empty journey"); Check(record.blocking_gate == -1, "new record has no blocking gate"); Check(record.reviews_performed == 0 && !record.is_closed, "new lifecycle state is initialized"); string json = logger.FormatTradeAsJSON(record, false); Check(StringFind(json, "\"gate\":") < 0, "empty journey formats without indexing entries"); for(int i=0; i= 0 && StringFind(json, "test_1") < 0, "one-step journey stays one step"); record.journey_length = 2147483647; json = logger.FormatTradeAsJSON(record, false); Check(StringFind(json, "test_23") >= 0, "oversized journey clamps to fixed capacity"); record.journey_length = -1; json = logger.FormatTradeAsJSON(record, true); Check(StringFind(json, "\"gate\":") < 0, "negative journey formats as empty"); Check(StringFind(json, "\"outcome\":") >= 0, "close formatting remains available"); MqlDateTime dt; TimeToStruct(TimeCurrent(),dt); string path=directory+"\\trades_"+StringFormat("%04d%02d%02d",dt.year,dt.mon,dt.day)+".json"; record.journey_length=1; record.trade_id="trade-a"; record.execution_time=TimeCurrent(); record.strategy_name="quoted \"strategy\" with \\ slash\nbrace }"; logger.LogTradeExecution(record); string first=ReadLog(path); Check(BalancedJSON(first) && Occurrences(first,"\"trade_id\":")==1, "first write is a complete document"); record.trade_id="trade-b"; logger.LogTradeExecution(record); string second=ReadLog(path); Check(BalancedJSON(second) && Occurrences(second,"\"trade_id\":")==2, "second append preserves both records and balanced arrays"); Check(Occurrences(second,"\"status\": \"open\"")==2, "both executions are open"); Check(StringFind(second,"quoted \\\"strategy\\\" with \\\\ slash\\nbrace }")>=0, "string quotes, slashes and newlines are escaped"); record.trade_id="trade-a"; record.close_time=TimeCurrent(); record.profit_absolute=12.5; logger.LogTradeClose(record); string closed=ReadLog(path); Check(BalancedJSON(closed) && Occurrences(closed,"\"trade_id\":")==2, "closing preserves two complete records"); Check(Occurrences(closed,"\"status\": \"closed\"")==1 && Occurrences(closed,"\"status\": \"open\"")==1, "only matching trade is closed"); Check(Occurrences(closed,"\"outcome\":")==1 && StringFind(closed,"\"profit_absolute\": 12.50")>=0, "close outcome persisted"); int logged, total_closed; logger.GetStatistics(logged,total_closed); Check(logged==2 && total_closed==1, "statistics reflect committed writes"); record.trade_id="missing"; logger.LogTradeClose(record); Check(ReadLog(path)==closed, "unknown close leaves log unchanged"); Print("[LOGGER ARTIFACT] ",path); PrintFormat("[LOGGER-CONTRACT] RESULT checks=%d failures=%d", checks, failures); return failures == 0 ? INIT_SUCCEEDED : INIT_FAILED; } void OnTick() { ExpertRemove(); } double OnTester() { return failures == 0 ? checks : -failures; }