//+------------------------------------------------------------------+ //| VtLive.mqh | //| Copyright 2026, Allan Niñal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| The terminal side of Vitals: account figures, open positions | //| (risk at the stops, exposure value) and the deal history, read | //| incrementally into CVtEngine. Read-only: no order function is | //| ever called (OrderCalcProfit only calculates). | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef VTLIVE_MQH #define VTLIVE_MQH #include "VtCore.mqh" #include "VtExposure.mqh" #define VT_SO_PERCENT ACCOUNT_STOPOUT_MODE_PERCENT struct VtAcct { long login,leverage; string name,server,ccy,mode; double balance,equity,margin,free,level,so,profit,credit; long so_mode; // ACCOUNT_STOPOUT_MODE_PERCENT / _MONEY }; void VtReadAccount(VtAcct &a) { a.login=AccountInfoInteger(ACCOUNT_LOGIN); a.leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); a.name=AccountInfoString(ACCOUNT_NAME); a.server=AccountInfoString(ACCOUNT_SERVER); a.ccy=AccountInfoString(ACCOUNT_CURRENCY); long m=AccountInfoInteger(ACCOUNT_MARGIN_MODE); a.mode=(m==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? "hedging" : (m==ACCOUNT_MARGIN_MODE_RETAIL_NETTING ? "netting" : "exchange")); a.balance=AccountInfoDouble(ACCOUNT_BALANCE); a.equity=AccountInfoDouble(ACCOUNT_EQUITY); a.margin=AccountInfoDouble(ACCOUNT_MARGIN); a.free=AccountInfoDouble(ACCOUNT_MARGIN_FREE); a.level=AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); a.so=AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); a.so_mode=AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); a.profit=AccountInfoDouble(ACCOUNT_PROFIT); a.credit=AccountInfoDouble(ACCOUNT_CREDIT); } //--- rate profit currency -> account currency from a cross symbol in the terminal (suffix of the position's symbol //--- tried first); returns 0 when there is none double VtCrossRate(const string ccy,const string acct,const string suffix) { if(ccy==acct) return(1.0); string a=ccy+acct+suffix,b=acct+ccy+suffix,c=ccy+acct,d=acct+ccy; string cand[4]={a,b,c,d}; for(int i=0; i<4; i++) { if(!SymbolInfoInteger(cand[i],SYMBOL_EXIST)) continue; double bid=SymbolInfoDouble(cand[i],SYMBOL_BID); if(bid<=0) continue; return(i%2==0 ? bid : 1.0/bid); } return(0.0); } //--- the open positions as engine input. The notional value in account money is volume x contract x price x rate of //--- the profit currency (cross symbol; else the terminal's own tick value). Risk = OrderCalcProfit at the stop. int VtReadPositions(VtPos &out[],const string acct_ccy) { int total=PositionsTotal(),k=0; ArrayResize(out,total); for(int i=0; i0) pips=(pc-po)/point/mult; out[k].pips=(type==POSITION_TYPE_BUY ? pips : -pips); out[k].risk=0; if(sl>0) { double r=0; if(OrderCalcProfit(type==POSITION_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,sym,vol,po,sl,r)) out[k].risk=r; } out[k].base=SymbolInfoString(sym,SYMBOL_CURRENCY_BASE); out[k].ccy=SymbolInfoString(sym,SYMBOL_CURRENCY_PROFIT); string suffix=(StringLen(sym)>6 ? StringSubstr(sym,6) : ""); double rate=VtCrossRate(out[k].ccy,acct_ccy,suffix); double contract=SymbolInfoDouble(sym,SYMBOL_TRADE_CONTRACT_SIZE); if(rate<=0) // no cross symbol: the terminal's own conversion { double tv=SymbolInfoDouble(sym,SYMBOL_TRADE_TICK_VALUE),ts=SymbolInfoDouble(sym,SYMBOL_TRADE_TICK_SIZE); rate=(contract>0 && ts>0 ? tv/ts/contract : 0.0); } out[k].value=vol*contract*pc*rate; if(out[k].base=="") out[k].base=sym; if(out[k].ccy=="") out[k].ccy=sym; k++; } ArrayResize(out,k); return(k); } //--- the deal history, read incrementally into the engine class CVtHistory { public: long last_time; ulong last_ticket; int ndeals; bool want_risk; long login; bool record; // tests only: keep every deal that went into the engine VtDeal rec[]; int nrec; CVtHistory(): last_time(0),last_ticket(0),ndeals(0),want_risk(false),login(0),record(false),nrec(0) {} void Forget() { last_time=0; last_ticket=0; ndeals=0; } //--- read deals [first..] of the current selection into the engine int Process(CVtEngine &e,const int first) { int n=HistoryDealsTotal(),added=0; for(int i=first; i0) { double r=0; if(OrderCalcProfit(ty==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,d.symbol,d.volume,d.price,d.sl,r)) d.risk=MathMax(0.0,-r); } } else d.type=(ty==DEAL_TYPE_CREDIT ? VT_T_CREDIT : VT_T_CASH); // balance, charges, corrections ... = cash flow if(record) { ArrayResize(rec,nrec+1,4096); rec[nrec++]=d; } e.Add(d); ndeals++; added++; } return(added); } //--- full rebuild bool Rebuild(CVtEngine &e) { e.Reset(); Forget(); if(!HistorySelect(0,(datetime)4102444800)) return(false); Process(e,0); return(true); } //--- new deals since the last call; rebuilds when the history was rewritten. true when the engine changed. bool Refresh(CVtEngine &e,bool &rebuilt) { rebuilt=false; long lg=AccountInfoInteger(ACCOUNT_LOGIN); if(last_ticket==0 || lg!=login) { login=lg; rebuilt=true; Rebuild(e); return(true); } if(!HistorySelect((datetime)last_time,(datetime)4102444800)) return(false); int n=HistoryDealsTotal(); if(n==0) { rebuilt=true; Rebuild(e); return(true); } int at=-1; for(int i=0; ilast_time) break; } if(at<0) { rebuilt=true; Rebuild(e); return(true); } if(at==n-1) return(false); return(Process(e,at+1)>0); } }; #endif //+------------------------------------------------------------------+