//+------------------------------------------------------------------+ //| ADWyckoffSignificantBarInversion.mq5 | //| Standalone significant-bar quality and movement inversion | //+------------------------------------------------------------------+ #property copyright "AD Institutional Indicators" #property link "" #property version "1.00" #property description "Wyckoff significant bar + control flip" #property indicator_separate_window #property indicator_buffers 5 #property indicator_plots 5 #property indicator_label1 "SignificantBarQuality" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGold #property indicator_width1 1 #property indicator_label2 "BullishSignificantBar" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLime #property indicator_width2 1 #property indicator_label3 "BearishSignificantBar" #property indicator_type3 DRAW_LINE #property indicator_color3 clrRed #property indicator_width3 1 #property indicator_label4 "BullishControlFlip" #property indicator_type4 DRAW_LINE #property indicator_color4 clrAqua #property indicator_width4 1 #property indicator_label5 "BearishControlFlip" #property indicator_type5 DRAW_LINE #property indicator_color5 clrOrange #property indicator_width5 1 input int InpLookback = 50; input double InpRangeSignificant = 1.2; input double InpVolumeHigh = 1.5; input double InpATR = 0.5; input int InpContextMode = 0; // 0=FixedBars, 1=Session input int InpSessionType = 5; // 1..10 input int InpSessionCount = 1; // 1..20 double BufQuality[]; double BufBullSig[]; double BufBearSig[]; double BufBullFlip[]; double BufBearFlip[]; int ExtLookback; double ExtRangeSignificant; double ExtVolumeHigh; double ExtATR; int ExtContextMode; int ExtSessionType; int ExtSessionCount; bool ExtHasBull = false; bool ExtHasBear = false; double ExtLastBullHigh = 0.0; double ExtLastBullLow = 0.0; double ExtLastBearHigh = 0.0; double ExtLastBearLow = 0.0; int IsPreviousSessionTypeMT5(const int sessionType) { return (sessionType >= 1 && sessionType <= 4); } int IsZigZagSessionTypeMT5(const int sessionType) { return (sessionType == 9 || sessionType == 10); } int MapBaseCalendarTypeMT5(const int sessionType) { if(sessionType == 1 || sessionType == 5) return 1; if(sessionType == 2 || sessionType == 6) return 2; if(sessionType == 3 || sessionType == 7) return 3; if(sessionType == 4 || sessionType == 8) return 4; return 1; } datetime DayStartMT5(const datetime t) { MqlDateTime dt; TimeToStruct(t, dt); dt.hour = 0; dt.min = 0; dt.sec = 0; return StructToTime(dt); } datetime AddMonthsSafeMT5(const datetime t, const int months) { MqlDateTime dt; TimeToStruct(t, dt); int m = dt.mon + months; while(m > 12) { m -= 12; dt.year++; } while(m < 1) { m += 12; dt.year--; } dt.mon = m; dt.day = 1; dt.hour = 0; dt.min = 0; dt.sec = 0; return StructToTime(dt); } datetime ResolveCalendarContextStartMT5(const datetime currentTime) { int st = ExtSessionType; int baseType = MapBaseCalendarTypeMT5(st); datetime currentStart = DayStartMT5(currentTime); if(baseType == 2) { MqlDateTime w; TimeToStruct(currentStart, w); int dow = w.day_of_week; int back = (dow == 0) ? 6 : (dow - 1); currentStart -= (datetime)(back * 86400); } else if(baseType == 3) { MqlDateTime m; TimeToStruct(currentStart, m); m.day = 1; m.hour = 0; m.min = 0; m.sec = 0; currentStart = StructToTime(m); } else if(baseType == 4) { MqlDateTime y; TimeToStruct(currentStart, y); y.mon = 1; y.day = 1; y.hour = 0; y.min = 0; y.sec = 0; currentStart = StructToTime(y); } int shift = IsPreviousSessionTypeMT5(st) ? -ExtSessionCount : -(ExtSessionCount - 1); if(baseType == 1) return currentStart + (datetime)(shift * 86400); if(baseType == 2) return currentStart + (datetime)(shift * 7 * 86400); if(baseType == 3) return AddMonthsSafeMT5(currentStart, shift); return AddMonthsSafeMT5(currentStart, shift * 12); } void ResetState() { ExtHasBull = false; ExtHasBear = false; ExtLastBullHigh = 0.0; ExtLastBullLow = 0.0; ExtLastBearHigh = 0.0; ExtLastBearLow = 0.0; } void OnInit() { ExtLookback = (int)MathMax(5, MathMin(200, InpLookback)); ExtRangeSignificant = MathMax(0.5, MathMin(3.0, InpRangeSignificant)); ExtVolumeHigh = MathMax(0.5, MathMin(3.0, InpVolumeHigh)); ExtATR = MathMax(0.1, MathMin(3.0, InpATR)); ExtContextMode = (InpContextMode == 1) ? 1 : 0; ExtSessionType = (int)MathMax(1, MathMin(10, InpSessionType)); ExtSessionCount = (int)MathMax(1, MathMin(20, InpSessionCount)); SetIndexBuffer(0, BufQuality, INDICATOR_DATA); SetIndexBuffer(1, BufBullSig, INDICATOR_DATA); SetIndexBuffer(2, BufBearSig, INDICATOR_DATA); SetIndexBuffer(3, BufBullFlip, INDICATOR_DATA); SetIndexBuffer(4, BufBearFlip, INDICATOR_DATA); ArraySetAsSeries(BufQuality, true); ArraySetAsSeries(BufBullSig, true); ArraySetAsSeries(BufBearSig, true); ArraySetAsSeries(BufBullFlip, true); ArraySetAsSeries(BufBearFlip, true); PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, ExtLookback); PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, ExtLookback); PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, ExtLookback); PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, ExtLookback); PlotIndexSetInteger(4, PLOT_DRAW_BEGIN, ExtLookback); IndicatorSetInteger(INDICATOR_DIGITS, 3); IndicatorSetString(INDICATOR_SHORTNAME, "WYSB(" + IntegerToString(ExtLookback) + ")"); ResetState(); } void OnDeinit(const int reason) { } int ResolveMaxShift(const int i, const int rates_total, const datetime &time[]) { int available = rates_total - 1 - i; if(available <= 0) return 0; if(ExtContextMode != 1) return MathMin(available, ExtLookback - 1); if(IsZigZagSessionTypeMT5(ExtSessionType)) return MathMin(available, ExtLookback * ExtSessionCount - 1); datetime start = ResolveCalendarContextStartMT5(time[i]); int maxShift = 0; for(int s = 0; s <= available; s++) { int idx = i + s; if(time[idx] < start) break; maxShift = s; } return MathMax(0, maxShift); } 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 < 20) return 0; ArraySetAsSeries(time, true); ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); ArraySetAsSeries(tick_volume, true); ResetState(); ArrayInitialize(BufQuality, 0.0); ArrayInitialize(BufBullSig, 0.0); ArrayInitialize(BufBearSig, 0.0); ArrayInitialize(BufBullFlip, 0.0); ArrayInitialize(BufBearFlip, 0.0); int startBar = rates_total - 1; for(int i = startBar; i >= 0 && !IsStopped(); i--) { int maxShift = ResolveMaxShift(i, rates_total, time); if(maxShift < 1) { BufQuality[i] = 0.0; continue; } double sumRange = 0.0; double sumVol = 0.0; int n = 0; for(int s = 0; s <= maxShift; s++) { int idx = i + s; double r = high[idx] - low[idx]; double v = MathMax((double)tick_volume[idx], 1.0); sumRange += MathMax(r, 0.000001); sumVol += v; n++; } if(n <= 0) continue; double avgRange = sumRange / n; double avgVol = sumVol / n; if(avgRange <= 0.0 || avgVol <= 0.0) continue; double barRange = MathMax(high[i] - low[i], 0.000001); double barVol = MathMax((double)tick_volume[i], 1.0); double closePos = (close[i] - low[i]) / barRange; if(closePos < 0.0) closePos = 0.0; if(closePos > 1.0) closePos = 1.0; double rangeRatio = barRange / avgRange; double volRatio = barVol / avgVol; int available = rates_total - 1 - i; int atrN = MathMin(14, available); if(atrN <= 0) atrN = 1; double sumTR = 0.0; for(int t = 0; t < atrN; t++) { int idx = i + t; double h = high[idx]; double l = low[idx]; double pc = (idx + 1 < rates_total) ? close[idx + 1] : close[idx]; double tr = MathMax(h - l, MathMax(MathAbs(h - pc), MathAbs(l - pc))); sumTR += tr; } double atr = sumTR / atrN; double atrRatio = barRange / MathMax(atr * ExtATR, 0.000001); double fallbackResistance = (i + 1 < rates_total) ? high[i + 1] : high[i]; double fallbackSupport = (i + 1 < rates_total) ? low[i + 1] : low[i]; double priorResistance = ExtHasBear ? ExtLastBearHigh : fallbackResistance; double priorSupport = ExtHasBull ? ExtLastBullLow : fallbackSupport; bool bullStructuralBreak = (close[i] > priorResistance && high[i] > priorResistance); bool bearStructuralBreak = (close[i] < priorSupport && low[i] < priorSupport); bool bullSig = (rangeRatio >= ExtRangeSignificant && volRatio >= ExtVolumeHigh && closePos >= 0.70 && close[i] > open[i] && atrRatio >= 1.0 && bullStructuralBreak); bool bearSig = (rangeRatio >= ExtRangeSignificant && volRatio >= ExtVolumeHigh && closePos <= 0.30 && close[i] < open[i] && atrRatio >= 1.0 && bearStructuralBreak); double bullScore = ((rangeRatio / ExtRangeSignificant) + (volRatio / ExtVolumeHigh) + closePos) / 3.0; double bearScore = ((rangeRatio / ExtRangeSignificant) + (volRatio / ExtVolumeHigh) + (1.0 - closePos)) / 3.0; if(bullScore < 0.0) bullScore = 0.0; if(bearScore < 0.0) bearScore = 0.0; if(bullScore > 2.0) bullScore = 2.0; if(bearScore > 2.0) bearScore = 2.0; double quality = 0.0; if(bullSig) quality = bullScore; else if(bearSig) quality = -bearScore; if(bullSig) { ExtHasBull = true; ExtLastBullHigh = high[i]; ExtLastBullLow = low[i]; } if(bearSig) { ExtHasBear = true; ExtLastBearHigh = high[i]; ExtLastBearLow = low[i]; } double prevClose = (i + 1 < rates_total) ? close[i + 1] : close[i]; bool bullFlip = (ExtHasBear && prevClose <= ExtLastBearHigh && close[i] > ExtLastBearHigh); bool bearFlip = (ExtHasBull && prevClose >= ExtLastBullLow && close[i] < ExtLastBullLow); // Normalize: guard against NaN/Inf from edge-case inputs if(!MathIsValidNumber(quality)) quality = 0.0; BufQuality[i] = quality; BufBullSig[i] = bullSig ? 1.0 : 0.0; BufBearSig[i] = bearSig ? 1.0 : 0.0; BufBullFlip[i] = bullFlip ? 1.0 : 0.0; BufBearFlip[i] = bearFlip ? 1.0 : 0.0; } return rates_total; }