Warrior_EA/CustomIndicators/ADShorteningOfThrust.mq5
AnimateDread e62c710d6f fix: correct array orientation and PReLU gradient backprop in hidden layers
- Ensure `tick_volume` array is set as series in ADShorteningOfThrust.mq5 to prevent future-data leak in volume calculations.
- Ensure `open` array is set as series in ADWyckoffFailedStructure.mq5 to prevent future-data leak in structure detection.
- Add missing PReLU gradient scaling (multiply by 0.01 for negative outputs) in CPU_CalcHiddenGradient and DirectML shader to match expected derivative behavior across all backends.
2026-07-17 23:21:12 -04:00

664 lines
No EOL
21 KiB
MQL5

//+------------------------------------------------------------------+
//| ADShorteningOfThrust.mq5 |
//| Wyckoff SOT Detection |
//| |
//| ArraySetAsSeries(true) — index 0 = current bar (newest). |
//| Loop: from oldest needed bar down to 0 (backward in index, |
//| but forward in time due to reversed array series). |
//| |
//| Returns: +ratio = bearish impulses shortening (bullish SOT) |
//| -ratio = bullish impulses shortening (bearish SOT) |
//| 0 = no SOT detected |
//| |
//| DRAW_LINE: line drawn across all bars (including zeros) |
//+------------------------------------------------------------------+
#property copyright "AD Institutional Indicators"
#property link ""
#property version "1.00"
#property description "Shortening of Thrust — momentum exhaustion in trends"
#property indicator_separate_window
#property indicator_minimum -1.0
#property indicator_maximum 1.0
#property indicator_level1 0
#property indicator_buffers 4
#property indicator_plots 4
#property indicator_label1 "SOT"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrWhite
#property indicator_width1 1
#property indicator_label2 "SOTEffortRegime"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGold
#property indicator_width2 1
#property indicator_label3 "SOTConfirmation"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrLime
#property indicator_width3 1
#property indicator_label4 "SOTPushRegime"
#property indicator_type4 DRAW_LINE
#property indicator_color4 clrAqua
#property indicator_width4 1
//--- Input parameters
input int InpThrustLookback = 30; // Lookback period (10-100)
input int InpMinImpulses = 3; // Min impulses for SOT (2-6)
input double InpSOTThreshold = 0.30; // SOT detection threshold (0.10-0.60)
input int InpContextMode = 0; // Context mode: 0=FixedBars, 1=Session
input int InpSessionType = 5; // Session type: 1..10
input int InpSessionCount = 1; // Number of sessions in context (1-20)
//--- Output buffer
double ExtSOTBuffer[];
double ExtSOTEffortBuffer[];
double ExtSOTConfirmationBuffer[];
double ExtSOTPushBuffer[];
//--- ZigZag state machine
int zzDirection = 0; // +1 tracking up, -1 tracking down, 0 uninit
double zzPivotHigh = 0;
double zzPivotLow = 0;
datetime zzPivotHighTime = 0;
datetime zzPivotLowTime = 0;
//--- Impulse tracking (newest at [count-1], oldest at [0])
double ExtImpulseDists[];
double ExtImpulseVols[];
int ExtImpulseDir[];
datetime ExtImpulseTimes[];
int ExtImpulseCount = 0;
//--- Leg bookkeeping and confirmation state
double ExtCurrentLegVolume = 0.0;
int ExtCurrentLegBars = 0;
double ExtLastPivotPrice = 0.0;
bool ExtHasLastPivotPrice = false;
int ExtPendingConfirmOppositeDir = 0;
double ExtPendingConfirmMinDist = 0.0;
double ExtPendingConfirmVolumeRef = 0.0;
double ExtPendingConfirmSignal = 0.0;
//--- Validated params
int ExtLookback;
int ExtMinImp;
double ExtThreshold;
int ExtContextMode;
int ExtSessionType;
int ExtSessionCount;
//+------------------------------------------------------------------+
void OnInit()
{
ExtLookback = (int)MathMax(10, MathMin(100, InpThrustLookback));
ExtMinImp = (int)MathMax(2, MathMin(6, InpMinImpulses));
ExtThreshold = MathMax(0.10, MathMin(0.60, InpSOTThreshold));
ExtContextMode = (InpContextMode == 1) ? 1 : 0;
ExtSessionType = (int)MathMax(1, MathMin(10, InpSessionType));
ExtSessionCount = (int)MathMax(1, MathMin(20, InpSessionCount));
SetIndexBuffer(0, ExtSOTBuffer, INDICATOR_DATA);
SetIndexBuffer(1, ExtSOTEffortBuffer, INDICATOR_DATA);
SetIndexBuffer(2, ExtSOTConfirmationBuffer, INDICATOR_DATA);
SetIndexBuffer(3, ExtSOTPushBuffer, INDICATOR_DATA);
ArraySetAsSeries(ExtSOTBuffer, true);
ArraySetAsSeries(ExtSOTEffortBuffer, true);
ArraySetAsSeries(ExtSOTConfirmationBuffer, true);
ArraySetAsSeries(ExtSOTPushBuffer, true);
int maxImp = ExtLookback + 1;
ArrayResize(ExtImpulseDists, maxImp);
ArrayResize(ExtImpulseVols, maxImp);
ArrayResize(ExtImpulseDir, maxImp);
ArrayResize(ExtImpulseTimes, maxImp);
ArrayInitialize(ExtImpulseDists, 0);
ArrayInitialize(ExtImpulseVols, 0);
ArrayInitialize(ExtImpulseDir, 0);
ArrayInitialize(ExtImpulseTimes, 0);
IndicatorSetInteger(INDICATOR_DIGITS, 3);
IndicatorSetString(INDICATOR_SHORTNAME,
"SOT(" + IntegerToString(ExtLookback) + "," +
IntegerToString(ExtMinImp) + "," +
DoubleToString(ExtThreshold, 2) + ")");
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, ExtLookback);
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, ExtLookback);
PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, ExtLookback);
PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, ExtLookback);
ResetState();
}
void OnDeinit(const int reason) {}
void ResetState()
{
zzDirection = 0;
zzPivotHigh = 0;
zzPivotLow = 0;
zzPivotHighTime = 0;
zzPivotLowTime = 0;
ExtImpulseCount = 0;
ExtCurrentLegVolume = 0.0;
ExtCurrentLegBars = 0;
ExtLastPivotPrice = 0.0;
ExtHasLastPivotPrice = false;
ExtPendingConfirmOppositeDir = 0;
ExtPendingConfirmMinDist = 0.0;
ExtPendingConfirmVolumeRef = 0.0;
ExtPendingConfirmSignal = 0.0;
ArrayInitialize(ExtImpulseDists, 0);
ArrayInitialize(ExtImpulseVols, 0);
ArrayInitialize(ExtImpulseDir, 0);
ArrayInitialize(ExtImpulseTimes, 0);
}
//+------------------------------------------------------------------+
void AgeOutOldImpulse()
{
if(ExtImpulseCount > ExtLookback)
{
for(int k = 1; k < ExtImpulseCount; k++)
{
ExtImpulseDists[k - 1] = ExtImpulseDists[k];
ExtImpulseVols[k - 1] = ExtImpulseVols[k];
ExtImpulseDir[k - 1] = ExtImpulseDir[k];
ExtImpulseTimes[k - 1] = ExtImpulseTimes[k];
}
ExtImpulseCount--;
}
}
//+------------------------------------------------------------------+
void RecordImpulse(double dist, int dir, double avgVol, datetime pivotTime)
{
if(dist <= 0) return;
//--- Ensure array capacity (mirrors Java ensureCapacity)
if(ExtImpulseCount >= ArraySize(ExtImpulseDists))
{
int newSize = ArraySize(ExtImpulseDists) + 1;
ArrayResize(ExtImpulseDists, newSize);
ArrayResize(ExtImpulseVols, newSize);
ArrayResize(ExtImpulseDir, newSize);
ArrayResize(ExtImpulseTimes, newSize);
}
ExtImpulseDists[ExtImpulseCount] = dist;
ExtImpulseVols[ExtImpulseCount] = MathMax(0.0, avgVol);
ExtImpulseDir[ExtImpulseCount] = dir;
ExtImpulseTimes[ExtImpulseCount] = pivotTime;
ExtImpulseCount++;
}
void TrimImpulseCount(const int maxImpulses)
{
int cap = MathMax(1, maxImpulses);
while(ExtImpulseCount > cap)
{
for(int k = 1; k < ExtImpulseCount; k++)
{
ExtImpulseDists[k - 1] = ExtImpulseDists[k];
ExtImpulseVols[k - 1] = ExtImpulseVols[k];
ExtImpulseDir[k - 1] = ExtImpulseDir[k];
ExtImpulseTimes[k - 1] = ExtImpulseTimes[k];
}
ExtImpulseCount--;
}
}
void PruneImpulsesByStartTime(const datetime minTime)
{
if(minTime <= 0 || ExtImpulseCount <= 0) return;
int write = 0;
for(int i = 0; i < ExtImpulseCount; i++)
{
if(ExtImpulseTimes[i] >= minTime)
{
ExtImpulseDists[write] = ExtImpulseDists[i];
ExtImpulseVols[write] = ExtImpulseVols[i];
ExtImpulseDir[write] = ExtImpulseDir[i];
ExtImpulseTimes[write] = ExtImpulseTimes[i];
write++;
}
}
ExtImpulseCount = write;
}
int IsZigZagSessionTypeMT5(const int sessionType)
{
return (sessionType == 9 || sessionType == 10);
}
int IsPreviousSessionTypeMT5(const int sessionType)
{
return (sessionType >= 1 && sessionType <= 4);
}
int MapBaseCalendarTypeMT5(const int sessionType)
{
if(sessionType == 1 || sessionType == 5) return 1; // day
if(sessionType == 2 || sessionType == 6) return 2; // week
if(sessionType == 3 || sessionType == 7) return 3; // month
if(sessionType == 4 || sessionType == 8) return 4; // year
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 ApplyContextWindow(const datetime currentTime)
{
if(ExtContextMode != 1)
{
AgeOutOldImpulse();
return;
}
if(IsZigZagSessionTypeMT5(ExtSessionType))
{
int maxImp = MathMax(ExtMinImp * 2, ExtSessionCount * MathMax(2, ExtMinImp) * 2);
TrimImpulseCount(maxImp);
return;
}
datetime start = ResolveCalendarContextStartMT5(currentTime);
PruneImpulsesByStartTime(start);
int minSafetyImp = MathMax(2, ExtMinImp) * 2;
int maxSafetyImp = MathMax(minSafetyImp, ExtLookback + 1);
TrimImpulseCount(maxSafetyImp * MathMax(1, ExtSessionCount));
}
double AverageRecentImpulseVolume(const int bars)
{
if(ExtImpulseCount <= 0 || bars <= 0) return 0.0;
int from = MathMax(0, ExtImpulseCount - bars);
double sum = 0.0;
int count = 0;
for(int i = from; i < ExtImpulseCount; i++)
{
sum += ExtImpulseVols[i];
count++;
}
return (count > 0) ? (sum / count) : 0.0;
}
double UpdateConfirmation(const int newImpulseDir, const double newImpulseDist, const double newImpulseVol)
{
if(ExtPendingConfirmOppositeDir == 0 || ExtPendingConfirmSignal == 0.0) return 0.0;
if(newImpulseDir != ExtPendingConfirmOppositeDir) return 0.0;
double distGate = MathMax(0.0, ExtPendingConfirmMinDist * 0.85);
double volGate = MathMax(0.0, ExtPendingConfirmVolumeRef * 1.05);
if(newImpulseDist >= distGate && newImpulseVol >= volGate)
{
double signal = ExtPendingConfirmSignal;
ExtPendingConfirmOppositeDir = 0;
ExtPendingConfirmMinDist = 0.0;
ExtPendingConfirmVolumeRef = 0.0;
ExtPendingConfirmSignal = 0.0;
return signal;
}
return 0.0;
}
struct SOTComputation
{
double sotValue;
double effortRegime;
double pushRegime;
int sotDir;
double latestDist;
double latestVol;
};
//+------------------------------------------------------------------+
//| Compute SOT — compares adjacent same-direction swing pair |
//| D_n = distance of last same-direction swing |
//| D_{n-1} = distance of previous same-direction swing |
//| Signal: D_n < D_{n-1} i.e., thrust of current swing is shorter |
//| |
//| Returns: +ratio = bearish impulses shortening (bullish SOT) |
//| -ratio = bullish impulses shortening (bearish SOT) |
//| 0 = no SOT detected |
//+------------------------------------------------------------------+
SOTComputation ComputeSOT()
{
SOTComputation out;
out.sotValue = 0.0;
out.effortRegime = 0.0;
out.pushRegime = 0.0;
out.sotDir = 0;
out.latestDist = 0.0;
out.latestVol = 0.0;
if(ExtImpulseCount < ExtMinImp) return out;
int lastDir = ExtImpulseDir[ExtImpulseCount - 1];
if(lastDir == 0) return out;
// Find the last two same-direction impulses.
// Since zigzag alternates, same-direction swings are at
// positions: [count-1], [count-3], [count-5], ...
double dNewest = 0; // D_n
double dPrev = 0; // D_{n-1}
double vNewest = 0;
double vPrev = 0;
int found = 0;
for(int g = ExtImpulseCount - 1; g >= 0 && found < 2; g--)
{
if(ExtImpulseDir[g] == lastDir)
{
if(found == 0)
{
dNewest = ExtImpulseDists[g]; // D_n
vNewest = ExtImpulseVols[g];
}
else
{
dPrev = ExtImpulseDists[g]; // D_{n-1}
vPrev = ExtImpulseVols[g];
}
found++;
}
}
if(found < 2 || dPrev <= 0) return out;
// Ratio: how much shorter is D_n compared to D_{n-1}?
double ratio = (dPrev - dNewest) / dPrev;
if(!MathIsValidNumber(ratio)) return out;
double directionalSign = (lastDir == -1) ? 1.0 : -1.0;
out.latestDist = dNewest;
out.latestVol = vNewest;
out.sotDir = lastDir;
// Only signal when the newest swing is at least ExtThreshold % shorter
if(ratio >= ExtThreshold)
{
out.sotValue = directionalSign * ratio;
double volRatio = (vPrev > 0.0) ? (vNewest / vPrev) : 1.0;
if(volRatio >= 1.1)
out.effortRegime = directionalSign;
else if(volRatio <= 0.9)
out.effortRegime = directionalSign * 0.5;
int sameDirCount = 0;
for(int i = ExtImpulseCount - 1; i >= 0; i--)
{
if(ExtImpulseDir[i] == lastDir)
sameDirCount++;
}
out.pushRegime = (sameDirCount >= 4) ? directionalSign : (directionalSign * 0.5);
}
return out;
}
//+------------------------------------------------------------------+
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 < ExtLookback + 5) return(0);
// ArraySetAsSeries: index 0 = newest bar (rightmost on chart)
ArraySetAsSeries(time, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
// tick_volume was missing here: every other array below is series-oriented and the main loop
// indexes with a series loop variable `i` (see the "Loop backward" comment below), but
// tick_volume was left in its default chronological orientation - tick_volume[i] therefore read
// the wrong bar's volume (severity grows toward the oldest processed bar, where it read the
// NEWEST bar's volume - a genuine future-data leak into ExtCurrentLegVolume/SOTEffortRegime/
// SOTConfirmation). Flipping it here makes it consistent with every other array in this loop.
ArraySetAsSeries(tick_volume, true);
ArraySetAsSeries(ExtSOTBuffer, true);
int startBar;
// Always recompute the full series on each call. This ensures the MT5
// output matches the Java OnBarUpdate() replay of all bars from the oldest
// available bar to the newest, eliminating incremental state drift.
ResetState();
startBar = rates_total - 1;
ArrayInitialize(ExtSOTBuffer, 0);
ArrayInitialize(ExtSOTEffortBuffer, 0);
ArrayInitialize(ExtSOTConfirmationBuffer, 0);
ArrayInitialize(ExtSOTPushBuffer, 0);
// Loop backward: oldest → newest (i decreases but time advances)
for(int i = startBar; i >= 0 && !IsStopped(); i--)
{
double hi = high[i];
double lo = low[i];
//--- Initialize zigzag on first bar processed (oldest in calc range)
if(zzDirection == 0)
{
zzPivotHigh = hi;
zzPivotLow = lo;
zzPivotHighTime = time[i];
zzPivotLowTime = time[i];
zzDirection = 1;
ExtCurrentLegVolume = MathMax((double)tick_volume[i], 0.0);
ExtCurrentLegBars = 1;
ExtSOTBuffer[i] = 0.0;
ExtSOTEffortBuffer[i] = 0.0;
ExtSOTConfirmationBuffer[i] = 0.0;
ExtSOTPushBuffer[i] = 0.0;
continue;
}
ExtCurrentLegVolume += MathMax((double)tick_volume[i], 0.0);
ExtCurrentLegBars++;
//--- Compute swing threshold (ATR-based, 14-period)
// Uses same bar indexing as Java: t counts from 0..available-1
// where t=0 is bar i (the bar being processed).
// Java: for(t=0; t<available; t++) uses Chart.High.get(t), Chart.Close.get(t+1)
// MQ5 with series-reversed: for(t=0; t<available; t++) uses high[i+t], close[i+t+1]
int available = MathMin(14, rates_total - i - 1);
if(available <= 0) available = 1;
double sumTR = 0;
for(int t = 0; t < available; 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 = h - l;
double hc = MathAbs(h - pc);
double lc = MathAbs(l - pc);
if(hc > tr) tr = hc;
if(lc > tr) tr = lc;
sumTR += tr;
}
double atr = sumTR / available;
double swingThreshold = MathMax(atr * 0.5, (hi - lo) * 0.3);
if(swingThreshold <= 0) swingThreshold = (hi - lo) * 0.5;
//--- ZigZag state machine
bool pivotFound = false;
int newPivotDir = 0;
double newPivotPrice = 0.0;
if(zzDirection == 1)
{
if(hi > zzPivotHigh)
{
zzPivotHigh = hi;
zzPivotHighTime = time[i];
}
double moveDown = zzPivotHigh - lo;
if(moveDown >= swingThreshold && zzPivotHigh > 0)
{
newPivotDir = 1;
newPivotPrice = zzPivotHigh;
pivotFound = true;
zzDirection = -1;
zzPivotLow = lo;
zzPivotLowTime = time[i];
}
}
else // zzDirection == -1
{
if(lo < zzPivotLow)
{
zzPivotLow = lo;
zzPivotLowTime = time[i];
}
double moveUp = hi - zzPivotLow;
if(moveUp >= swingThreshold && zzPivotLow > 0)
{
newPivotDir = -1;
newPivotPrice = zzPivotLow;
pivotFound = true;
zzDirection = 1;
zzPivotHigh = hi;
zzPivotHighTime = time[i];
}
}
double confirmSignal = 0.0;
if(pivotFound)
{
double avgLegVolume = (ExtCurrentLegBars > 0) ? (ExtCurrentLegVolume / ExtCurrentLegBars) : 0.0;
if(ExtHasLastPivotPrice)
{
double dist = MathAbs(newPivotPrice - ExtLastPivotPrice);
RecordImpulse(dist, newPivotDir, avgLegVolume, time[i]);
ApplyContextWindow(time[i]);
confirmSignal = UpdateConfirmation(newPivotDir, dist, avgLegVolume);
}
ExtLastPivotPrice = newPivotPrice;
ExtHasLastPivotPrice = true;
ExtCurrentLegVolume = 0.0;
ExtCurrentLegBars = 0;
}
SOTComputation sot = ComputeSOT();
double sotValue = sot.sotValue;
double effortRegime = sot.effortRegime;
double pushRegime = sot.pushRegime;
if(sotValue != 0.0 && sot.sotDir != 0)
{
ExtPendingConfirmOppositeDir = -sot.sotDir;
ExtPendingConfirmMinDist = sot.latestDist;
ExtPendingConfirmVolumeRef = MathMax(sot.latestVol, AverageRecentImpulseVolume(MathMin(ExtImpulseCount, ExtMinImp)));
ExtPendingConfirmSignal = (sot.sotDir == -1) ? 1.0 : -1.0;
}
if(!MathIsValidNumber(sotValue)) sotValue = 0.0;
if(!MathIsValidNumber(effortRegime)) effortRegime = 0.0;
if(!MathIsValidNumber(confirmSignal)) confirmSignal = 0.0;
if(!MathIsValidNumber(pushRegime)) pushRegime = 0.0;
ExtSOTBuffer[i] = sotValue;
ExtSOTEffortBuffer[i] = effortRegime;
ExtSOTConfirmationBuffer[i] = confirmSignal;
ExtSOTPushBuffer[i] = pushRegime;
}
return(rates_total);
}
//+------------------------------------------------------------------+