diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 964d710..c6d97f9 100644 --- a/NANDR_ORB_OB_EA.mq5 +++ b/NANDR_ORB_OB_EA.mq5 @@ -5,7 +5,7 @@ //| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector | //+------------------------------------------------------------------+ #property copyright "NANDR" -#property version "1.38" +#property version "1.39" #property strict #include @@ -53,6 +53,12 @@ enum ENUM_OB_MITIGATION MITIG_CLOSE // Close (close penetrates zone) }; +enum ENUM_LADDER_SL_MODE +{ + LADDER_SL_FIXED_PIPS, // Use InpSLPips + LADDER_SL_LEVEL_BUFFER // Buffer beyond source level +}; + //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ @@ -92,6 +98,18 @@ input bool InpUseOBRetestEntry = true; // OB Retest Entry (ent input int InpMaxRetestsPerSession = 3; // Max Retest Entries Per Session (per breakout) input bool InpUseSessionBiasFilter = true; // Session Bias Filter: block signals against day bias +// --- Session Level Ladder --- +input group "═══ Session Level Ladder ═══" +input bool InpUseSessionLevelLadder = false; // Enable Session Level Ladder Entries +input bool InpLadderOnDailyOpen = true; // Ladder on Daily Open session +input bool InpLadderOnTokyo = true; // Ladder on Tokyo session +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 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) + // --- Lot Size & Risk --- input group "═══ Lot Size & Risk ═══" input ENUM_LOT_MODE InpLotMode = LOT_RISK_PERCENT; // Lot Mode @@ -157,6 +175,13 @@ struct SSession int breakoutBarsAgo; bool breakoutEntryTaken; // one entry max per breakout confirmation leg + bool ladderBullLowDone; // Bull ladder: low -> mid done + bool ladderBullMidDone; // Bull ladder: mid -> high done + bool ladderBearHighDone; // Bear ladder: high -> mid done + bool ladderBearMidDone; // Bear ladder: mid -> low done + + int ladderTradesThisSession; + int tradesThisSession; bool retestFiredThisBar; // blocks duplicate entries on consecutive ticks within same bar @@ -172,6 +197,13 @@ struct SSession inRetest = false; breakoutBarsAgo = 0; breakoutEntryTaken = false; + + ladderBullLowDone = false; + ladderBullMidDone = false; + ladderBearHighDone = false; + ladderBearMidDone = false; + ladderTradesThisSession = 0; + tradesThisSession = 0; retestFiredThisBar = false; } @@ -522,6 +554,7 @@ void UpdateORBSessions() g_Sessions[s].inRetest = false; g_Sessions[s].breakoutBarsAgo = 0; g_Sessions[s].tradesThisSession= 0; + ResetLadderProgress(s); if(g_Sessions[s].orbComplete) PrintFormat("NANDR EA: [%s] ORB complete. High=%.2f Low=%.2f", @@ -640,6 +673,7 @@ void DetectBreakouts() g_Sessions[s].inBreakout = true; g_Sessions[s].breakoutBarsAgo= 0; g_Sessions[s].breakoutEntryTaken = false; + ResetLadderDirection(s, 1); if(g_GlobalBiasDir == 0) g_GlobalBiasDir = 1; // first breakout of the day sets bias PrintFormat("NANDR EA: [%s] Bullish ORB breakout at %.2f [DayBias=%d]", g_Sessions[s].name, g_Sessions[s].orbHigh, g_GlobalBiasDir); @@ -657,6 +691,7 @@ void DetectBreakouts() g_Sessions[s].inBreakout = true; g_Sessions[s].breakoutBarsAgo= 0; g_Sessions[s].breakoutEntryTaken = false; + ResetLadderDirection(s, -1); if(g_GlobalBiasDir == 0) g_GlobalBiasDir = -1; // first breakout of the day sets bias PrintFormat("NANDR EA: [%s] Bearish ORB breakout at %.2f [DayBias=%d]", g_Sessions[s].name, g_Sessions[s].orbLow, g_GlobalBiasDir); @@ -690,6 +725,7 @@ void UpdateRetestState(int sessIdx) { g_Sessions[sessIdx].breakoutDir = -1; g_Sessions[sessIdx].breakoutEntryTaken = false; + ResetLadderDirection(sessIdx, -1); g_GlobalBiasDir = -1; // price swept the level and closed below — day bias now bearish PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH [DayBias=%d]", g_Sessions[sessIdx].name, orbHigh, g_GlobalBiasDir); @@ -702,6 +738,7 @@ void UpdateRetestState(int sessIdx) g_Sessions[sessIdx].inBreakout = false; g_Sessions[sessIdx].breakoutDir = 0; g_Sessions[sessIdx].breakoutEntryTaken = false; + ResetLadderProgress(sessIdx); } } else if(g_Sessions[sessIdx].breakoutDir == -1) @@ -712,6 +749,7 @@ void UpdateRetestState(int sessIdx) { g_Sessions[sessIdx].breakoutDir = 1; g_Sessions[sessIdx].breakoutEntryTaken = false; + ResetLadderDirection(sessIdx, 1); g_GlobalBiasDir = 1; // price swept the level and closed above — day bias now bullish PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH [DayBias=%d]", g_Sessions[sessIdx].name, orbLow, g_GlobalBiasDir); @@ -724,6 +762,7 @@ void UpdateRetestState(int sessIdx) g_Sessions[sessIdx].inBreakout = false; g_Sessions[sessIdx].breakoutDir = 0; g_Sessions[sessIdx].breakoutEntryTaken = false; + ResetLadderProgress(sessIdx); } } } @@ -949,6 +988,34 @@ bool HasActiveExposure() return (CountOpenPositions() + CountPendingOrders()) > 0; } +double SessionMid(const SSession &sess) +{ + return (sess.orbHigh + sess.orbLow) / 2.0; +} + +void ResetLadderProgress(int sessIdx) +{ + g_Sessions[sessIdx].ladderBullLowDone = false; + g_Sessions[sessIdx].ladderBullMidDone = false; + g_Sessions[sessIdx].ladderBearHighDone = false; + g_Sessions[sessIdx].ladderBearMidDone = false; + g_Sessions[sessIdx].ladderTradesThisSession = 0; +} + +void ResetLadderDirection(int sessIdx, int dir) +{ + if(dir > 0) + { + g_Sessions[sessIdx].ladderBullLowDone = false; + g_Sessions[sessIdx].ladderBullMidDone = false; + } + else + { + g_Sessions[sessIdx].ladderBearHighDone = false; + g_Sessions[sessIdx].ladderBearMidDone = false; + } +} + void RefreshBreakoutEntryLocks() { // Allow a new entry from the same breakout leg only after all managed @@ -962,6 +1029,157 @@ void RefreshBreakoutEntryLocks() } } +bool OpenLadderTrade(int sessIdx, int dir, double srcLevel, double dstLevel, const string stepTag) +{ + if(g_TradingHalted) return false; + if(g_TodayTrades >= InpMaxTradesPerDay) return false; + if(HasActiveExposure()) return false; + if(InpLadderMaxTradesPerSession > 0 + && g_Sessions[sessIdx].ladderTradesThisSession >= InpLadderMaxTradesPerSession) + return false; + + double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(g_Symbol, SYMBOL_BID); + double entry = (dir > 0) ? ask : bid; + double tp = NormalizeDouble(dstLevel, (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); + double sl = 0.0; + + if(InpLadderSLMode == LADDER_SL_LEVEL_BUFFER) + { + sl = (dir > 0) ? srcLevel - PipsToPrice(InpLadderSLBufferPips) + : srcLevel + PipsToPrice(InpLadderSLBufferPips); + } + else + { + sl = (dir > 0) ? entry - PipsToPrice(InpSLPips) + : entry + PipsToPrice(InpSLPips); + } + sl = NormalizeDouble(sl, (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); + + if((dir > 0 && (tp <= entry || sl >= entry)) + || (dir < 0 && (tp >= entry || sl <= entry))) + return false; + + double slPips = PriceToPips(MathAbs(entry - sl)); + if(slPips <= 0) return false; + double lots = CalcLotSize(slPips); + + string comment = StringFormat("NANDR|Ladder|%s|%s|%s", + g_Sessions[sessIdx].name, + (dir > 0 ? "BUY" : "SELL"), + stepTag); + + bool result = (dir > 0) + ? g_Trade.Buy(lots, g_Symbol, 0, sl, tp, comment) + : g_Trade.Sell(lots, g_Symbol, 0, sl, tp, comment); + + if(!result) + { + PrintFormat("NANDR EA: Ladder trade failed. Step=%s Session=%s Error=%d", + stepTag, g_Sessions[sessIdx].name, GetLastError()); + return false; + } + + g_TodayTrades++; + g_Sessions[sessIdx].tradesThisSession++; + g_Sessions[sessIdx].ladderTradesThisSession++; + g_Sessions[sessIdx].breakoutEntryTaken = true; + + PrintFormat("NANDR EA: Ladder trade opened. Step=%s Dir=%s Lots=%.2f Entry=%.2f SL=%.2f TP=%.2f Session=%s", + stepTag, (dir > 0 ? "BUY" : "SELL"), lots, entry, sl, tp, g_Sessions[sessIdx].name); + if(InpShowTradeLabels) + DrawTradeLabel(dir, entry, sl, tp); + + return true; +} + +bool IsLadderEnabledForSession(int sessIdx) +{ + string name = g_Sessions[sessIdx].name; + if(name == "DailyOpen") return InpLadderOnDailyOpen; + if(name == "Tokyo") return InpLadderOnTokyo; + if(name == "London") return InpLadderOnLondon; + if(name == "NY") return InpLadderOnNY; + if(name == "NYORB") return InpLadderOnNYORB; + return true; +} + +bool TrySessionLadderEntry(int sessIdx, + double c1, + double h_cur, + double l_cur, + double ask, + double bid, + double tol) +{ + if(!InpUseSessionLevelLadder) return false; + if(!IsLadderEnabledForSession(sessIdx)) return false; + if(!g_Sessions[sessIdx].enabled || !g_Sessions[sessIdx].orbComplete) return false; + if(!g_Sessions[sessIdx].inBreakout) return false; + 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; + + double orbHigh = g_Sessions[sessIdx].orbHigh; + double orbLow = g_Sessions[sessIdx].orbLow; + double orbMid = SessionMid(g_Sessions[sessIdx]); + + if(g_Sessions[sessIdx].breakoutDir > 0) + { + if(!g_Sessions[sessIdx].ladderBullLowDone + && c1 > orbLow && l_cur <= orbLow && ask >= orbLow - tol) + { + if(OpenLadderTrade(sessIdx, 1, orbLow, orbMid, "LOW_TO_MID")) + { + g_Sessions[sessIdx].ladderBullLowDone = true; + g_Sessions[sessIdx].retestFiredThisBar = true; + return true; + } + } + + if(g_Sessions[sessIdx].ladderBullLowDone + && !g_Sessions[sessIdx].ladderBullMidDone + && c1 > orbMid && l_cur <= orbMid && ask >= orbMid - tol) + { + if(OpenLadderTrade(sessIdx, 1, orbMid, orbHigh, "MID_TO_HIGH")) + { + g_Sessions[sessIdx].ladderBullMidDone = true; + g_Sessions[sessIdx].retestFiredThisBar = true; + return true; + } + } + } + else if(g_Sessions[sessIdx].breakoutDir < 0) + { + if(!g_Sessions[sessIdx].ladderBearHighDone + && c1 < orbHigh && h_cur >= orbHigh && bid <= orbHigh + tol) + { + if(OpenLadderTrade(sessIdx, -1, orbHigh, orbMid, "HIGH_TO_MID")) + { + g_Sessions[sessIdx].ladderBearHighDone = true; + g_Sessions[sessIdx].retestFiredThisBar = true; + return true; + } + } + + if(g_Sessions[sessIdx].ladderBearHighDone + && !g_Sessions[sessIdx].ladderBearMidDone + && c1 < orbMid && h_cur >= orbMid && bid <= orbMid + tol) + { + if(OpenLadderTrade(sessIdx, -1, orbMid, orbLow, "MID_TO_LOW")) + { + g_Sessions[sessIdx].ladderBearMidDone = true; + g_Sessions[sessIdx].retestFiredThisBar = true; + return true; + } + } + } + + return false; +} + //+------------------------------------------------------------------+ //| Cancel all pending orders | //+------------------------------------------------------------------+ @@ -1080,70 +1298,34 @@ void CheckRetestEntriesTick() if(g_Sessions[s].breakoutDir == 1) // Bullish { - // Standard: prev close above orbHigh, wick dipped to orbHigh - if(c1 > orbHigh && l_cur <= orbHigh) + // Continuation retest: bullish breakout remains active and wick retests level. + // Use live ask filter for confirmation instead of flipping direction intrabar. + if(l_cur <= orbHigh && ask >= orbHigh - tol) { orbLevel = orbHigh; - // ask still above orbHigh → support held → BUY - // ask dropped below orbHigh → sweep failed → SELL, flip to bearish - if(ask >= orbHigh - tol) - dir = 1; - else - { - dir = -1; - g_Sessions[s].breakoutDir = -1; - g_Sessions[s].breakoutEntryTaken = false; - g_GlobalBiasDir = -1; // tick sweep below orbHigh — day bias flips bearish - PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL [DayBias=%d]", - g_Sessions[s].name, orbHigh, g_GlobalBiasDir); - } + dir = 1; } - // Mid retest: prev close above mid, wick dipped to mid - else if(c1 > orbMid && l_cur <= orbMid) + // Mid retest continuation. + else if(l_cur <= orbMid && ask >= orbMid - tol) { orbLevel = orbMid; - if(ask >= orbMid - tol) - dir = 1; - else - { - dir = -1; - g_Sessions[s].breakoutDir = -1; - g_Sessions[s].breakoutEntryTaken = false; - } + dir = 1; } } else if(g_Sessions[s].breakoutDir == -1) // Bearish { - // Standard: prev close below orbLow, wick came back up to orbLow - if(c1 < orbLow && h_cur >= orbLow) + // Continuation retest: bearish breakout remains active and wick retests level. + // Use live bid filter for confirmation instead of flipping direction intrabar. + if(h_cur >= orbLow && bid <= orbLow + tol) { orbLevel = orbLow; - // bid still below orbLow → resistance held → SELL (continuation) - // bid moved above orbLow → sweep reversal → BUY, flip to bullish - if(bid <= orbLow + tol) - dir = -1; - else - { - dir = 1; - g_Sessions[s].breakoutDir = 1; - g_Sessions[s].breakoutEntryTaken = false; - g_GlobalBiasDir = 1; // tick sweep above orbLow — day bias flips bullish - PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY [DayBias=%d]", - g_Sessions[s].name, orbLow, g_GlobalBiasDir); - } + dir = -1; } - // Mid retest: prev close below mid, wick came back up to mid - else if(c1 < orbMid && h_cur >= orbMid) + // Mid retest continuation. + else if(h_cur >= orbMid && bid <= orbMid + tol) { orbLevel = orbMid; - if(bid <= orbMid + tol) - dir = -1; - else - { - dir = 1; - g_Sessions[s].breakoutDir = 1; - g_Sessions[s].breakoutEntryTaken = false; - } + dir = -1; } } @@ -1160,6 +1342,17 @@ void CheckRetestEntriesTick() } } + // ORB retest has priority. Ladder logic runs as fallback only when no active + // exposure exists after ORB checks in this tick. + if(InpUseSessionLevelLadder && !HasActiveExposure()) + { + for(int s = 0; s < g_SessionCount; s++) + { + if(TrySessionLadderEntry(s, c1, h_cur, l_cur, ask, bid, tol)) + break; + } + } + if(!InpUseOBRetestEntry) return; // ── Bearish OB Retest → SELL at wick touch ─────────────────────── @@ -1264,6 +1457,7 @@ void CheckEntrySignals() g_Sessions[s].breakoutDir = 0; g_Sessions[s].inRetest = false; g_Sessions[s].breakoutEntryTaken = false; + ResetLadderProgress(s); continue; } diff --git a/README.md b/README.md index e373aee..c45a657 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,14 @@ A MetaTrader 5 Expert Advisor for scalping **XAUUSD (Gold/USD)** on **M15** (als Entry is taken when the ORB retest aligns with a nearby Order Block (optional — configurable). +### Session Level Ladder (New) +- After a confirmed session breakout direction, the EA can trade between that session's ORB levels: +- Bullish ladder: `Low -> Mid`, then `Mid -> High` +- Bearish ladder: `High -> Mid`, then `Mid -> Low` +- Confirmation for ladder entries uses wick retest plus bar-close context. +- Priority is ORB retest first, with ladder as fallback. +- Ladder TP is always the next session level. SL is user-selectable (fixed pips or source-level buffer). + --- ## Timeframe Scaling @@ -91,6 +99,14 @@ All sessions are **disabled by default** — enable the ones you want to trade: | `InpEntryMode` | `Market` | Market order on retest close, or Limit at ORB level | | `InpUseStrictFilter` | `true` | Strict N-bar breakout filter (recommended) | +### Session Level Ladder Settings +| Parameter | Default | Description | +|-----------|---------|-------------| +| `InpUseSessionLevelLadder` | `true` | Enable level-to-level ladder entries after breakout direction confirmation | +| `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) | + ### Lot Size & Risk | Parameter | Default | Description | |-----------|---------|-------------|