| // 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.
|
| }
|