Five-file library (scenarios, priors, dual solver, views, posterior), closed-form checks, a demo with replay, a walk-forward forecast test, the EP_ViewCost indicator and the EP_Sizer expected-shortfall sizing EA, with a README covering the method, the evidence and the layout.
616 lignes
22 Kio
MQL5
616 lignes
22 Kio
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 <Canvas\Canvas.mqh>
|
|
#include <EntropyPooling\Scenarios.mqh>
|
|
#include <EntropyPooling\Probabilities.mqh>
|
|
#include <EntropyPooling\MinRelEntropy.mqh>
|
|
#include <EntropyPooling\Views.mqh>
|
|
#include <EntropyPooling\Posterior.mqh>
|
|
|
|
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<n;i++)
|
|
{
|
|
StringTrimLeft(out[i]);
|
|
StringTrimRight(out[i]);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Bind buffers and read the basket; the work runs in slices. |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit(void)
|
|
{
|
|
SetIndexBuffer(EP_BUF_RETURN,BufReturn,INDICATOR_DATA);
|
|
SetIndexBuffer(EP_BUF_RETURN_COLOR,BufReturnColor,INDICATOR_COLOR_INDEX);
|
|
SetIndexBuffer(EP_BUF_VAR,BufVar,INDICATOR_DATA);
|
|
SetIndexBuffer(EP_BUF_ES,BufEs,INDICATOR_DATA);
|
|
SetIndexBuffer(EP_BUF_ES_COLOR,BufEsColor,INDICATOR_COLOR_INDEX);
|
|
SetIndexBuffer(EP_BUF_ES_WINDOW,BufEsWindow,INDICATOR_DATA);
|
|
SetIndexBuffer(EP_BUF_ENS,BufEns,INDICATOR_CALCULATIONS);
|
|
SetIndexBuffer(EP_BUF_SIZE,BufSize,INDICATOR_CALCULATIONS);
|
|
SetIndexBuffer(EP_BUF_ES_VIEWS,BufEsViews,INDICATOR_CALCULATIONS);
|
|
for(int k=0;k<4;k++)
|
|
PlotIndexSetDouble(k,PLOT_EMPTY_VALUE,EMPTY_VALUE);
|
|
|
|
string wtext[];
|
|
const int n=SplitList(InpSymbols,g_symbols);
|
|
if(n<2 || SplitList(InpWeights,wtext)!=n)
|
|
{
|
|
Print("EP_ViewCost: give at least two symbols and one weight per symbol");
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
ArrayResize(g_weights,n);
|
|
for(int i=0;i<n;i++)
|
|
g_weights[i]=StringToDouble(wtext[i]);
|
|
if(InpLevel<=0.0 || InpLevel>=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;i<ArraySize(g_symbols);i++)
|
|
{
|
|
if(!SymbolSelect(g_symbols[i],true))
|
|
return false;
|
|
if(!SeriesInfoInteger(g_symbols[i],_Period,SERIES_SYNCHRONIZED) || Bars(g_symbols[i],_Period)<2)
|
|
{
|
|
MqlRates probe[];
|
|
CopyRates(g_symbols[i],_Period,0,1,probe);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Joint history of closed bars, the book, and its state column. |
|
|
//+------------------------------------------------------------------+
|
|
bool BuildScenarios(void)
|
|
{
|
|
if(!g_scen.LoadReturns(g_symbols,_Period,InpBars))
|
|
return false;
|
|
const int n=ArraySize(g_symbols);
|
|
int cols[];
|
|
ArrayResize(cols,n);
|
|
for(int i=0;i<n;i++)
|
|
cols[i]=i;
|
|
vector book;
|
|
EpPortfolio(g_scen,cols,g_weights,book);
|
|
double values[];
|
|
ArrayResize(values,g_scen.Rows());
|
|
for(int t=0;t<g_scen.Rows();t++)
|
|
values[t]=book[t];
|
|
const int book_col=g_scen.AddColumn("Book",EP_COLUMN_RETURN,values,EMPTY_VALUE);
|
|
const int state_col=g_scen.AddTrailingVolatility(book_col,InpStateWindow);
|
|
if(state_col<0 || g_scen.Rows()<2*InpWindow)
|
|
{
|
|
if(!g_warned)
|
|
PrintFormat("EP_ViewCost: %d joint bars so far, %d needed before the first forecast",
|
|
g_scen.Rows(),2*InpWindow);
|
|
g_warned=true;
|
|
return false;
|
|
}
|
|
g_scen.Column(book_col,g_book);
|
|
g_scen.Column(state_col,g_state);
|
|
EpSortOrder(g_book,g_order);
|
|
g_state_col=state_col;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Row of the bar at t; Rows() past the last row, -1 if not shared. |
|
|
//+------------------------------------------------------------------+
|
|
int RowOf(const datetime t)
|
|
{
|
|
const int T=g_scen.Rows();
|
|
if(T==0 || t>g_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(m<t)
|
|
lo=mid+1;
|
|
else
|
|
hi=mid-1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Decay prior over the rows before r, zero from r on. |
|
|
//+------------------------------------------------------------------+
|
|
void DecayBefore(const int r,vector &p)
|
|
{
|
|
const int T=g_scen.Rows();
|
|
const double k=MathLog(2.0)/InpHalfLife;
|
|
p.Init(T);
|
|
p.Fill(0.0);
|
|
for(int i=0;i<r;i++)
|
|
p[i]=MathExp(-k*(r-1-i));
|
|
EpNormalise(p);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Forecast row r from earlier rows: VaR, ES, window ES, ENS, bound.|
|
|
//+------------------------------------------------------------------+
|
|
bool Forecast(const int r,double &var,double &es,double &es_window,double &ens,bool &bound,
|
|
vector &p_decay,vector &p_state)
|
|
{
|
|
const int T=g_scen.Rows();
|
|
if(r<InpWindow || r>T)
|
|
return false;
|
|
const double target=(r<T ? g_state[r] : g_scen.Next(g_state_col));
|
|
|
|
DecayBefore(r,p_decay);
|
|
SEpSolve res;
|
|
const double used=EpConditionState(g_scen,p_decay,g_state_col,target,InpMinEns,p_state,res);
|
|
bound=(used!=target);
|
|
ens =EpEffectiveScenarios(p_state);
|
|
if(!EpTailOrdered(g_book,g_order,p_state,InpLevel,var,es))
|
|
return false;
|
|
|
|
vector p_roll;
|
|
p_roll.Init(T);
|
|
p_roll.Fill(0.0);
|
|
for(int i=r-InpWindow;i<r;i++)
|
|
p_roll[i]=1.0/InpWindow;
|
|
double var_window;
|
|
return EpTailOrdered(g_book,g_order,p_roll,InpLevel,var_window,es_window);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ClearBar(const int i)
|
|
{
|
|
BufReturn[i] =EMPTY_VALUE;
|
|
BufReturnColor[i]=0.0;
|
|
BufVar[i] =EMPTY_VALUE;
|
|
BufEs[i] =EMPTY_VALUE;
|
|
BufEsColor[i] =0.0;
|
|
BufEsWindow[i] =EMPTY_VALUE;
|
|
BufEns[i] =EMPTY_VALUE;
|
|
BufSize[i] =EMPTY_VALUE;
|
|
BufEsViews[i] =EMPTY_VALUE;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ForecastBar(const int i)
|
|
{
|
|
ClearBar(i);
|
|
const int r=RowOf(g_time[i]);
|
|
if(r<0)
|
|
return;
|
|
double var,es,es_window,ens;
|
|
bool bound;
|
|
vector p_decay,p_state;
|
|
if(!Forecast(r,var,es,es_window,ens,bound,p_decay,p_state))
|
|
return;
|
|
if(r<g_scen.Rows())
|
|
{
|
|
BufReturn[i] =100.0*g_book[r];
|
|
BufReturnColor[i]=(g_book[r]<=var ? 1.0 : 0.0);
|
|
}
|
|
BufVar[i] =100.0*var;
|
|
BufEs[i] =100.0*es;
|
|
BufEsColor[i] =(bound ? 1.0 : 0.0);
|
|
BufEsWindow[i]=100.0*es_window;
|
|
BufEns[i] =ens;
|
|
BufSize[i] =(es<0.0 ? es_window/es : EMPTY_VALUE);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Forecast bars until the time slice runs out; 0 means no limit. |
|
|
//+------------------------------------------------------------------+
|
|
void Work(const uint budget_ms)
|
|
{
|
|
if(g_rates==0)
|
|
return;
|
|
//--- a failed reload waits for the next bar, or a second on a live chart
|
|
if(g_reload)
|
|
{
|
|
if(g_tried_bar==g_bar_time && (MQLInfoInteger(MQL_TESTER) || GetTickCount()-g_tried_ms<1000))
|
|
return;
|
|
g_tried_bar=g_bar_time;
|
|
g_tried_ms =GetTickCount();
|
|
if(!BasketReady() || !BuildScenarios())
|
|
return;
|
|
g_reload=false;
|
|
}
|
|
const uint t0=GetTickCount();
|
|
while(g_cursor<g_rates)
|
|
{
|
|
ForecastBar(g_cursor);
|
|
g_cursor++;
|
|
if(budget_ms>0 && 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;i<rates_total;i++)
|
|
ClearBar(i);
|
|
g_cursor =MathMax(0,rates_total-InpHistory);
|
|
g_bar_time=0;
|
|
}
|
|
ArrayCopy(g_time,time,0,0,rates_total);
|
|
g_rates=rates_total;
|
|
|
|
//--- a new bar closes the last one: reload, redo it, forecast the new one
|
|
if(time[rates_total-1]!=g_bar_time)
|
|
{
|
|
g_bar_time =time[rates_total-1];
|
|
g_reload =true;
|
|
g_panel_due=true;
|
|
if(prev_calculated>0)
|
|
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<g_rates || g_panel_due))
|
|
EventChartCustom(0,EP_EVENT_SLICE,0,0.0,g_short);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
|
|
{
|
|
if(id!=CHARTEVENT_CUSTOM+EP_EVENT_SLICE || sparam!=g_short)
|
|
return;
|
|
Work(100);
|
|
NextSlice();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| The forming bar: conditioned and view-pooled ES, ENS and panel. |
|
|
//+------------------------------------------------------------------+
|
|
void LiveBar(void)
|
|
{
|
|
const int T=g_scen.Rows();
|
|
double var,es,es_window,ens;
|
|
bool bound;
|
|
vector p_decay,p_state;
|
|
if(!Forecast(T,var,es,es_window,ens,bound,p_decay,p_state))
|
|
return;
|
|
double v_decay,es_decay;
|
|
EpTailOrdered(g_book,g_order,p_decay,InpLevel,v_decay,es_decay);
|
|
|
|
vector p_views=p_state;
|
|
double es_views=es,ens_views=ens;
|
|
bool have_views=false;
|
|
if(InpUseViews)
|
|
{
|
|
const int vol_col =g_scen.Find(InpVolSymbol);
|
|
const int tail_col=g_scen.Find(InpTailSymbol);
|
|
const int ca=g_scen.Find(InpCorrA),cb=g_scen.Find(InpCorrB);
|
|
if(vol_col<0 || tail_col<0 || ca<0 || cb<0)
|
|
Print("EP_ViewCost: every view symbol must be in the basket");
|
|
else
|
|
{
|
|
vector z,x;
|
|
g_scen.Column(g_state_col,z);
|
|
CEpViews views;
|
|
views.AddMean(g_state_col,EP_EQUAL,EpMean(z,p_state));
|
|
g_scen.Column(vol_col,x);
|
|
views.AddVolatility(vol_col,EP_EQUAL,InpVolScale*EpVolatility(x,p_state));
|
|
views.AddTail(tail_col,InpTailMove,EP_AT_LEAST,InpTailProb);
|
|
views.AddCorrelation(ca,cb,EP_AT_MOST,InpCorr);
|
|
for(int k=0;k<2;k++)
|
|
{
|
|
const int col=(k==0 ? ca : cb);
|
|
if(col==vol_col)
|
|
continue;
|
|
g_scen.Column(col,x);
|
|
views.AddVolatility(col,EP_EQUAL,EpVolatility(x,p_state));
|
|
}
|
|
SEpSolve rv;
|
|
if(views.Pool(g_scen,p_decay,g_solver,p_views,rv))
|
|
{
|
|
double v_views;
|
|
EpTailOrdered(g_book,g_order,p_views,InpLevel,v_views,es_views);
|
|
ens_views =rv.ens;
|
|
have_views=true;
|
|
}
|
|
else
|
|
{
|
|
p_views=p_state;
|
|
Print("EP_ViewCost: the views cannot be met by reweighting these scenarios");
|
|
}
|
|
}
|
|
}
|
|
BufEsViews[g_rates-1]=(have_views ? 100.0*es_views : EMPTY_VALUE);
|
|
|
|
if(!PanelReady())
|
|
{
|
|
if(InpPanel && ++g_panel_try<50)
|
|
g_panel_due=true;
|
|
return;
|
|
}
|
|
SPanelRow rows[4];
|
|
rows[0].name="rolling";
|
|
rows[0].es =es_window;
|
|
rows[0].ens =InpWindow;
|
|
rows[1].name="decay";
|
|
rows[1].es =es_decay;
|
|
rows[1].ens =EpEffectiveScenarios(p_decay);
|
|
rows[2].name="conditioned";
|
|
rows[2].es =es;
|
|
rows[2].ens =ens;
|
|
rows[3].name="+ views";
|
|
rows[3].es =es_views;
|
|
rows[3].ens =ens_views;
|
|
DrawPanel(rows,(have_views ? 4 : 3),p_state,p_views,have_views,bound,T);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Opaque colour between a and b; the canvas does not blend. |
|
|
//+------------------------------------------------------------------+
|
|
uint Mix(const color a,const color b,const double share)
|
|
{
|
|
const int r=(int)MathRound((1.0-share)*(a&0xFF)+share*(b&0xFF));
|
|
const int g=(int)MathRound((1.0-share)*((a>>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<n;i++)
|
|
{
|
|
const int y =pad+(i+2)*lh;
|
|
const uint clr=(i==3 ? orng : (i==2 ? blue : ink));
|
|
const string size=(rows[i].es<0.0 ? StringFormat("%.2fx",rows[0].es/rows[i].es) : "-");
|
|
g_canvas.TextOut(pad,y,StringFormat("%-12s %7.3f%% %5s",rows[i].name,100.0*rows[i].es,size),clr);
|
|
const int w=(int)MathRound(gw*MathMin(rows[i].ens/T,1.0));
|
|
g_canvas.FillRectangle(gx,y+ch/4,gx+gw,y+ch-ch/4,grid);
|
|
g_canvas.FillRectangle(gx,y+ch/4,gx+MathMax(w,1),y+ch-ch/4,(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;t<T;t++)
|
|
{
|
|
const int b=MathMax(0,MathMin(bins-1,(int)((g_book[t]-lo)/(hi-lo)*bins)));
|
|
hs[b]+=p_state[t];
|
|
hv[b]+=p_views[t];
|
|
}
|
|
const double top=MathMax(hs[ArrayMaximum(hs)],hv[ArrayMaximum(hv)]);
|
|
const int bw=MathMax(hw/bins,1);
|
|
for(int b=0;b<bins;b++)
|
|
{
|
|
const int x =hx+b*bw;
|
|
const int hS=(int)MathRound(hh*hs[b]/top);
|
|
const int hV=(int)MathRound(hh*hv[b]/top);
|
|
if(hS>0)
|
|
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<hx+bins*bw)
|
|
g_canvas.LineVertical(zero,hy,hy+hh,dim);
|
|
g_canvas.TextOut(hx,hy+hh+2,StringFormat("%.2f%%",100.0*lo),dim);
|
|
g_canvas.TextOut(hx+bins*bw,hy+hh+2,StringFormat("%.2f%%",100.0*hi),dim,TA_RIGHT);
|
|
}
|
|
g_canvas.Update();
|
|
}
|
|
//+------------------------------------------------------------------+
|