//+------------------------------------------------------------------+ //| Dump4.mqh | //| Copyright 2026, Allan NiƱal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| MT4 test harness for an indicator, run as an EA in the Strategy | //| Tester (MT4 iCustom takes its inputs at compile time, so each | //| product has a small EA that defines IndValue(buf,shift) with its | //| own iCustom call BEFORE including this file). | //| During the test: on every new bar, records which signal buffers | //| are set on the bar that just closed; on every tick, counts | //| signals drawn on the live bar (must stay 0). | //| At the end (tester/files): | //| __.csv time,open,high,low,close,b... | //| for every bar (empty = no value) | //| repaint___.csv closed bars checked, signals | //| seen live, mismatches with the | //| final values, live-bar signals | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #property strict //--- a test EA may #define DUMP4_BUFFERS / DUMP4_PROBE before the include (a .set value cannot hold the commas) #ifndef DUMP4_BUFFERS #define DUMP4_BUFFERS "0,2,3" #endif #ifndef DUMP4_PROBE #define DUMP4_PROBE "2,3" #endif input string InpTag = "default"; // Tag for the output file names input string InpBuffers = DUMP4_BUFFERS; // Buffers to dump input string InpProbe = DUMP4_PROBE; // Signal buffers checked for repainting int g_bufs[],g_probe[]; datetime g_bar=0; datetime g_rec_t[]; int g_rec_i[]; // the bar's position counted from the oldest bar (stable as bars are added) int g_rec_f[]; int g_rec_n=0,g_live0=0; //--- DUMP4_LIVE (define before the include): for an indicator that calculates only its last N bars. MT4 runs the //--- indicator through a fresh full calculation when the test ends, so the final values hold only the last N bars. //--- Live mode stores every bar's values from the running instance when the bar closes, re-checks the last //--- DUMP4_RECHECK closed bars on every new bar (a repaint after the close shows there), and dumps the stored values. #ifndef DUMP4_RECHECK #define DUMP4_RECHECK 100 #endif //--- DUMP4_LATE "": buffers the indicator may still write up to DUMP4_RECHECK bars into the past (a swing //--- pivot drawn on an older bar once the swing turns); live mode re-stores them for the last DUMP4_RECHECK bars. double g_lv[][16]; // stored values per bar position (oldest = 0), buffers in InpBuffers order int g_lf[]; // probe flags stored at the close bool g_lset[],g_lbad[]; int g_ln=0; // positions allocated void StoreBar(const int s) { int pos=Bars-1-s; if(pos>=g_ln) { g_ln=pos+1; ArrayResize(g_lv,g_ln,100000); ArrayResize(g_lf,g_ln,100000); ArrayResize(g_lset,g_ln,100000); ArrayResize(g_lbad,g_ln,100000); } for(int k=0; k=1; s--) StoreBar(s); // the bars already closed at the first new bar else StoreBar(1); #ifdef DUMP4_LATE int late[]; ParseInts(DUMP4_LATE,late); for(int s=2; s<=DUMP4_RECHECK && s=0; s--) { string row=TimeToString(Time[s],TIME_DATE|TIME_MINUTES)+","+DoubleToString(Open[s],_Digits)+","+ DoubleToString(High[s],_Digits)+","+DoubleToString(Low[s],_Digits)+","+DoubleToString(Close[s],_Digits); for(int k=0; k=Bars || Time[s]!=g_rec_t[r]) { lost++; // the bar could not be found again: counts as a failure continue; } if(g_rec_f[r]!=0) sig++; if(Flags(s)!=g_rec_f[r]) bad++; } #endif h=FileOpen("repaint_"+name,FILE_WRITE|FILE_TXT|FILE_ANSI); if(h!=INVALID_HANDLE) { FileWriteString(h,StringFormat("closed_bars_checked,%d\nsignals_seen_live,%d\nmismatches,%d\nlive_bar_signals,%d\nbars,%d\nnot_found,%d\n", g_rec_n-lost,sig,bad,g_live0,Bars,lost)); FileClose(h); } PrintFormat("Dump4 %s: %d bars dumped; %d closed bars checked (%d not found), %d signals, %d mismatches, %d live-bar signals", name,Bars,g_rec_n-lost,lost,sig,bad,g_live0); } double OnTester() { Finish(); return(0.0); } void OnDeinit(const int reason) { Finish(); } //+------------------------------------------------------------------+