// 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].closeupper; 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 || InpHistoryBars0 && 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=-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