fix: The simple logic was buried under layered filters that collectively blocked valid retests

This commit is contained in:
Naji El Chemaly
2026-06-10 22:43:59 +03:00
parent c7b34033e5
commit de24c99af4
2 changed files with 139 additions and 51 deletions
+125 -50
View File
@@ -5,7 +5,7 @@
//| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector |
//+------------------------------------------------------------------+
#property copyright "NANDR"
#property version "1.39"
#property version "1.393"
#property strict
#include <Trade\Trade.mqh>
@@ -106,6 +106,14 @@ input bool InpLadderOnTokyo = true; // Ladder on Tokyo ses
input bool InpLadderOnLondon = true; // Ladder on London session
input bool InpLadderOnNY = true; // Ladder on NY session
input bool InpLadderOnNYORB = true; // Ladder on NY ORB session
input bool InpUseLadderATRVolCeiling = false; // Use ATR Volatility Ceiling for Ladder Entries
input double InpLadderATRMaxPips = 200.0; // Max ATR (pips) allowed for Ladder Entries
input bool InpUseLadderSessionCaps = false; // Use Per-Session Ladder Trade Caps
input int InpLadderCapDailyOpen = 2; // Daily Open Ladder Cap (0=unlimited)
input int InpLadderCapTokyo = 2; // Tokyo Ladder Cap (0=unlimited)
input int InpLadderCapLondon = 2; // London Ladder Cap (0=unlimited)
input int InpLadderCapNY = 2; // NY Ladder Cap (0=unlimited)
input int InpLadderCapNYORB = 2; // NY ORB Ladder Cap (0=unlimited)
input ENUM_LADDER_SL_MODE InpLadderSLMode = LADDER_SL_FIXED_PIPS; // Ladder SL Mode
input double InpLadderSLBufferPips = 5.0; // Ladder SL Buffer Pips (when level-buffer mode)
input int InpLadderMaxTradesPerSession = 2; // Max Ladder Trades Per Session (0=unlimited)
@@ -653,6 +661,10 @@ void DetectBreakouts()
if(g_Sessions[s].orbHigh <= 0 || g_Sessions[s].orbLow >= DBL_MAX) continue;
if(g_Sessions[s].inBreakout || g_Sessions[s].inRetest) continue;
double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar
bool outsideBull = (c0 > g_Sessions[s].orbHigh);
bool outsideBear = (c0 < g_Sessions[s].orbLow);
bool bullBO = InpUseStrictFilter
? CheckStrictBreakout( 1, g_Sessions[s].orbHigh)
: CheckSimpleBreakout( 1, g_Sessions[s].orbHigh);
@@ -660,6 +672,24 @@ void DetectBreakouts()
? CheckStrictBreakout(-1, g_Sessions[s].orbLow)
: CheckSimpleBreakout(-1, g_Sessions[s].orbLow);
// Fallback arming for simple mode: if the exact close-cross event was missed,
// still arm breakout while price is already closed beyond the ORB boundary.
if(!InpUseStrictFilter)
{
if(!bullBO && outsideBull)
{
bullBO = true;
PrintFormat("NANDR EA: [%s] Bullish breakout armed by outside-range fallback. Close=%.2f ORBHigh=%.2f",
g_Sessions[s].name, c0, g_Sessions[s].orbHigh);
}
if(!bearBO && outsideBear)
{
bearBO = true;
PrintFormat("NANDR EA: [%s] Bearish breakout armed by outside-range fallback. Close=%.2f ORBLow=%.2f",
g_Sessions[s].name, c0, g_Sessions[s].orbLow);
}
}
if(bullBO)
{
// Bias filter note: do not suppress ORB breakout detection.
@@ -1032,8 +1062,8 @@ bool OpenLadderTrade(int sessIdx, int dir, double srcLevel, double dstLevel, con
if(g_TradingHalted) return false;
if(g_TodayTrades >= InpMaxTradesPerDay) return false;
if(HasActiveExposure()) return false;
if(InpLadderMaxTradesPerSession > 0
&& g_Sessions[sessIdx].ladderTradesThisSession >= InpLadderMaxTradesPerSession)
int ladderCap = GetEffectiveLadderCap(sessIdx);
if(ladderCap > 0 && g_Sessions[sessIdx].ladderTradesThisSession >= ladderCap)
return false;
double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK);
@@ -1102,6 +1132,46 @@ bool IsLadderEnabledForSession(int sessIdx)
return true;
}
int GetLadderSessionCap(int sessIdx)
{
if(!InpUseLadderSessionCaps)
return 0;
string name = g_Sessions[sessIdx].name;
if(name == "DailyOpen") return InpLadderCapDailyOpen;
if(name == "Tokyo") return InpLadderCapTokyo;
if(name == "London") return InpLadderCapLondon;
if(name == "NY") return InpLadderCapNY;
if(name == "NYORB") return InpLadderCapNYORB;
return 0;
}
int GetEffectiveLadderCap(int sessIdx)
{
int globalCap = InpLadderMaxTradesPerSession;
int sessionCap = GetLadderSessionCap(sessIdx);
if(globalCap <= 0) return sessionCap;
if(sessionCap <= 0) return globalCap;
return MathMin(globalCap, sessionCap);
}
bool IsLadderVolatilityTooHigh(double &atrPips)
{
atrPips = 0.0;
if(!InpUseLadderATRVolCeiling)
return false;
if(g_ATRHandle == INVALID_HANDLE)
return false;
double atr[1];
if(CopyBuffer(g_ATRHandle, 0, 0, 1, atr) <= 0)
return false;
atrPips = PriceToPips(atr[0]);
return (atrPips > InpLadderATRMaxPips);
}
bool TrySessionLadderEntry(int sessIdx,
double c1,
double h_cur,
@@ -1117,8 +1187,17 @@ bool TrySessionLadderEntry(int sessIdx,
if(HasActiveExposure()) return false;
if(g_Sessions[sessIdx].retestFiredThisBar) return false;
if(InpBreakoutExpireBars > 0 && g_Sessions[sessIdx].breakoutBarsAgo >= InpBreakoutExpireBars) return false;
if(InpLadderMaxTradesPerSession > 0
&& g_Sessions[sessIdx].ladderTradesThisSession >= InpLadderMaxTradesPerSession) return false;
int ladderCap = GetEffectiveLadderCap(sessIdx);
if(ladderCap > 0 && g_Sessions[sessIdx].ladderTradesThisSession >= ladderCap)
return false;
double atrPips = 0.0;
if(IsLadderVolatilityTooHigh(atrPips))
{
PrintFormat("NANDR EA: Ladder skipped [%s] - ATR %.1f pips exceeds ceiling %.1f pips.",
g_Sessions[sessIdx].name, atrPips, InpLadderATRMaxPips);
return false;
}
double orbHigh = g_Sessions[sessIdx].orbHigh;
double orbLow = g_Sessions[sessIdx].orbLow;
@@ -1253,95 +1332,91 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot
}
//+------------------------------------------------------------------+
//| Tick-based retest entries — fires at the wick, not on bar close |
//| Covers both ORB session retests and OB boundary retests. |
//| Uses live bar[0] high/low + last closed bar[1] for confirmation. |
//| Direction: determined by breakoutDir for ORB, OB type for OBs. |
//| Sweep reversals: detected via ask/bid relative to the level. |
//| Tick-based retest entries |
//| Core ORB strategy: |
//| 1. Session ORB complete |
//| 2. Breakout confirmed (close crossed ORB level) |
//| 3. Wick of current bar touches the broken level |
//| 4. Enter in breakout direction |
//+------------------------------------------------------------------+
void CheckRetestEntriesTick()
{
if(g_TradingHalted) return;
if(g_TodayTrades >= InpMaxTradesPerDay) return;
double h_cur = iHigh(g_Symbol, PERIOD_CURRENT, 0); // current bar live high so far
double l_cur = iLow(g_Symbol, PERIOD_CURRENT, 0); // current bar live low so far
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 1); // last closed bar close
double open0 = iOpen(g_Symbol, PERIOD_CURRENT, 0); // current bar open
double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(g_Symbol, SYMBOL_BID);
double tol = PipsToPrice(1.0); // 1-pip tolerance for direction detection
double h_cur = iHigh(g_Symbol, PERIOD_CURRENT, 0);
double l_cur = iLow(g_Symbol, PERIOD_CURRENT, 0);
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 1);
double open0 = iOpen(g_Symbol, PERIOD_CURRENT, 0);
double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(g_Symbol, SYMBOL_BID);
double tol = PipsToPrice(1.0);
// ── ORB Session Retest Entries ──────────────────────────────────
// Fires at the moment the wick touches the ORB level, not one bar later.
// Direction uses ask/bid to distinguish continuation vs sweep reversal:
// bid <= level + tol → price is AT or below the level → continuation in breakout dir
// ask >= level - tol → price has passed back through the level → sweep reversal, flip dir
if(InpWaitForRetest)
{
for(int s = 0; s < g_SessionCount; s++)
{
// --- Gate 1: session must have a live confirmed breakout ---
if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue;
if(!g_Sessions[s].inBreakout) continue;
if(g_Sessions[s].breakoutEntryTaken) continue;
if(g_Sessions[s].tradesThisSession >= InpMaxRetestsPerSession) continue;
// --- Gate 2: no active exposure (one trade at a time) ---
if(HasActiveExposure()) continue;
// --- Gate 3: entry already taken and not yet released ---
if(g_Sessions[s].breakoutEntryTaken) continue;
// --- Gate 4: session trade count limit ---
if(g_Sessions[s].tradesThisSession >= InpMaxRetestsPerSession) continue;
// --- Gate 5: breakout expiry ---
if(InpBreakoutExpireBars > 0 && g_Sessions[s].breakoutBarsAgo >= InpBreakoutExpireBars) continue;
double orbHigh = g_Sessions[s].orbHigh;
double orbLow = g_Sessions[s].orbLow;
double orbMid = (orbHigh + orbLow) / 2.0;
int dir = 0;
// --- Gate 6: one entry per bar to prevent duplicate ticks ---
if(g_Sessions[s].retestFiredThisBar) continue;
double orbHigh = g_Sessions[s].orbHigh;
double orbLow = g_Sessions[s].orbLow;
int dir = 0;
double orbLevel = 0;
if(g_Sessions[s].breakoutDir == 1) // Bullish
if(g_Sessions[s].breakoutDir == 1) // Bullish breakout: price broke above orbHigh
{
// Continuation retest: breakout direction is already confirmed.
// Trigger on wick-touch of the session level.
// Retest: wick comes back down to touch orbHigh
if(l_cur <= orbHigh)
{
orbLevel = orbHigh;
dir = 1;
}
// Mid retest continuation.
else if(l_cur <= orbMid)
{
orbLevel = orbMid;
dir = 1;
}
}
else if(g_Sessions[s].breakoutDir == -1) // Bearish
else if(g_Sessions[s].breakoutDir == -1) // Bearish breakout: price broke below orbLow
{
// Continuation retest: breakout direction is already confirmed.
// Trigger on wick-touch of the session level.
// Retest: wick comes back up to touch orbLow
if(h_cur >= orbLow)
{
orbLevel = orbLow;
dir = -1;
}
// Mid retest continuation.
else if(h_cur >= orbMid)
{
orbLevel = orbMid;
dir = -1;
}
}
if(dir == 0) continue;
// One entry per wick touch per bar — prevents 3 duplicate trades on consecutive ticks
if(g_Sessions[s].retestFiredThisBar) continue;
g_Sessions[s].retestFiredThisBar = true;
double obTop = 0, obBottom = 0;
IsOBNearLevel(orbLevel, dir, obTop, obBottom);
if(InpOBRequireConf && obTop == 0) continue;
if(InpOBRequireConf && obTop == 0)
{
PrintFormat("NANDR EA: [%s] Retest at %.2f blocked — OB confirmation required but no OB nearby.",
g_Sessions[s].name, orbLevel);
continue;
}
OpenTrade(dir, s, orbLevel, obTop, obBottom);
}
}
// ORB retest has priority. Ladder logic runs as fallback only when no active
// exposure exists after ORB checks in this tick.
// ── Session Level Ladder (fallback, only when no ORB entry taken this tick) ──
if(InpUseSessionLevelLadder && !HasActiveExposure())
{
for(int s = 0; s < g_SessionCount; s++)
+14 -1
View File
@@ -103,9 +103,22 @@ All sessions are **disabled by default** — enable the ones you want to trade:
| Parameter | Default | Description |
|-----------|---------|-------------|
| `InpUseSessionLevelLadder` | `true` | Enable level-to-level ladder entries after breakout direction confirmation |
| `InpLadderOnDailyOpen` | `true` | Enable ladder entries on Daily Open session |
| `InpLadderOnTokyo` | `true` | Enable ladder entries on Tokyo session |
| `InpLadderOnLondon` | `true` | Enable ladder entries on London session |
| `InpLadderOnNY` | `true` | Enable ladder entries on NY session |
| `InpLadderOnNYORB` | `true` | Enable ladder entries on NY ORB session |
| `InpUseLadderATRVolCeiling` | `false` | Enable ATR volatility ceiling filter for ladder entries |
| `InpLadderATRMaxPips` | `200.0` | Skip ladder entries when ATR exceeds this pips threshold |
| `InpUseLadderSessionCaps` | `false` | Enable per-session ladder trade caps |
| `InpLadderCapDailyOpen` | `2` | Daily Open ladder cap (`0` = unlimited) |
| `InpLadderCapTokyo` | `2` | Tokyo ladder cap (`0` = unlimited) |
| `InpLadderCapLondon` | `2` | London ladder cap (`0` = unlimited) |
| `InpLadderCapNY` | `2` | NY ladder cap (`0` = unlimited) |
| `InpLadderCapNYORB` | `2` | NY ORB ladder cap (`0` = unlimited) |
| `InpLadderSLMode` | `Fixed Pips` | Ladder SL mode: fixed pips or buffer beyond source level |
| `InpLadderSLBufferPips` | `5.0` | Buffer size when ladder SL mode is level-buffer |
| `InpLadderMaxTradesPerSession` | `2` | Max ladder trades per session (`0` = unlimited) |
| `InpLadderMaxTradesPerSession` | `2` | Global ladder cap per session (`0` = unlimited); combined with per-session cap using the stricter limit |
### Lot Size & Risk
| Parameter | Default | Description |