//+------------------------------------------------------------------+ //| ADCumulativeDelta.mq5| //| Wyckoff Cumulative Delta / Flow Pressure | //+------------------------------------------------------------------+ #property copyright "AD Institutional Indicators" #property link "" #property version "1.00" #property description "Wyckoff Cumulative Delta — tick-volume and price-flow order-flow proxy" #property indicator_separate_window #property indicator_buffers 6 #property indicator_plots 6 #property indicator_minimum -2.0 #property indicator_maximum 2.0 #property indicator_level1 0 #property indicator_label1 "Pressure" #property indicator_type1 DRAW_LINE #property indicator_color1 clrWhite #property indicator_width1 1 #property indicator_label2 "CumulativeDelta" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGold #property indicator_width2 1 #property indicator_label3 "BullishPressure" #property indicator_type3 DRAW_LINE #property indicator_color3 clrLime #property indicator_width3 1 #property indicator_label4 "BearishPressure" #property indicator_type4 DRAW_LINE #property indicator_color4 clrRed #property indicator_width4 1 #property indicator_label5 "Absorption" #property indicator_type5 DRAW_LINE #property indicator_color5 clrAqua #property indicator_width5 1 #property indicator_label6 "Initiative" #property indicator_type6 DRAW_LINE #property indicator_color6 clrMagenta #property indicator_width6 1 input int InpLookbackPeriod = 50; input double InpVolumeClimaxMultiplier = 2.5; input double InpVolumeHighMultiplier = 1.5; input double InpRangeClimaxMultiplier = 1.8; input double InpRangeSignificantMult = 1.2; input double InpSTVolumeRatio = 0.6; input double InpATRMultiplier = 0.5; input int InpContextMode = 0; input int InpSessionType = 5; input int InpSessionCount = 1; double ExtPressureBuffer[]; double ExtCumulativeDeltaBuffer[]; double ExtBullishPressureBuffer[]; double ExtBearishPressureBuffer[]; double ExtAbsorptionBuffer[]; double ExtInitiativeBuffer[]; double H[]; double L[]; double O[]; double C[]; double V[]; datetime T[]; int ExtLookbackPeriod; double ExtVolumeClimaxMultiplier; double ExtVolumeHighMultiplier; double ExtRangeClimaxMultiplier; double ExtRangeSignificantMult; double ExtSTVolumeRatio; double ExtATRMultiplier; int ExtContextMode; int ExtSessionType; int ExtSessionCount; int ClampInt(const int value, const int minValue, const int maxValue) { return (int)MathMax(minValue, MathMin(maxValue, value)); } double ClampDouble(const double value, const double minValue, const double maxValue) { return MathMax(minValue, MathMin(maxValue, value)); } bool IsPreviousSessionTypeMT5(const int sessionType) { return (sessionType >= 1 && sessionType <= 4); } bool 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 ShiftCalendarStartMT5(const datetime start, const int baseType, const int shiftCount) { if(shiftCount == 0) return start; if(baseType == 1) return start + (datetime)(shiftCount * 86400); if(baseType == 2) return start + (datetime)(shiftCount * 7 * 86400); if(baseType == 3) return AddMonthsSafeMT5(start, shiftCount); return AddMonthsSafeMT5(start, shiftCount * 12); } 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); return ShiftCalendarStartMT5(currentStart, baseType, shift); } void ResetState() { ArrayInitialize(ExtPressureBuffer, 0); ArrayInitialize(ExtCumulativeDeltaBuffer, 0); ArrayInitialize(ExtBullishPressureBuffer, 0); ArrayInitialize(ExtBearishPressureBuffer, 0); ArrayInitialize(ExtAbsorptionBuffer, 0); ArrayInitialize(ExtInitiativeBuffer, 0); } void SetOutputs(const int outIndex, const double pressure, const double cumulativeDelta, const double bullishPressure, const double bearishPressure, const double absorption, const double initiative) { ExtPressureBuffer[outIndex] = pressure; ExtCumulativeDeltaBuffer[outIndex] = cumulativeDelta; ExtBullishPressureBuffer[outIndex] = bullishPressure; ExtBearishPressureBuffer[outIndex] = bearishPressure; ExtAbsorptionBuffer[outIndex] = absorption; ExtInitiativeBuffer[outIndex] = initiative; } int OnInit() { ExtLookbackPeriod = ClampInt(InpLookbackPeriod, 5, 200); ExtVolumeClimaxMultiplier = ClampDouble(InpVolumeClimaxMultiplier, 1.0, 5.0); ExtVolumeHighMultiplier = ClampDouble(InpVolumeHighMultiplier, 0.5, 3.0); ExtRangeClimaxMultiplier = ClampDouble(InpRangeClimaxMultiplier, 1.0, 5.0); ExtRangeSignificantMult = ClampDouble(InpRangeSignificantMult, 0.5, 3.0); ExtSTVolumeRatio = ClampDouble(InpSTVolumeRatio, 0.1, 1.0); ExtATRMultiplier = ClampDouble(InpATRMultiplier, 0.1, 3.0); ExtContextMode = (InpContextMode == 1) ? 1 : 0; ExtSessionType = ClampInt(InpSessionType, 1, 10); ExtSessionCount = ClampInt(InpSessionCount, 1, 20); SetIndexBuffer(0, ExtPressureBuffer, INDICATOR_DATA); SetIndexBuffer(1, ExtCumulativeDeltaBuffer, INDICATOR_DATA); SetIndexBuffer(2, ExtBullishPressureBuffer, INDICATOR_DATA); SetIndexBuffer(3, ExtBearishPressureBuffer, INDICATOR_DATA); SetIndexBuffer(4, ExtAbsorptionBuffer, INDICATOR_DATA); SetIndexBuffer(5, ExtInitiativeBuffer, INDICATOR_DATA); ArraySetAsSeries(ExtPressureBuffer, true); ArraySetAsSeries(ExtCumulativeDeltaBuffer, true); ArraySetAsSeries(ExtBullishPressureBuffer, true); ArraySetAsSeries(ExtBearishPressureBuffer, true); ArraySetAsSeries(ExtAbsorptionBuffer, true); ArraySetAsSeries(ExtInitiativeBuffer, true); IndicatorSetInteger(INDICATOR_DIGITS, 3); IndicatorSetString(INDICATOR_SHORTNAME, "CVD(" + IntegerToString(ExtLookbackPeriod) + "," + DoubleToString(ExtVolumeClimaxMultiplier, 2) + "," + DoubleToString(ExtVolumeHighMultiplier, 2) + "," + DoubleToString(ExtRangeClimaxMultiplier, 2) + "," + DoubleToString(ExtRangeSignificantMult, 2) + ")"); PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, ExtLookbackPeriod); PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, ExtLookbackPeriod); PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, ExtLookbackPeriod); PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, ExtLookbackPeriod); PlotIndexSetInteger(4, PLOT_DRAW_BEGIN, ExtLookbackPeriod); PlotIndexSetInteger(5, PLOT_DRAW_BEGIN, ExtLookbackPeriod); ResetState(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) {} double ComputeATRChrono(const int i, const int period) { int available = MathMin(period, i); if(available <= 0) return H[i] - L[i]; double sum = 0.0; for(int k = 0; k < available; k++) { int idx = i - k; double hi = H[idx]; double lo = L[idx]; double prevClose = (idx > 0) ? C[idx - 1] : hi; double tr = MathMax(hi - lo, MathMax(MathAbs(hi - prevClose), MathAbs(lo - prevClose))); sum += tr; } return sum / available; } 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 < ExtLookbackPeriod + 2) return(0); ArraySetAsSeries(time, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(tick_volume, true); ArraySetAsSeries(volume, true); ArrayResize(H, rates_total); ArrayResize(L, rates_total); ArrayResize(O, rates_total); ArrayResize(C, rates_total); ArrayResize(V, rates_total); ArrayResize(T, rates_total); ResetState(); for(int chrono = 0; chrono < rates_total; chrono++) { int si = rates_total - 1 - chrono; H[chrono] = high[si]; L[chrono] = low[si]; O[chrono] = open[si]; C[chrono] = close[si]; V[chrono] = (double)MathMax(tick_volume[si], 1); T[chrono] = time[si]; } for(int chrono = 0; chrono < rates_total && !IsStopped(); chrono++) { int si = rates_total - 1 - chrono; int start = 0; if(ExtContextMode == 0) { start = MathMax(0, chrono - ExtLookbackPeriod + 1); } else if(IsZigZagSessionTypeMT5(ExtSessionType)) { start = MathMax(0, chrono - (ExtLookbackPeriod * ExtSessionCount) + 1); } else { datetime sessionStart = ResolveCalendarContextStartMT5(T[chrono]); for(int j = chrono; j >= 0; j--) { if(T[j] < sessionStart) { start = j + 1; break; } start = j; } } double sumRange = 0.0; double sumVolume = 0.0; double cumulativeDelta = 0.0; double bullishDelta = 0.0; double bearishDelta = 0.0; int count = 0; for(int k = start; k <= chrono; k++) { double hi = H[k]; double lo = L[k]; double op = O[k]; double cl = C[k]; double vol = MathMax(V[k], 1.0); double barRange = MathMax(hi - lo, 0.000001); double closePos = ClampDouble((cl - lo) / barRange, 0.0, 1.0); double bodyBias = ClampDouble((cl - op) / barRange, -1.0, 1.0); double priceStep = (k > 0) ? (cl - C[k - 1]) : (cl - op); double flowBias = ClampDouble(priceStep / barRange, -1.0, 1.0); double signedDelta = vol * ClampDouble(0.7 * flowBias + 0.3 * bodyBias, -1.0, 1.0); sumRange += barRange; sumVolume += vol; cumulativeDelta += signedDelta; if(signedDelta >= 0.0) bullishDelta += signedDelta; else bearishDelta += -signedDelta; count++; } if(count <= 0 || !MathIsValidNumber(sumRange) || !MathIsValidNumber(sumVolume) || sumRange <= 0.0 || sumVolume <= 0.0) { SetOutputs(si, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); continue; } double avgRange = sumRange / count; double avgVolume = sumVolume / count; if(!MathIsValidNumber(avgRange) || !MathIsValidNumber(avgVolume) || avgRange <= 0.0 || avgVolume <= 0.0) { SetOutputs(si, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); continue; } double absorptionSum = 0.0; double initiativeSum = 0.0; double atrNow = ComputeATRChrono(chrono, 14); double atrRatio = (H[chrono] - L[chrono]) / MathMax(atrNow * ExtATRMultiplier, 0.000001); for(int k = start; k <= chrono; k++) { double hi = H[k]; double lo = L[k]; double op = O[k]; double cl = C[k]; double vol = MathMax(V[k], 1.0); double barRange = MathMax(hi - lo, 0.000001); double closePos = ClampDouble((cl - lo) / barRange, 0.0, 1.0); double rangeRatio = barRange / avgRange; double volRatio = vol / avgVolume; double midpointDistance = MathAbs(closePos - 0.5) * 2.0; bool absorptionHit = volRatio >= ExtVolumeHighMultiplier && rangeRatio <= ExtRangeSignificantMult && closePos >= 0.35 && closePos <= 0.65; double absorptionScore = absorptionHit ? ClampDouble((volRatio / MathMax(rangeRatio, 0.000001)) / MathMax(ExtVolumeClimaxMultiplier, 0.000001), 0.0, 2.0) : 0.0; bool initiativeHit = volRatio >= ExtVolumeClimaxMultiplier && rangeRatio >= ExtRangeClimaxMultiplier && atrRatio >= 1.0 && (closePos >= 0.70 || closePos <= 0.30); double initiativeScore = initiativeHit ? ClampDouble((volRatio * rangeRatio) / MathMax(ExtVolumeHighMultiplier, 0.000001), 0.0, 2.0) * (closePos >= 0.5 ? 1.0 : -1.0) : 0.0; if(volRatio <= ExtSTVolumeRatio && rangeRatio <= ExtRangeSignificantMult && midpointDistance <= 0.5) absorptionScore += 0.10 * (1.0 - midpointDistance); absorptionSum += absorptionScore; initiativeSum += initiativeScore; } double normalizedCumulativeDelta = ClampDouble((cumulativeDelta / MathMax(sumVolume, 0.000001)) * 2.0, -2.0, 2.0); double pressure = ClampDouble(normalizedCumulativeDelta + (initiativeSum / count) * 0.25 - (absorptionSum / count) * 0.15, -2.0, 2.0); double bullishPressure = ClampDouble((bullishDelta / MathMax(sumVolume, 0.000001)) * 2.0, 0.0, 2.0); double bearishPressure = ClampDouble((bearishDelta / MathMax(sumVolume, 0.000001)) * 2.0, 0.0, 2.0); double absorption = ClampDouble(absorptionSum / count, 0.0, 2.0); double initiative = ClampDouble(initiativeSum / count, -2.0, 2.0); // Normalize: guard against NaN/Inf from edge-case inputs if(!MathIsValidNumber(pressure)) pressure = 0.0; if(!MathIsValidNumber(normalizedCumulativeDelta)) normalizedCumulativeDelta = 0.0; if(!MathIsValidNumber(bullishPressure)) bullishPressure = 0.0; if(!MathIsValidNumber(bearishPressure)) bearishPressure = 0.0; if(!MathIsValidNumber(absorption)) absorption = 0.0; if(!MathIsValidNumber(initiative)) initiative = 0.0; SetOutputs(si, pressure, normalizedCumulativeDelta, bullishPressure, bearishPressure, absorption, initiative); } return(rates_total); } //+------------------------------------------------------------------+