//+------------------------------------------------------------------+ //| VtJournal.mqh | //| Copyright 2026, Allan Niñal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| The floating-drawdown journal. MT5 keeps no past floating | //| equity, so the lowest (equity - balance) of every server day is | //| recorded here as it happens: MQL5\Files\Vitals\FDD_.csv, | //| one line "day,money,percent" per day (day = server seconds / | //| 86400; percent = of the balance at that instant). | //| - Sample() is called on every timer tick and every OnCalculate;| //| the first sample of a day marks it as covered (0). | //| - The file is rewritten when the day's low deepened and the | //| last write is 10 s old, and by Flush(true) at deinit. A | //| crash loses at most the last 10 s of depth, never a day. | //| - Every write first merges with what the file holds (another | //| chart of the same account), keeping the deeper low per day. | //| Money is kept to 2 decimals and percent to 4, so memory and file | //| always agree. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef VTJOURNAL_MQH #define VTJOURNAL_MQH #define VT_JMAX 4000 // days kept #define VT_JFLUSH_MS 10000 class CVtJournal { public: long d[]; // sorted ascending, unique double m[],p[]; // lowest floating (<= 0) and its percent int n; string file; bool dirty; ulong last_ms; int errors; CVtJournal(): n(0),dirty(false),last_ms(0),errors(0) {} void Init(const string path,const ulong now_ms) { file=path; n=0; dirty=false; errors=0; last_ms=now_ms; ArrayResize(d,0,512); ArrayResize(m,0,512); ArrayResize(p,0,512); long fd[]; double fm[],fp[]; int fn=ReadFile(fd,fm,fp); for(int i=0; ilo; i--) { d[i]=d[i-1]; m[i]=m[i-1]; p[i]=p[i-1]; } d[lo]=day; m[lo]=money; p[lo]=pct; n++; return(true); } void Sample(const long server_now,const double equity,const double balance,const ulong now_ms) { long day=server_now/86400; double money=NormalizeDouble(MathMin(equity-balance,0.0),2); double pct=NormalizeDouble(balance>0 ? money/balance*100.0 : 0.0,4); if(Put(day,money,pct)) dirty=true; if(dirty && now_ms-last_ms>=VT_JFLUSH_MS) Flush(false,now_ms); } int ReadFile(long &fd[],double &fm[],double &fp[]) { ArrayResize(fd,0); ArrayResize(fm,0); ArrayResize(fp,0); int f=FileOpen(file,FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE); if(f==INVALID_HANDLE) return(0); int k=0; while(!FileIsEnding(f)) { string s=FileReadString(f); string v[]; if(StringSplit(s,',',v)<3) continue; long day=StringToInteger(v[0]); if(day<=0) continue; ArrayResize(fd,k+1,512); ArrayResize(fm,k+1,512); ArrayResize(fp,k+1,512); fd[k]=day; fm[k]=MathMin(StringToDouble(v[1]),0.0); fp[k]=MathMin(StringToDouble(v[2]),0.0); k++; } FileClose(f); return(k); } //--- merge with the file, then rewrite it; keeps the data dirty when the file could not be written bool Flush(const bool force,const ulong now_ms) { if(!dirty) return(true); if(!force && now_ms-last_ms