//+------------------------------------------------------------------+ //| HealthLive.mqh | //| Copyright 2026, Allan NiƱal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| The live side of EA Health Monitor: the open positions per magic | //| and the names: the user's list "magic=name,...", else the comment| //| of the EA's first opening deal, "Manual" for magic 0. | //| MT4 edition: MT4 has no deal list to read from where it stopped, | //| so the book is rebuilt from the orders (HealthCore.mqh) whenever | //| they change (a trade opened, closed or partly closed); the open | //| trades' floating includes their commission (MT4 books it at the | //| close); the server clock keeps running while no ticks arrive. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef HEALTHLIVE_MQH #define HEALTHLIVE_MQH #include "HealthCore.mqh" struct EHLive { EHBook b; string sig; // the orders the book was built from (history count, open tickets and lots) long since,day; long map_magic[]; string map_name[]; }; void EHLiveInit(EHLive &L,const string names) { EHInit(L.b); L.sig=""; L.since=-1; L.day=-1; ArrayResize(L.map_magic,0); ArrayResize(L.map_name,0); string items[]; int n=StringSplit(names,',',items); for(int i=0; i=-12*3600 && r<=14*3600) off=r; return(off==LONG_MIN ? TimeCurrent() : (datetime)((long)TimeGMT()+off)); } //--- server midnight of today long EHDayStart() { MqlDateTime t; TimeToStruct(EHServerNow(),t); t.hour=0; t.min=0; t.sec=0; return((long)StructToTime(t)); } //--- bring the book up to date; true when anything changed bool EHRefresh(EHLive &L,const long since) { long day=EHDayStart(); string sig=IntegerToString(OrdersHistoryTotal())+"|"; for(int i=OrdersTotal()-1; i>=0; i--) if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) && OrderType()<=OP_SELL) sig+=IntegerToString(OrderTicket())+":"+DoubleToString(OrderLots(),2)+","; bool restart=(sig!=L.sig || day!=L.day || since!=L.since); if(restart) { EHInit(L.b); L.sig=sig; L.day=day; L.since=since; EHDeal dl[]; string nm[]; int n=EHLoadHistory(dl,nm); for(int i=0; i=0; i--) { if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES) || OrderType()>OP_SELL) continue; int k=EHStatIdx(L.b,OrderMagicNumber()); L.b.st[k].open++; L.b.st[k].floating+=OrderProfit()+OrderSwap()+OrderCommission(); } return(chg); } string EHName(const EHLive &L,const EHStat &s) { string m=EHMapped(L,s.magic); if(m!="") return(m); if(s.magic==0) return("Manual"); string c=s.name; if(StringLen(c)>14) c=StringSubstr(c,0,14); return(c=="" ? "#"+IntegerToString(s.magic) : c+" #"+IntegerToString(s.magic)); } //--- seconds since a deal as "45m", "3h", "2d" (short, so the panel stays narrow) string EHAgo(const long t) { if(t<=0) return("-"); long s=MathMax(0,(long)EHServerNow()-t); if(s<3600) return(IntegerToString(s/60)+"m"); if(s<86400*2) return(IntegerToString(s/3600)+"h"); return(IntegerToString(s/86400)+"d"); } //--- 1234.5 -> "1,234.50", with a sign when asked string EHMoney(const double v,const bool sign=false) { string s=DoubleToString(MathAbs(v),2); int dot=StringFind(s,"."); string ip=StringSubstr(s,0,dot),out=""; for(int i=StringLen(ip)-1,k=0; i>=0; i--,k++) out=(k>0 && k%3==0 ? StringSubstr(ip,i,1)+"," : StringSubstr(ip,i,1))+out; string r=out+StringSubstr(s,dot); if(v<0 && MathAbs(v)>=0.005) return("-"+r); return((sign && v>=0.005 ? "+" : "")+r); } #endif //+------------------------------------------------------------------+