Warrior_EA/CustomIndicators/ADCumulativeDelta.mq5
AnimateDread 721f2970f2 feat: normalize CumulativeDelta feature, add explicit bullish/bearish flag
ADCumulativeDelta's CumulativeDelta buffer now outputs
cumulativeDelta/sumVolume clamped +/-2 instead of a raw, unbounded,
tick-volume-scale running sum - also fixes the indicator's own
declared -2..2 min/max property, which the raw sum silently violated.
Re-added to the feature set now that it's properly scaled, as a
distinct order-flow-imbalance signal from Pressure (same term plus
Initiative/Absorption adjustments).

Added an explicit +1/-1/0 bullish/bearish/doji flag alongside the
ATR-normalized price features, so the network gets candle direction
as a clean standalone signal instead of having to disentangle it from
(close-open)/atr's combined direction+magnitude encoding.
2026-07-16 19:55:51 -04:00

414 lines
15 KiB
MQL5

//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+