616 Zeilen
22 KiB
MQL5
616 Zeilen
22 KiB
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();
| |||
}
| |||
//+------------------------------------------------------------------+
|