152 lines
6.7 KiB
MQL5
// Educational companion: confirmed swing zones, not the Jericho Gold engine.
| |||
#property copyright "Support and Resistance Research"
| |||
#property version "1.00"
| |||
#property description "Confirmed swing zones for the Python and MT5 research article"
| |||
#property indicator_chart_window
| |||
#property indicator_plots 0
| |||
#property indicator_buffers 0
| |||
| |||
input int InpHistoryBars=2000; // Recent chart bars to examine
| |||
input int InpSwingBars=2; // Closed candles on EACH side of a pivot
| |||
input int InpATRPeriod=14; // Simple mean true range, not Wilder smoothing
| |||
input double InpATRHalfWidth=0.25; // Zone half-width / confirmation ATR
| |||
input double InpMinimumHalfWidthPoints=10; // Minimum half-width in broker points
| |||
input int InpMaximumZones=20; // Latest zones in total
| |||
input bool InpShowSupport=true;
| |||
input bool InpShowResistance=true;
| |||
input bool InpInvalidateOnBreak=true;
| |||
input int InpBreakCloses=2; // Consecutive closes beyond the whole zone
| |||
input bool InpShowBrokenZones=false;
| |||
input int InpExtendBars=200; // Active rectangles extend past current bar
| |||
input bool InpFillZones=true;
| |||
input color InpSupportColor=C'190,222,208';
| |||
input color InpResistanceColor=C'237,200,200';
| |||
| |||
struct SRZone
| |||
{
| |||
datetime pivot,available,ended;
| |||
double lower,upper;
| |||
int side; // 1 support, -1 resistance
| |||
bool broken;
| |||
};
| |||
string srPrefix="";
| |||
datetime srLastBar=0;
| |||
int srLastCount=0;
| |||
| |||
// SR_CORE_BEGIN -- same function bodies are exercised by portable checks.
| |||
bool SRPivot(const MqlRates &r[],const int i,const int wing,const int side)
| |||
{
| |||
for(int k=1;k<=wing;k++)
| |||
{
| |||
if(side>0 && (r[i].low>=r[i-k].low || r[i].low>=r[i+k].low)) return false;
| |||
if(side<0 && (r[i].high<=r[i-k].high || r[i].high<=r[i+k].high)) return false;
| |||
}
| |||
return true; // Strict extremes: equal highs/lows do not qualify.
| |||
}
| |||
double SRHalfWidth(const MqlRates &r[],const int confirmed,const int period,
| |||
const double multiplier,const double minimum)
| |||
{
| |||
double total=0;
| |||
for(int j=confirmed-period+1;j<=confirmed;j++)
| |||
total+=MathMax(r[j].high-r[j].low,
| |||
MathMax(MathAbs(r[j].high-r[j-1].close),MathAbs(r[j].low-r[j-1].close)));
| |||
return MathMax(minimum,multiplier*total/period);
| |||
}
| |||
int SRBreakIndex(const MqlRates &r[],const int first,const int last,const int side,
| |||
const double lower,const double upper,const int needed)
| |||
{
| |||
int run=0;
| |||
for(int j=first;j<=last;j++)
| |||
{
| |||
bool outside=(side>0)?r[j].close<lower:r[j].close>upper;
| |||
run=outside?run+1:0;
| |||
if(run>=needed) return j;
| |||
}
| |||
return -1;
| |||
}
| |||
// SR_CORE_END
| |||
| |||
void SRClear() { if(srPrefix!="") ObjectsDeleteAll(0,srPrefix,0,-1); }
| |||
| |||
bool SRDraw(const SRZone &z,const int serial,const datetime future)
| |||
{
| |||
string name=srPrefix+IntegerToString(serial);
| |||
datetime end=z.broken?z.ended:future;
| |||
if(!ObjectCreate(0,name,OBJ_RECTANGLE,0,z.available,z.lower,end,z.upper))
| |||
{ Print("SR zones: drawing request failed, error ",GetLastError()); return false; }
| |||
color ink=z.side>0?InpSupportColor:InpResistanceColor;
| |||
bool ok=true;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_COLOR,ink)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_FILL,InpFillZones)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_BACK,true)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_HIDDEN,true)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_WIDTH,1)&&ok;
| |||
ok=ObjectSetInteger(0,name,OBJPROP_STYLE,z.broken?STYLE_DOT:STYLE_SOLID)&&ok;
| |||
string note=(z.side>0?"Support":"Resistance");
| |||
note+=(z.broken?" (broken)":" (active)");
| |||
note+=
| |||
"\nPivot: "+TimeToString(z.pivot)+"\nAvailable: "+TimeToString(z.available)+
| |||
"\nBounds: "+DoubleToString(z.lower,_Digits)+" / "+DoubleToString(z.upper,_Digits);
| |||
ok=ObjectSetString(0,name,OBJPROP_TOOLTIP,note)&&ok;
| |||
return ok;
| |||
}
| |||
| |||
int OnInit()
| |||
{
| |||
if(InpHistoryBars<100 || InpHistoryBars>10000 || InpSwingBars<1 || InpSwingBars>20 ||
| |||
InpATRPeriod<1 || InpATRPeriod>200 || InpMaximumZones<1 || InpMaximumZones>100 ||
| |||
InpBreakCloses<1 || InpBreakCloses>20 || InpExtendBars<1 || InpExtendBars>10000 ||
| |||
!MathIsValidNumber(InpATRHalfWidth) || InpATRHalfWidth<=0 ||
| |||
!MathIsValidNumber(InpMinimumHalfWidthPoints) || InpMinimumHalfWidthPoints<=0 ||
| |||
InpHistoryBars<MathMax(2*InpSwingBars,InpATRPeriod)+2)
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
srPrefix="SRR_"+(string)GetMicrosecondCount()+"_";
| |||
srLastBar=0; srLastCount=0;
| |||
IndicatorSetString(INDICATOR_SHORTNAME,"SR Research Zones");
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
void OnDeinit(const int reason) { SRClear(); ChartRedraw(0); }
| |||
| |||
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[])
| |||
{
| |||
datetime current=iTime(_Symbol,_Period,0);
| |||
if(current<=0) return prev_calculated;
| |||
if(prev_calculated>0 && current==srLastBar && rates_total==srLastCount) return rates_total;
| |||
int wanted=MathMin(rates_total,InpHistoryBars);
| |||
MqlRates r[]; ArraySetAsSeries(r,false);
| |||
int count=CopyRates(_Symbol,_Period,0,wanted,r);
| |||
if(count<MathMax(2*InpSwingBars,InpATRPeriod)+2) return prev_calculated;
| |||
if(r[count-1].time!=current) return prev_calculated;
| |||
SRZone zones[];
| |||
int lastClosed=count-2; // Excludes the forming candle from every calculation.
| |||
int first=MathMax(InpSwingBars,InpATRPeriod-InpSwingBars);
| |||
for(int i=first;i+InpSwingBars<=lastClosed;i++)
| |||
{
| |||
int confirmed=i+InpSwingBars;
| |||
for(int side=1;side>=-1;side-=2)
| |||
{
| |||
if((side>0&&!InpShowSupport)||(side<0&&!InpShowResistance)) continue;
| |||
if(!SRPivot(r,i,InpSwingBars,side)) continue;
| |||
SRZone z;
| |||
z.side=side; z.pivot=r[i].time; z.available=r[confirmed+1].time;
| |||
double anchor=side>0?r[i].low:r[i].high;
| |||
double width=SRHalfWidth(r,confirmed,InpATRPeriod,InpATRHalfWidth,InpMinimumHalfWidthPoints*_Point);
| |||
z.lower=anchor-width; z.upper=anchor+width;
| |||
int broken=InpInvalidateOnBreak?SRBreakIndex(r,confirmed+1,lastClosed,side,z.lower,z.upper,InpBreakCloses):-1;
| |||
z.broken=broken>=0; z.ended=z.broken?r[broken+1].time:0;
| |||
if(z.broken&&!InpShowBrokenZones) continue;
| |||
int n=ArraySize(zones); ArrayResize(zones,n+1); zones[n]=z;
| |||
}
| |||
}
| |||
SRClear();
| |||
datetime future=current+(datetime)((long)PeriodSeconds(_Period)*InpExtendBars);
| |||
int firstDraw=MathMax(0,ArraySize(zones)-InpMaximumZones);
| |||
bool drawn=true;
| |||
for(int i=firstDraw;i<ArraySize(zones);i++) drawn=SRDraw(zones[i],i,future)&&drawn;
| |||
if(drawn) { srLastBar=current; srLastCount=rates_total; }
| |||
ChartRedraw(0);
| |||
return drawn?rates_total:0; // Retry failed requests on a later tick.
| |||
}
|