543 lines
23 KiB
MQL5
543 lines
23 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| SFAAnalog.mq5 |
| |
//| MMQ — Muhammad Minhas Qamar |
| |
//| www.mql5.com/en/articles/24006 |
| |
//+------------------------------------------------------------------+
| |
#property copyright "MMQ — Muhammad Minhas Qamar"
| |
#property link "https://www.mql5.com/en/articles/24006"
| |
#property version "1.00"
| |
#property strict
| |
#property indicator_chart_window
| |
#property indicator_plots 0
| |
| |
#include <SFA\SFAAnalogs.mqh>
| |
| |
//+------------------------------------------------------------------+
| |
//| Turns the SFA analog search into something you can read on a |
| |
//| chart: a fan cone over the next H bars and a corner panel. |
| |
//| |
| |
//| The cone is NOT a forecast line. Each step plots the median and |
| |
//| the p25/p75 band of what actually happened after the closest |
| |
//| historical matches, scaled by the current ATR. A wide band |
| |
//| means the precedents disagreed, and that is the useful signal — |
| |
//| it is drawn only when the sample clears both conviction gates, |
| |
//| so an empty chart is a verdict, not a failure. |
| |
//| |
| |
//| The panel carries two rows the SAX version cannot: the size of |
| |
//| the MCB fitting sample, and the share of candidates the lower |
| |
//| bound rejected on this bar. The first tells you whether the bin |
| |
//| edges rest on enough history to be meaningful; the second tells |
| |
//| you what the symbolic layer is actually buying you. |
| |
//+------------------------------------------------------------------+
| |
| |
//--- shape / search
| |
input int InpWinLen = 48; // window length (bars)
| |
input int InpHorizon = 12; // forecast horizon (bars)
| |
input int InpWordLen = 8; // SFA word length (even)
| |
input int InpAlphabet = 8; // alphabet size
| |
input ENUM_SFA_BINNING InpBinning = SFA_BINS_EQUI_DEPTH; // bin edge rule
| |
input int InpMaxAnalogs= 50; // max analogs kept
| |
input int InpMinAnalogs= 10; // min analogs for a verdict
| |
input double InpEdgeATR = 0.25; // |median| ATR for an edge
| |
input ENUM_SFA_METRIC InpMetric = SFA_METRIC_MINDIST; // search metric
| |
input int InpATRPeriod = 14; // ATR period (scale)
| |
input int InpMaxHistory= 6000; // bars of history to scan
| |
| |
//--- panel theme (background + body-text colors)
| |
enum ENUM_PANEL_THEME
| |
{
| |
THEME_DARK, // dark box, light text (for dark charts)
| |
THEME_LIGHT // light box, dark text (for light charts)
| |
};
| |
| |
input color InpUpColor = clrDodgerBlue; // bullish cone / panel
| |
input color InpDownColor = clrTomato; // bearish cone / panel
| |
input color InpFlatColor = clrGray; // no-edge / too-few
| |
input color InpBandColor = clrSlateGray; // p25..p75 band lines
| |
input ENUM_BASE_CORNER InpCorner = CORNER_LEFT_UPPER; // panel corner
| |
input ENUM_PANEL_THEME InpTheme = THEME_LIGHT; // panel theme
| |
input int InpPanelX = 14; // panel offset X (px)
| |
input int InpPanelY = 18; // panel offset Y (px)
| |
| |
//--- panel metrics in pixels. Everything the panel draws is placed from
| |
//--- these, so changing the pitch or padding reflows the whole layout.
| |
#define P_PAD 14 // inner margin
| |
#define P_HDR 28 // header strip height
| |
#define P_ROW 20 // body row pitch
| |
#define P_HERO 34 // hero (median move) block height
| |
#define P_BAR 6 // up-rate bar thickness
| |
#define P_CHIP 28 // verdict chip height
| |
#define P_GAP 28 // minimum gutter between a label and its value
| |
#define P_SEC 16 // breathing room around a section divider
| |
#define P_MINW 212 // panel never narrower than this
| |
| |
//--- fonts: proportional for prose, monospace for the symbolic word
| |
#define F_UI "Segoe UI"
| |
#define F_MONO "Consolas"
| |
| |
//--- globals
| |
int g_hATR=INVALID_HANDLE;
| |
string g_pfx="SFAAN_"; // object-name prefix (all ours, easy cleanup)
| |
datetime g_lastBar=0;
| |
CSFAAnalogFinder g_finder;
| |
| |
//--- geometry of the panel being drawn, shared with the two placement
| |
//--- helpers so callers can work in panel-relative pixels and stay
| |
//--- unaware of which chart corner the panel is pinned to
| |
int g_px=0,g_py=0,g_pw=0,g_ph=0;
| |
bool g_pLeft=true,g_pUpper=true;
| |
| |
//+------------------------------------------------------------------+
| |
//| Indicator init: configure the finder and create the ATR handle. |
| |
//+------------------------------------------------------------------+
| |
int OnInit()
| |
{
| |
//--- clear anything a previous load left behind, so a parameter change
| |
//--- that reflows the panel cannot strand objects from the old layout
| |
ObjectsDeleteAll(0,g_pfx);
| |
| |
if(!g_finder.Configure(InpWinLen,InpHorizon,InpWordLen,InpAlphabet,
| |
InpBinning,InpMetric))
| |
{
| |
Print("SFAAnalog: bad configuration. Word length must be even and "
| |
"below the window length; alphabet must be 2..10.");
| |
return(INIT_PARAMETERS_INCORRECT);
| |
}
| |
g_finder.SetMaxAnalogs(InpMaxAnalogs);
| |
g_finder.SetMinAnalogs(InpMinAnalogs);
| |
g_finder.SetEdgeThreshold(InpEdgeATR);
| |
| |
g_hATR=iATR(_Symbol,_Period,InpATRPeriod);
| |
if(g_hATR==INVALID_HANDLE)
| |
{
| |
Print("SFAAnalog: failed to create ATR handle");
| |
return(INIT_FAILED);
| |
}
| |
| |
IndicatorSetString(INDICATOR_SHORTNAME,"SFA Analog Forecaster");
| |
return(INIT_SUCCEEDED);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Indicator shutdown: drop our objects and release the ATR handle. |
| |
//+------------------------------------------------------------------+
| |
void OnDeinit(const int reason)
| |
{
| |
ObjectsDeleteAll(0,g_pfx);
| |
if(g_hATR!=INVALID_HANDLE)
| |
IndicatorRelease(g_hATR);
| |
ChartRedraw();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Recompute only when a new bar has formed. |
| |
//+------------------------------------------------------------------+
| |
bool NewBar()
| |
{
| |
datetime t=iTime(_Symbol,_Period,0);
| |
if(t==g_lastBar)
| |
return false;
| |
g_lastBar=t;
| |
return true;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Load closes + aligned ATR into chronological buffers (oldest at |
| |
//| index 0). 'shift 1' so only CLOSED bars are used. Returns the |
| |
//| number of usable bars, or 0 on failure. |
| |
//+------------------------------------------------------------------+
| |
int LoadHistory(double &close[],double &atr[])
| |
{
| |
int got=CopyClose(_Symbol,_Period,1,InpMaxHistory,close);
| |
if(got<InpWinLen+InpHorizon+InpATRPeriod+5)
| |
return 0;
| |
ArraySetAsSeries(close,false);
| |
| |
double atrBuf[];
| |
if(CopyBuffer(g_hATR,0,1,got,atrBuf)<got)
| |
return 0;
| |
ArraySetAsSeries(atrBuf,false);
| |
| |
ArrayResize(atr,got);
| |
for(int i=0;i<got;i++)
| |
atr[i]=atrBuf[i];
| |
return got;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Create-or-update a trend segment object between two chart points.|
| |
//+------------------------------------------------------------------+
| |
void SetTrend(string name,datetime t1,double p1,datetime t2,double p2,
| |
color clr,int width,int style)
| |
{
| |
if(ObjectFind(0,name)<0)
| |
ObjectCreate(0,name,OBJ_TREND,0,t1,p1,t2,p2);
| |
ObjectSetInteger(0,name,OBJPROP_TIME,0,t1);
| |
ObjectSetDouble(0,name,OBJPROP_PRICE,0,p1);
| |
ObjectSetInteger(0,name,OBJPROP_TIME,1,t2);
| |
ObjectSetDouble(0,name,OBJPROP_PRICE,1,p2);
| |
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |
ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
| |
ObjectSetInteger(0,name,OBJPROP_STYLE,style);
| |
ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
| |
ObjectSetInteger(0,name,OBJPROP_BACK,false);
| |
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Width of a string in pixels, in the font it will actually be |
| |
//| drawn with. Sizing the panel from measured text — rather than |
| |
//| from a character count — is what keeps it from clipping when the |
| |
//| word length, the symbol name or the user's DPI changes. |
| |
//+------------------------------------------------------------------+
| |
int TextW(string s,string font,int fs)
| |
{
| |
TextSetFont(font,-fs*10); // negative size = points*10, dpi-aware
| |
uint w=0,h=0;
| |
TextGetSize(s,w,h);
| |
return (int)w;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Place a filled rectangle in panel-relative pixels. |
| |
//| (px,py) is the offset from the panel's visual top-left whichever|
| |
//| corner it is pinned to; the mirroring for right and lower |
| |
//| corners happens here and nowhere else. |
| |
//| |
| |
//| 'edge' draws the one-pixel outline. Passing the fill colour |
| |
//| makes the shape borderless, which is what every element except |
| |
//| the outer card wants. |
| |
//+------------------------------------------------------------------+
| |
void PanelRect(string tag,int px,int py,int w,int h,color bg,color edge)
| |
{
| |
string name=g_pfx+tag;
| |
if(ObjectFind(0,name)<0)
| |
ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,0,0,0);
| |
| |
int x=g_pLeft ?(g_px+px):(g_px+g_pw-px-w);
| |
int y=g_pUpper?(g_py+py):(g_py+g_ph-py-h);
| |
| |
ObjectSetInteger(0,name,OBJPROP_CORNER,InpCorner);
| |
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
| |
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
| |
ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
| |
ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
| |
ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg);
| |
ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT);
| |
ObjectSetInteger(0,name,OBJPROP_COLOR,edge);
| |
ObjectSetInteger(0,name,OBJPROP_BACK,false);
| |
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Place a text run in panel-relative pixels. |
| |
//| |
| |
//| py is the vertical CENTRE of the text, not its top, and |
| |
//| right=true anchors the string by its right edge. Between them |
| |
//| the two rails do all the alignment work: values stay flush |
| |
//| without padding, and a string sits truly centred in its block |
| |
//| whatever the font size or the reader's DPI turns out to be. |
| |
//| Offsets tuned by eye against one font drift on both counts. |
| |
//+------------------------------------------------------------------+
| |
void PanelText(string tag,int px,int py,string text,color clr,
| |
string font,int fs,bool right)
| |
{
| |
string name=g_pfx+tag;
| |
if(ObjectFind(0,name)<0)
| |
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
| |
| |
int x=g_pLeft ?(g_px+px):(g_px+g_pw-px);
| |
int y=g_pUpper?(g_py+py):(g_py+g_ph-py);
| |
| |
ENUM_ANCHOR_POINT a=right?ANCHOR_RIGHT:ANCHOR_LEFT;
| |
| |
ObjectSetInteger(0,name,OBJPROP_CORNER,InpCorner);
| |
ObjectSetInteger(0,name,OBJPROP_ANCHOR,a);
| |
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
| |
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
| |
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fs);
| |
ObjectSetString(0,name,OBJPROP_FONT,font);
| |
ObjectSetString(0,name,OBJPROP_TEXT,text);
| |
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Erase the fan-cone objects (they are rebuilt each new bar). |
| |
//+------------------------------------------------------------------+
| |
void ClearCone()
| |
{
| |
for(int k=0;k<InpHorizon+2;k++)
| |
{
| |
ObjectDelete(0,g_pfx+"med_"+(string)k);
| |
ObjectDelete(0,g_pfx+"p25_"+(string)k);
| |
ObjectDelete(0,g_pfx+"p75_"+(string)k);
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Draw the fan cone. |
| |
//| The per-step distribution is in ATR units; multiply by the |
| |
//| CURRENT ATR to turn it into a price offset from the last close, |
| |
//| so the cone sits on the real price axis. Future bar times don't |
| |
//| exist yet, so their timestamps are extrapolated from the last |
| |
//| bar time + k * PeriodSeconds(). |
| |
//+------------------------------------------------------------------+
| |
void DrawCone(datetime lastTime,double lastClose,double curATR,color clr)
| |
{
| |
int ps=PeriodSeconds(_Period);
| |
double medPrev=lastClose,p25Prev=lastClose,p75Prev=lastClose;
| |
datetime tPrev=lastTime;
| |
| |
for(int k=1;k<=InpHorizon;k++)
| |
{
| |
SSfaStepStat st;
| |
if(!g_finder.StepStats(k,st))
| |
break;
| |
datetime tk=lastTime+(datetime)(k*ps);
| |
double med=lastClose+st.median_atr*curATR;
| |
double p25=lastClose+st.p25_atr *curATR;
| |
double p75=lastClose+st.p75_atr *curATR;
| |
| |
SetTrend(g_pfx+"med_"+(string)k,tPrev,medPrev,tk,med,clr,2,STYLE_SOLID);
| |
SetTrend(g_pfx+"p75_"+(string)k,tPrev,p75Prev,tk,p75,InpBandColor,1,STYLE_DOT);
| |
SetTrend(g_pfx+"p25_"+(string)k,tPrev,p25Prev,tk,p25,InpBandColor,1,STYLE_DOT);
| |
| |
tPrev=tk;
| |
medPrev=med;
| |
p25Prev=p25;
| |
p75Prev=p75;
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Draw the verdict panel. |
| |
//| |
| |
//| The layout is a single column with two alignment rails: dim |
| |
//| labels flush left, bright values flush right. Because the |
| |
//| values are anchored by their right edge rather than padded into |
| |
//| place, the columns stay true for any content without needing a |
| |
//| monospace font — only the symbolic word is monospaced, because |
| |
//| there it is the letters themselves that carry meaning. |
| |
//| |
| |
//| Weight follows importance. The median forward move gets its own |
| |
//| oversized row in the accent colour, since it is the one number |
| |
//| a reader wants at a glance; the up-rate gets a bar, because a |
| |
//| proportion is read faster as a length than as digits; and the |
| |
//| diagnostics that matter only when something looks wrong sit |
| |
//| below a divider in the smallest type. |
| |
//| |
| |
//| Everything is sized from measured text and laid out against a |
| |
//| running cursor, so changing a metric or a string reflows the |
| |
//| panel instead of overlapping it. |
| |
//+------------------------------------------------------------------+
| |
void DrawPanel(SSfaForwardStats &s,string word,string verdict,color clr)
| |
{
| |
const int fLab=8,fVal=9,fHdr=9,fHero=15,fChip=9;
| |
| |
//--- palette. The card must sit clearly ON the chart rather than in
| |
//--- it, so the fill is pushed away from typical chart backgrounds
| |
//--- and carried by a visible edge.
| |
color bgCol =(InpTheme==THEME_LIGHT)?C'255,255,255':C'26,28,36';
| |
color edgCol=(InpTheme==THEME_LIGHT)?C'208,213,222':C'56,61,76';
| |
color labCol=(InpTheme==THEME_LIGHT)?C'122,130,146':C'130,138,157';
| |
color valCol=(InpTheme==THEME_LIGHT)?C'22,26,35' :C'238,240,246';
| |
color divCol=(InpTheme==THEME_LIGHT)?C'230,233,240':C'48,53,66';
| |
color trkCol=(InpTheme==THEME_LIGHT)?C'232,235,242':C'54,59,73';
| |
| |
//--- prune share on this bar: what the lower bound actually saved
| |
int scanned=g_finder.Scanned();
| |
double pruneRate=(scanned>0)
| |
?100.0*(1.0-(double)g_finder.TrueCalcs()/(double)scanned)
| |
:0.0;
| |
| |
//--- header, hero and chip strings
| |
string hTitle="SFA ANALOG";
| |
string hSym =_Symbol+" · "+StringSubstr(EnumToString((ENUM_TIMEFRAMES)_Period),7);
| |
string heroL =StringFormat("MEDIAN %d BARS",InpHorizon);
| |
string heroV =(s.count>0)?StringFormat("%+.2f ATR",s.median_atr):"—";
| |
| |
//--- verdict chip. The arrow repeats the direction in a second channel
| |
//--- so the panel still reads correctly on a monochrome screenshot.
| |
string mark="•";
| |
if(s.verdict==SFA_ANALOG_BULLISH)
| |
mark="▲";
| |
if(s.verdict==SFA_ANALOG_BEARISH)
| |
mark="▼";
| |
string chip=verdict;
| |
StringToUpper(chip);
| |
| |
//--- body rows, as label/value pairs
| |
string lab[6],val[6];
| |
string vfont[6];
| |
lab[0]="BAND";
| |
val[0]=(s.count>0)?StringFormat("%+.2f … %+.2f",s.p25_atr,s.p75_atr):"—";
| |
lab[1]="UP RATE";
| |
val[1]=(s.count>0)?StringFormat("%.0f%%",100.0*s.up_rate):"—";
| |
lab[2]="WORD";
| |
val[2]=word;
| |
lab[3]="ANALOGS";
| |
val[3]=StringFormat("%d · avg %.2f",s.count,s.avg_distance);
| |
lab[4]="MCB BINS";
| |
val[4]=StringFormat("%d win · l=%d a=%d",g_finder.FitWindows(),
| |
InpWordLen,InpAlphabet);
| |
lab[5]="PRUNED";
| |
val[5]=StringFormat("%.0f%%",pruneRate);
| |
for(int i=0;i<6;i++)
| |
vfont[i]=(i==2)?F_MONO:F_UI;
| |
| |
//--- width: the widest label+gutter+value pair, header and hero included
| |
int need=P_MINW;
| |
for(int i=0;i<6;i++)
| |
{
| |
int w=TextW(lab[i],F_UI,fLab)+P_GAP+TextW(val[i],vfont[i],fVal);
| |
if(w>need)
| |
need=w;
| |
}
| |
int wh=TextW(hTitle,F_UI,fHdr)+P_GAP+TextW(hSym,F_UI,fLab);
| |
if(wh>need)
| |
need=wh;
| |
int wv=TextW(heroL,F_UI,fLab)+P_GAP+TextW(heroV,F_UI,fHero);
| |
if(wv>need)
| |
need=wv;
| |
int wc=30+TextW(chip,F_UI,fChip);
| |
if(wc>need)
| |
need=wc;
| |
| |
//--- publish the geometry the placement helpers work against
| |
g_px=InpPanelX;
| |
g_py=InpPanelY;
| |
g_pw=need+2*P_PAD;
| |
g_pLeft =(InpCorner==CORNER_LEFT_UPPER || InpCorner==CORNER_LEFT_LOWER);
| |
g_pUpper=(InpCorner==CORNER_LEFT_UPPER || InpCorner==CORNER_RIGHT_UPPER);
| |
| |
//--- walk the layout with a running cursor so every gap is stated once
| |
int rail=g_pw-P_PAD; // x of the right-hand value rail
| |
int y=P_HDR+14; // cursor, starts under the header strip
| |
int yHero=y;
| |
y+=P_HERO+P_SEC;
| |
int yDiv1=y;
| |
y+=1+P_SEC;
| |
int yRow0=y;
| |
y+=P_ROW; // BAND
| |
int yRow1=y;
| |
y+=P_ROW+10; // UP RATE, then clear air for its bar
| |
int yBar=y;
| |
y+=P_BAR+P_SEC;
| |
int yDiv2=y;
| |
y+=1+P_SEC;
| |
int yRow2=y;
| |
y+=P_ROW*4+P_SEC; // WORD / ANALOGS / MCB BINS / PRUNED
| |
int yChip=y;
| |
g_ph=yChip+P_CHIP+P_PAD;
| |
| |
//--- shell and header strip
| |
PanelRect("bg",0,0,g_pw,g_ph,bgCol,edgCol);
| |
PanelRect("hdr",0,0,g_pw,P_HDR,clr,clr);
| |
PanelText("hT",P_PAD,P_HDR/2,hTitle,clrWhite,F_UI,fHdr,false);
| |
PanelText("hS",rail,P_HDR/2,hSym,clrWhite,F_UI,fLab,true);
| |
| |
//--- hero row: the one number worth reading from across the room.
| |
//--- Label and value share one centre line, so the small caps sit
| |
//--- optically level with the middle of the large figure.
| |
PanelText("heroL",P_PAD,yHero+P_HERO/2,heroL,labCol,F_UI,fLab,false);
| |
PanelText("heroV",rail,yHero+P_HERO/2,heroV,
| |
(s.count>0)?clr:labCol,F_UI,fHero,true);
| |
| |
PanelRect("d1",P_PAD,yDiv1,g_pw-2*P_PAD,1,divCol,divCol);
| |
| |
//--- forward-distribution rows
| |
PanelText("l0",P_PAD,yRow0+P_ROW/2,lab[0],labCol,F_UI,fLab,false);
| |
PanelText("v0",rail,yRow0+P_ROW/2,val[0],valCol,F_UI,fVal,true);
| |
PanelText("l1",P_PAD,yRow1+P_ROW/2,lab[1],labCol,F_UI,fLab,false);
| |
PanelText("v1",rail,yRow1+P_ROW/2,val[1],valCol,F_UI,fVal,true);
| |
| |
//--- up-rate bar: a proportion reads faster as a length than as digits
| |
int barW=g_pw-2*P_PAD;
| |
int fill=(s.count>0)?(int)MathRound(barW*MathMax(0.0,MathMin(1.0,s.up_rate)))
| |
:0;
| |
PanelRect("barBg",P_PAD,yBar,barW,P_BAR,trkCol,trkCol);
| |
if(fill>0)
| |
PanelRect("barFg",P_PAD,yBar,fill,P_BAR,clr,clr);
| |
else
| |
ObjectDelete(0,g_pfx+"barFg");
| |
| |
PanelRect("d2",P_PAD,yDiv2,g_pw-2*P_PAD,1,divCol,divCol);
| |
| |
//--- diagnostics: only interesting when something looks wrong
| |
for(int i=2;i<6;i++)
| |
{
| |
int ry=yRow2+P_ROW*(i-2)+P_ROW/2;
| |
PanelText("l"+(string)i,P_PAD,ry,lab[i],labCol,F_UI,fLab,false);
| |
PanelText("v"+(string)i,rail,ry,val[i],valCol,vfont[i],fVal,true);
| |
}
| |
| |
PanelRect("chip",P_PAD,yChip,g_pw-2*P_PAD,P_CHIP,clr,clr);
| |
PanelText("chipM",P_PAD+12,yChip+P_CHIP/2,mark,clrWhite,F_UI,fChip,false);
| |
PanelText("chipT",P_PAD+31,yChip+P_CHIP/2,chip,clrWhite,F_UI,fChip,false);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Map a verdict to its display color. |
| |
//+------------------------------------------------------------------+
| |
color VerdictColor(ENUM_SFA_VERDICT v)
| |
{
| |
switch(v)
| |
{
| |
case SFA_ANALOG_BULLISH:
| |
return InpUpColor;
| |
case SFA_ANALOG_BEARISH:
| |
return InpDownColor;
| |
default:
| |
return InpFlatColor; // no-edge / too-few / no-data
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Main: run the search on each new bar and redraw cone + panel. |
| |
//+------------------------------------------------------------------+
| |
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(!NewBar() && prev_calculated>0)
| |
return(rates_total);
| |
| |
double c[],atr[];
| |
int n=LoadHistory(c,atr);
| |
if(n<=0)
| |
return(rates_total);
| |
| |
//--- query ends at the newest loaded (closed) bar
| |
bool ok=g_finder.Find(c,atr,n,n-1);
| |
| |
SSfaForwardStats s=g_finder.Stats();
| |
color clr=VerdictColor(s.verdict);
| |
| |
//--- last CLOSED bar anchors the cone (index 1 on the live chart)
| |
datetime lastTime =iTime(_Symbol,_Period,1);
| |
double lastClose=iClose(_Symbol,_Period,1);
| |
double curATR =(atr[n-1]>0.0)?atr[n-1]:0.0;
| |
| |
ClearCone();
| |
//--- draw the cone only when there is a high-conviction edge
| |
if(ok && curATR>0.0 &&
| |
(s.verdict==SFA_ANALOG_BULLISH || s.verdict==SFA_ANALOG_BEARISH))
| |
DrawCone(lastTime,lastClose,curATR,clr);
| |
| |
DrawPanel(s,ok?g_finder.QueryWord():"-",g_finder.VerdictText(),clr);
| |
| |
ChartRedraw();
| |
return(rates_total);
| |
}
| |
//+------------------------------------------------------------------+
|