forked from animatedread/Warrior_EA
- 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.
664 lines
No EOL
21 KiB
MQL5
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);
|
|
}
|
|
//+------------------------------------------------------------------+ |