fix: The simple logic was buried under layered filters that collectively blocked valid retests
This commit is contained in:
+125
-50
@@ -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++)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user