//+------------------------------------------------------------------+ //| EP_ViewCost.mq5 | //| MMQ — Muhammad Minhas Qamar | //| www.mql5.com/en/articles/24757 | //+------------------------------------------------------------------+ #property copyright "MMQ — Muhammad Minhas Qamar" #property link "https://www.mql5.com/en/articles/24757" #property version "1.00" #property strict #property description "A basket's next-bar risk from its whole history, tilted so that the" #property description "average volatility state matches today's. Each bar is forecast from" #property description "the bars before it; red bars broke the forecast VaR. The panel adds" #property description "the views to the forming bar and shows what they cost in scenarios." #property indicator_separate_window #property indicator_buffers 9 #property indicator_plots 4 #property indicator_label1 "Book return" #property indicator_type1 DRAW_COLOR_HISTOGRAM #property indicator_color1 clrSilver,clrTomato #property indicator_width1 2 #property indicator_label2 "VaR" #property indicator_type2 DRAW_LINE #property indicator_color2 clrSteelBlue #property indicator_style2 STYLE_DOT #property indicator_label3 "ES" #property indicator_type3 DRAW_COLOR_LINE #property indicator_color3 clrDodgerBlue,clrOrange #property indicator_width3 2 #property indicator_label4 "ES rolling window" #property indicator_type4 DRAW_LINE #property indicator_color4 clrGray #property indicator_style4 STYLE_DASH #include #include #include #include #include #include input group "Data" input string InpSymbols = "EURUSD,GBPUSD,AUDUSD,USDJPY,USDCHF"; // basket input string InpWeights = "0.2,0.2,0.2,-0.2,-0.2"; // book notional per symbol, same order input int InpBars = 5000; // bars loaded per symbol input int InpHistory = 750; // chart bars to forecast input group "Probabilities" input int InpWindow = 250; // rolling window drawn for comparison input double InpHalfLife = 250.0; // exponential decay half-life, bars input int InpStateWindow = 20; // trailing volatility of the book, bars input double InpMinEns = 250; // fewest effective scenarios conditioning may leave input double InpLevel = 0.025; // tail level for VaR and ES input group "Views on the forming bar" input bool InpUseViews = true; // pool the views into the panel input string InpVolSymbol = "USDJPY"; // volatility view on input double InpVolScale = 1.5; // times its conditioned volatility input string InpTailSymbol = "USDCHF"; // tail view on input double InpTailMove = -0.02; // log return at or below input double InpTailProb = 0.01; // has at least this probability input string InpCorrA = "EURUSD"; // correlation view between input string InpCorrB = "GBPUSD"; // and input double InpCorr = 0.40; // at most this correlation input group "Display" input bool InpPanel = true; // draw the forming-bar panel on the chart input color InpPanelBack = clrWhite; // panel background input color InpPanelText = clrBlack; // panel text double BufReturn[]; double BufReturnColor[]; double BufVar[]; double BufEs[]; double BufEsColor[]; double BufEsWindow[]; double BufEns[]; double BufSize[]; double BufEsViews[]; //--- one row of the panel struct SPanelRow { string name; double es; double ens; }; #define EP_EVENT_SLICE 4711 // custom chart event that resumes the history pass string g_symbols[]; double g_weights[]; CEpScenarios g_scen; CEpSolver g_solver; vector g_book; // book return per scenario row int g_order[]; // rows by book return, sorted once per reload vector g_state; // trailing volatility per row int g_state_col=-1; bool g_reload =true; // rebuild the scenarios before the next forecast int g_cursor =-1; // next chart bar to forecast datetime g_bar_time =0; // open time of the forming bar datetime g_time[]; // chart bar times, copied from OnCalculate int g_rates =0; bool g_panel_due=false; int g_panel_try=0; datetime g_tried_bar=0; // bar of the last reload attempt uint g_tried_ms =0; bool g_warned =false; // history warning printed once string g_short; // short name, used to find the indicator on the chart CCanvas g_canvas; bool g_canvas_ok=false; //+------------------------------------------------------------------+ int SplitList(const string text,string &out[]) { const int n=StringSplit(text,',',out); for(int i=0;i=0.5 || InpMinEns<1.0 || InpHistory<1) return INIT_PARAMETERS_INCORRECT; g_short=StringFormat("EP_ViewCost (%.1f%% ES, ENS >= %.0f)",100.0*InpLevel,InpMinEns); IndicatorSetString(INDICATOR_SHORTNAME,g_short); IndicatorSetInteger(INDICATOR_DIGITS,3); IndicatorSetInteger(INDICATOR_LEVELS,1); IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.0); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Create the panel once the indicator is found on this chart. | //+------------------------------------------------------------------+ bool PanelReady(void) { if(g_canvas_ok) return true; if(!InpPanel || MQLInfoInteger(MQL_TESTER)) return false; if(ChartWindowFind(0,g_short)<1) return false; g_canvas_ok=g_canvas.CreateBitmapLabel(0,0,"EP_ViewCost_Panel",8,24,400,200, COLOR_FORMAT_ARGB_NORMALIZE); if(g_canvas_ok) g_canvas.FontSet("Consolas",-80); return g_canvas_ok; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(g_canvas_ok) g_canvas.Destroy(); } //+------------------------------------------------------------------+ //| True once every symbol's history is fully synchronised. | //+------------------------------------------------------------------+ bool BasketReady(void) { for(int i=0;ig_scen.Time(T-1)) return T; int lo=0,hi=T-1; while(lo<=hi) { const int mid=(lo+hi)/2; const datetime m=g_scen.Time(mid); if(m==t) return mid; if(mT) return false; const double target=(r0 && GetTickCount()-t0>=budget_ms) break; } if(g_cursor>=g_rates && g_panel_due) { g_panel_due=false; LiveBar(); } ChartRedraw(); } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[], const double &open[],const double &high[],const double &low[],const double &close[], const long &tick_volume[],const long &volume[],const int &spread[]) { if(rates_total<2) return 0; if(prev_calculated==0) { for(int i=0;i0) g_cursor=MathMin(g_cursor,rates_total-2); } //--- the tester runs it all at once; on a chart the rest follows in slices if(MQLInfoInteger(MQL_TESTER)) { Work(0); return rates_total; } const bool on_chart=(ChartWindowFind(0,g_short)>0); Work(on_chart ? 100 : 250); if(on_chart) NextSlice(); return rates_total; } //+------------------------------------------------------------------+ //| Queue the next slice as a chart event; a refresh would restart. | //+------------------------------------------------------------------+ void NextSlice(void) { if(!g_reload && (g_cursor>8)&0xFF)+share*((b>>8)&0xFF)); const int u=(int)MathRound((1.0-share)*((a>>16)&0xFF)+share*((b>>16)&0xFF)); return ColorToARGB((color)(r|(g<<8)|(u<<16)),255); } //+------------------------------------------------------------------+ //| Panel: ES, size and ENS per vector, and the book's distribution. | //+------------------------------------------------------------------+ void DrawPanel(const SPanelRow &rows[],const int n,const vector &p_state,const vector &p_views, const bool have_views,const bool bound,const int T) { const color bg =InpPanelBack; const color fg =InpPanelText; const uint ink =ColorToARGB(fg,255); const uint dim =Mix(bg,fg,0.55); const uint grid=Mix(bg,fg,0.15); const uint blue=ColorToARGB(clrRoyalBlue,255); const uint orng=ColorToARGB(clrDarkOrange,255); int cw=0,ch=0; g_canvas.TextSize("0",cw,ch); const int pad=cw,lh=ch+4; const int gx=pad+30*cw,gw=8*cw; const int hx=gx+gw+7*cw,hw=26*cw,bins=26; const int W=hx+hw+pad; const int H=pad+(n+3)*lh+pad; if(g_canvas.Width()!=W || g_canvas.Height()!=H) g_canvas.Resize(W,H); g_canvas.Erase(ColorToARGB(bg,255)); g_canvas.Rectangle(0,0,W-1,H-1,grid); g_canvas.TextOut(pad,pad,StringFormat("Next bar ES %.1f%%",100.0*InpLevel),ink); g_canvas.TextOut(pad,pad+lh,StringFormat("%-12s %8s %5s","","ES","size"),dim); g_canvas.TextOut(gx,pad+lh,"ENS",dim); for(int i=0;i=2 ? clr : dim)); g_canvas.TextOut(gx+gw+cw,y,StringFormat("%.0f",rows[i].ens),dim); } //--- floor on the gauges; the conditioned row is marked when it binds const int mark=gx+(int)MathRound(gw*MathMin(InpMinEns/T,1.0)); g_canvas.LineVertical(mark,pad+2*lh,pad+(n+2)*lh,(bound ? orng : ink)); double lo,hi,unused; vector p_all; EpUniform(T,p_all); EpTailOrdered(g_book,g_order,p_all,0.005,lo,unused); EpTailOrdered(g_book,g_order,p_all,0.995,hi,unused); const int hy=pad+lh/2,hh=H-hy-lh-pad; if(hi>lo && hh>10) { double hs[],hv[]; ArrayResize(hs,bins); ArrayResize(hv,bins); ArrayInitialize(hs,0.0); ArrayInitialize(hv,0.0); for(int t=0;t0) g_canvas.FillRectangle(x,hy+hh-hS,x+bw-2,hy+hh,Mix(bg,clrRoyalBlue,0.45)); if(have_views && hV>0) g_canvas.Rectangle(x,hy+hh-hV,x+bw-2,hy+hh,orng); } g_canvas.LineHorizontal(hx,hx+bins*bw,hy+hh,dim); const int zero=hx+(int)MathRound(-lo/(hi-lo)*bins*bw); if(zero>hx && zero