ea-health-monitor/mql4/Indicators/EAHealth/HealthLive.mqh
Allan Niñal Allan Niñal 286e60f707 Free source regenerated: Pro-only code removed from the engine, debug log added
Generated by the publish tool from the private source; all publish gates pass, compile 0 errors 0 warnings on MT5 and MT4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:12:10 +08:00
170 lines
5.5 KiB
MQL5
//+------------------------------------------------------------------+
//| 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<n; i++)
{
string kv[];
if(StringSplit(items[i],'=',kv)!=2)
continue;
StringTrimLeft(kv[0]); StringTrimRight(kv[0]); StringTrimLeft(kv[1]); StringTrimRight(kv[1]);
int k=ArraySize(L.map_magic);
ArrayResize(L.map_magic,k+1);
ArrayResize(L.map_name,k+1);
L.map_magic[k]=StringToInteger(kv[0]);
L.map_name[k]=kv[1];
}
}
string EHMapped(const EHLive &L,const long magic)
{
for(int i=0; i<ArraySize(L.map_magic); i++)
if(L.map_magic[i]==magic)
return(L.map_name[i]);
return("");
}
//--- the server's time now. MQL4 has no TimeTradeServer and TimeCurrent stops between ticks (all weekend): the offset of
//--- the server from GMT is learnt from a fresh tick (a whole half hour within -12 h .. +14 h) and kept
datetime EHServerNow()
{
static long off=LONG_MIN;
if(IsTesting())
return(TimeCurrent());
long d=(long)TimeCurrent()-(long)TimeGMT();
long r=(long)MathRound(d/1800.0)*1800;
if(MathAbs(d-r)<=120 && r>=-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<n; i++)
{
bool closed;
double tnet;
EHAdd(L.b,dl[i],since,day,closed,tnet);
if(dl[i].entry==EH_IN && dl[i].magic!=0 && nm[i]!="")
{
int k=EHStatIdx(L.b,dl[i].magic);
if(L.b.st[k].name=="") // the first comment names it (comments that change per trade do not)
L.b.st[k].name=nm[i];
}
}
}
bool chg=restart;
//--- open positions
for(int i=0; i<L.b.nst; i++)
{ L.b.st[i].open=0; L.b.st[i].floating=0; }
for(int i=OrdersTotal()-1; 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
//+------------------------------------------------------------------+