//+------------------------------------------------------------------+ //| VtViews.mqh | //| Copyright 2026, Allan Niñal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| The model and the three tabs: Overview, History and Stats. Each | //| Draw* function paints into the card from the body top and | //| returns the y (logical units) where it ended. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef VTVIEWS_MQH #define VTVIEWS_MQH #include "VtCore.mqh" #include "VtExposure.mqh" #include "VtJournal.mqh" #include "VtLive.mqh" #include "VtCard.mqh" #define VT_BODY_X 12 #define VT_BODY_R 448 #define VT_BODY_TOP 64 //--- the model the views read CVtEngine g_eng; CVtJournal g_jr; CVtCard g_card; VtAcct g_acct; VtPos g_pos[]; int g_npos=0; long g_server_now=0; long g_today_low_time=0; // when today's floating low was set (memory only) string g_filter_text=""; bool g_demo=false; // synthetic data (tests and screenshots): account text is made up int g_cal_y=0,g_cal_m=0; // calendar month string g_note=""; // one-line message (export result ...) //--- formatting ----------------------------------------------------------------------------------------------------- string VtMinus() { return(ShortToString(0x2212)); } string VtGroup(const string s) // 1234567.89 -> 1,234,567.89 { int dot=StringFind(s,"."); string ip=(dot>=0 ? StringSubstr(s,0,dot) : s),fp=(dot>=0 ? StringSubstr(s,dot) : ""); string o=""; int n=StringLen(ip); for(int i=0; i0 && left%3==0) o+=","; } return(o+fp); } string VtNum(const double v,const int d=2) { string s=VtGroup(DoubleToString(MathAbs(v),d)); return(v<0 && StringToDouble(DoubleToString(MathAbs(v),d))>0 ? VtMinus()+s : s); } string VtSgn(const double v,const int d=2) { double r=StringToDouble(DoubleToString(MathAbs(v),d)); if(r==0) return(VtGroup(DoubleToString(0,d))); return((v<0 ? VtMinus() : "+")+VtGroup(DoubleToString(MathAbs(v),d))); } color VtSignClr(const double v,const int d=2) { double r=StringToDouble(DoubleToString(MathAbs(v),d)); return(r==0 ? g_card.pal.dim : (v>0 ? g_card.pal.pos : g_card.pal.neg)); } string VtWd3(const long day) { string n[7]={"Mon","Tue","Wed","Thu","Fri","Sat","Sun"}; return(n[VtWeekday(day)]); } string VtMon3(const int m) { string n[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; return(n[(m-1+12)%12]); } string VtMonFull(const int m) { string n[12]={"January","February","March","April","May","June","July","August","September","October","November","December"}; return(n[(m-1+12)%12]); } string VtDM(const long day) // "12 Oct" { int y,m,d; VtCivil(day,y,m,d); return(IntegerToString(d)+" "+VtMon3(m)); } string VtDMY(const long day) // "5 Jan 2026" { int y,m,d; VtCivil(day,y,m,d); return(IntegerToString(d)+" "+VtMon3(m)+" "+IntegerToString(y)); } string VtHM(const long t) { return(StringFormat("%02d:%02d",(int)((t%86400)/3600),(int)((t%3600)/60))); } string VtMaskLogin(const long login) { string s=IntegerToString(login); int n=StringLen(s); if(n<7) return(s); return(StringSubstr(s,0,4)+ShortToString(0x2022)+ShortToString(0x2022)+ShortToString(0x2022)+StringSubstr(s,n-2)); } //--- building blocks ------------------------------------------------------------------------------------------------ double VtSec(CVtCard &c,const double y,const string left,const string right="",const color rclr=clrNONE) { string u=left; StringToUpper(u); c.Txt(VT_BODY_X,y,u,c.pal.label,VTK_SEC); if(right!="") c.Txt(VT_BODY_R,y,right,rclr==clrNONE ? c.pal.dim : rclr,VTK_SMALL,1); return(y+16); } void VtKV(CVtCard &c,const double x1,const double x2,const double y,const string k,const string v,const color vc) { c.Txt(x1,y,k,c.pal.label,VTK_SANS); c.Txt(x2,y,v,vc,VTK_MONO,1); } //--- OVERVIEW --------------------------------------------------------------------------------------------------------- double VtDrawOverview(CVtCard &c) { double y=VT_BODY_TOP; VtAcct a=g_acct; double floating=a.equity-a.balance-a.credit; //--- hero c.Txt(VT_BODY_X,y,"EQUITY",c.pal.label,VTK_SEC); c.Txt(190,y,"BALANCE",c.pal.label,VTK_SEC); c.Txt(320,y,"FLOATING",c.pal.label,VTK_SEC); c.Txt(VT_BODY_X,y+12,VtNum(a.equity),c.pal.ink,VTK_BIG); c.Txt(VT_BODY_X,y+40,a.balance>0 ? DoubleToString(100.0*a.equity/a.balance,2)+"% of balance" : "-",c.pal.label,VTK_MONOS); c.Txt(190,y+16,VtNum(a.balance),c.pal.ink,VTK_MONO); c.Txt(320,y+16,VtSgn(floating),VtSignClr(floating),VTK_MONO); c.Txt(320,y+34,a.balance>0 ? VtSgn(100.0*floating/a.balance)+"%" : "-",VtSignClr(floating),VTK_MONOS); y+=60; //--- floating drawdown string since=""; if(g_jr.n>0) since="journal since "+VtDM(g_jr.d[0]); y=VtSec(c,y,"Floating drawdown",since); double now_pct=(a.balance>0 && floating<0 ? 100.0*floating/a.balance : 0.0); long today=g_server_now/86400; double tw_pct=0,tw_money=0; bool tw_ok=false; if(g_jr.n>0 && g_jr.d[g_jr.n-1]==today) { tw_pct=g_jr.p[g_jr.n-1]; tw_money=g_jr.m[g_jr.n-1]; tw_ok=true; } double ww_pct=0; long ww_day=0; bool ww_ok=false; for(int i=0; i0 && g_today_low_time/86400==today) c.Txt(150+78+c.TextW(twt,VTK_MONOS)+6,y,VtHM(g_today_low_time),c.pal.dim,VTK_MONOS); string wwt=(ww_ok && ww_pct<0 ? VtSgn(ww_pct)+"%" : "-"); c.Txt(VT_BODY_R,y,(ww_ok && ww_pct<0 ? VtDM(ww_day) : ""),c.pal.dim,VTK_MONOS,1); int dw=(ww_ok && ww_pct<0 ? c.TextW(VtDM(ww_day),VTK_MONOS)+6 : 0); c.Txt(VT_BODY_R-dw,y,wwt,ww_ok && ww_pct<0 ? c.pal.neg : c.pal.dim,VTK_MONOS,1); c.Txt(VT_BODY_R-dw-c.TextW(wwt,VTK_MONOS)-6,y,"Worst day",c.pal.label,VTK_SMALL,1); y+=20; //--- margin y=VtSec(c,y,"Margin"); string lvl=(a.margin>0 ? VtNum(a.level,0)+"%" : "-"); string so=(a.so_mode==VT_SO_PERCENT ? DoubleToString(a.so,0)+"%" : VtNum(a.so)); VtKV(c,VT_BODY_X,206,y,"Used",VtNum(a.margin),c.pal.ink); VtKV(c,242,VT_BODY_R,y,"Free",VtNum(a.free),c.pal.ink); y+=17; VtKV(c,VT_BODY_X,206,y,"Level",lvl,c.pal.ink); VtKV(c,242,VT_BODY_R,y,"Stop out",so,c.pal.dim); y+=22; //--- open positions string rk=""; color rkc=c.pal.dim; if(g_eng.o_nostop0) { y+=3; string t=(g_eng.o_nostop==1 ? "1 position without a stop" : IntegerToString(g_eng.o_nostop)+" positions without a stop")+ " "+ShortToString(0x00B7)+" "+g_eng.o_nostop_first+(g_eng.o_nostop>1 ? " +"+IntegerToString(g_eng.o_nostop-1) : ""); int tw=c.TextW(t,VTK_SMALL); c.Box(VT_BODY_X,y,VT_BODY_X+tw+12,y+17,c.pal.warn); c.Txt(VT_BODY_X+6,y+2,t,c.pal.warn,VTK_SMALL); y+=22; } else y+=4; //--- exposure y=VtSec(c,y,"Exposure by currency","net, "+g_acct.ccy+" thousands"); int rows=MathMin(g_eng.o_nex,6); if(rows==0) { c.Txt(VT_BODY_X,y,"No open positions",c.pal.dim,VTK_SMALL); y+=18; } double big=0; for(int i=0; i0 ? MathAbs(v)/big*half : 0); if(v<0) c.Fill(mid-bw,y+3,mid,y+12,c.pal.neg,190); else c.Fill(mid,y+3,mid+bw,y+12,c.pal.pos,190); c.Txt(VT_BODY_R,y,VtSgn(v/1000.0,1)+"k",VtSignClr(v/1000.0,1),VTK_MONO,1); y+=17; } return(y); } //--- HISTORY ---------------------------------------------------------------------------------------------------------- string VtRowLabel(const VtRow &r,const long today) { int y,m,d; if(r.kind==0) { VtCivil(r.day,y,m,d); if(r.day==today) return("Today "+ShortToString(0x00B7)+" "+VtWd3(r.day)+" "+IntegerToString(d)); if(r.day==today-1) return("Yesterday "+ShortToString(0x00B7)+" "+VtWd3(r.day)+" "+IntegerToString(d)); return(VtWd3(r.day)+" "+IntegerToString(d)); } VtCivil(today,y,m,d); if(r.kind==1) return("Week"); if(r.kind==2) return(VtMonFull(m)); return(IntegerToString(y)); } void VtHistRow(CVtCard &c,const double y,const VtRow &r,const long today) { c.Txt(VT_BODY_X,y,VtRowLabel(r,today),c.pal.label,VTK_SANS); string wl=IntegerToString(r.w)+"/"+IntegerToString(r.l); c.Txt(194,y,r.n>0 ? wl : "",c.pal.dim,VTK_MONOS,1); int ww=(r.n>0 ? c.TextW(wl,VTK_MONOS)+5 : 0); c.Txt(194-ww,y,IntegerToString(r.n),c.pal.ink,VTK_MONO,1); c.Txt(262,y,VtSgn(r.net),VtSignClr(r.net),VTK_MONO,1); c.Txt(326,y,r.gain_ok ? VtSgn(r.gain)+"%" : "-",r.gain_ok ? VtSignClr(r.gain) : c.pal.dim,VTK_MONO,1); if(r.dd_ok) { bool zero=(StringToDouble(DoubleToString(MathAbs(r.dd_pct),2))==0); string t=(r.dd_approx ? "~" : "")+(zero ? "0.00%" : VtSgn(r.dd_pct)+"%"); color col=(r.dd_approx || zero ? c.pal.dim : c.pal.neg); c.Txt(390,y,t,col,VTK_MONO,1); } else c.Txt(390,y,"-",c.pal.dim,VTK_MONO,1); c.Txt(VT_BODY_R,y,DoubleToString(r.lots,2),c.pal.ink,VTK_MONO,1); } double VtDrawHistory(CVtCard &c) { double y=VT_BODY_TOP; long today=g_server_now/86400; string h[6]={"PERIOD","TRADES W/L","NET","GAIN","WORST DD","LOTS"}; c.Txt(VT_BODY_X,y,h[0],c.pal.label,VTK_SEC); double hx[6]={0,194,262,326,390,VT_BODY_R}; for(int i=1; i<6; i++) c.Txt(hx[i],y,h[i],c.pal.label,VTK_SEC,1); y+=15; c.HLine(VT_BODY_X,VT_BODY_R,y,c.pal.rule); y+=4; int nday=0; for(int i=0; i0) right="since "+VtDMY(s.first_day)+" "+ShortToString(0x00B7)+" "+DoubleToString(s.lots,2)+" lots"; y=VtSec(c,y,"Account statistics",right); double col[3]={VT_BODY_X,166,320}; int i=0; string m=VtMinus(); bool any=(s.n>0); //--- row 1 VtStatCell(c,col[0],y,"Win rate",any ? DoubleToString(s.winrate,1)+"%" : "-",any ? IntegerToString(s.w)+" of "+IntegerToString(s.n) : "",c.pal.ink); VtStatCell(c,col[1],y,"Profit factor",s.pf_ok ? DoubleToString(s.pf,2) : (any && s.gp>0 ? "no losses" : "-"),"",c.pal.ink); VtStatCell(c,col[2],y,"Expectancy",any ? VtSgn(s.expct) : "-",any ? "per trade" : "",any ? VtSignClr(s.expct) : c.pal.dim); y+=48; VtStatCell(c,col[0],y,"Avg win",s.w>0 ? VtSgn(s.avgwin) : "-","",s.w>0 ? c.pal.pos : c.pal.dim); VtStatCell(c,col[1],y,"Avg loss",s.l>0 ? VtSgn(s.avgloss) : "-","",s.l>0 ? c.pal.neg : c.pal.dim); VtStatCell(c,col[2],y,"Reward/risk",(s.w>0 && s.l>0) ? DoubleToString(s.rr,2) : "-",(s.w>0 && s.l>0) ? "avg win / avg loss" : "",c.pal.ink); y+=48; VtStatCell(c,col[0],y,"Max drawdown",s.maxdd_money>0 ? m+DoubleToString(s.maxdd_pct,2)+"%" : (any ? "0.00%" : "-"), s.maxdd_money>0 ? m+VtNum(s.maxdd_money)+" "+ShortToString(0x00B7)+" "+VtDM(s.maxdd_time/86400) : "",s.maxdd_money>0 ? c.pal.neg : c.pal.dim); VtStatCell(c,col[1],y,"Relative DD",s.rel_pct>0 ? m+DoubleToString(s.rel_pct,2)+"%" : (any ? "0.00%" : "-"), s.rel_pct>0 ? m+VtNum(s.rel_money)+" "+ShortToString(0x00B7)+" "+VtDM(s.rel_time/86400) : "",s.rel_pct>0 ? c.pal.neg : c.pal.dim); VtStatCell(c,col[2],y,"Sharpe "+ShortToString(0x00B7)+" 60 days",s.sharpe_ok ? DoubleToString(s.sharpe,2) : "-",s.sharpe_ok ? "daily returns" : "needs 2+ trading days",c.pal.ink); y+=48; VtStatCell(c,col[0],y,"Best day",s.best_ok ? VtSgn(s.best_pct)+"%" : "-",s.best_ok ? VtDM(s.best_day) : "",s.best_ok ? VtSignClr(s.best_pct) : c.pal.dim); VtStatCell(c,col[1],y,"Worst day",s.best_ok ? VtSgn(s.worst_pct)+"%" : "-",s.best_ok ? VtDM(s.worst_day) : "",s.best_ok ? VtSignClr(s.worst_pct) : c.pal.dim); double costs=s.comm+s.swap+s.fee; VtStatCell(c,col[2],y,"Costs",any ? VtSgn(costs) : "-","",any ? VtSignClr(costs) : c.pal.dim); y+=50; if(any) { c.Txt(VT_BODY_X,y,"commission "+VtSgn(s.comm)+" "+ShortToString(0x00B7)+" swap "+VtSgn(s.swap)+" "+ShortToString(0x00B7)+" fees "+VtSgn(s.fee), c.pal.dim,VTK_MONOS); y+=16; } else { c.Txt(VT_BODY_X,y,"No closed trades in the history yet.",c.pal.dim,VTK_SMALL); y+=16; } c.Txt(VT_BODY_X,y,"Max and relative drawdown: closed-trade balance curve.",c.pal.dim,VTK_SMALL); c.Txt(VT_BODY_X,y+13,"Sharpe: cumulative return / stdev of daily returns, last 60 days.",c.pal.dim,VTK_SMALL); return(y+30); } #endif //+------------------------------------------------------------------+