From 338eead5224bd81e09f4c7c72e008d38e6554880 Mon Sep 17 00:00:00 2001 From: Naji El Chemaly Date: Thu, 11 Jun 2026 12:18:03 +0300 Subject: [PATCH] feat: ORB-native SL modes added (boundary stays default), All session start times are now configurable, Optional breakout volume filter (default OFF) --- NANDR_ORB_OB_EA.mq5 | 115 +++++++++++++++++++++++++++++++++++--------- README.md | 18 +++++-- 2 files changed, 107 insertions(+), 26 deletions(-) diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index cca48ff..717203d 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.395" +#property version "1.396" #property strict #include @@ -26,7 +26,9 @@ enum ENUM_SL_MODE { SL_OB_BOUNDARY, // OB Boundary SL_FIXED_PIPS, // Fixed Pips - SL_ATR_BASED // ATR-Based + SL_ATR_BASED, // ATR-Based + SL_ORB_OPPOSITE, // ORB Opposite Boundary (buy=ORL, sell=ORH) + SL_ORB_MID // ORB Range Midpoint }; enum ENUM_TP_MODE @@ -76,9 +78,20 @@ input int InpBreakoutConfBars = 0; // Breakout Confirm Bars input group "═══ Session Settings ═══" input bool InpUseDailyOpen = true; // Daily Open Session (22:00 UTC, 01:00 Kuwait) input bool InpUseTokyoSession = true; // Tokyo Session (00:00 UTC) -input bool InpUseLondonSession = true; // London Session (07:00 UTC) +input bool InpUseLondonSession = true; // London Session (08:00 UTC = 8AM GMT) input bool InpUseNYSession = true; // NY Session (12:00 UTC) -input bool InpUseNYOrbSession = true; // NY ORB Session (13:30 UTC) +input bool InpUseNYOrbSession = true; // NY ORB Session (13:30 UTC = 9:30 EDT) +// Session start times are UTC-based (converted to broker server time internally). +input int InpDailyOpenHour = 22; // Daily Open Start Hour (UTC) +input int InpDailyOpenMin = 0; // Daily Open Start Minute +input int InpTokyoHour = 0; // Tokyo Start Hour (UTC) +input int InpTokyoMin = 0; // Tokyo Start Minute +input int InpLondonHour = 8; // London Start Hour (UTC, 8=8AM GMT) +input int InpLondonMin = 0; // London Start Minute +input int InpNYHour = 12; // NY Start Hour (UTC) +input int InpNYMin = 0; // NY Start Minute +input int InpNYOrbHour = 13; // NY ORB Start Hour (UTC) +input int InpNYOrbMin = 30; // NY ORB Start Minute // --- Order Block Settings --- input group "═══ Order Block Settings ═══" @@ -97,6 +110,9 @@ input int InpBreakoutExpireBars = 20; // Bars Before Breakout input bool InpUseOBRetestEntry = true; // OB Retest Entry (enter on OB boundary retest) input int InpMaxRetestsPerSession = 3; // Max Retest Entries Per Session (per breakout) input bool InpUseSessionBiasFilter = true; // Session Bias Filter: block signals against day bias +input bool InpUseBreakoutVolFilter = false; // Require Volume Spike on Breakout Bar (fakeout filter) +input double InpBreakoutVolMult = 1.5; // Breakout Volume >= X * Average Volume +input int InpBreakoutVolAvgBars = 20; // Bars to Average Volume Over // --- Session Level Ladder --- input group "═══ Session Level Ladder ═══" @@ -389,13 +405,15 @@ double CalcLotSize(double slPips) //+------------------------------------------------------------------+ //| Calculate SL price | //+------------------------------------------------------------------+ -double CalcSL(int dir, double entry, double obBottom, double obTop) +double CalcSL(int dir, double entry, double obBottom, double obTop, double orbHigh, double orbLow) { double sl = 0; + double fixedFallback = (dir > 0) ? entry - PipsToPrice(InpSLPips) + : entry + PipsToPrice(InpSLPips); + if(InpSLMode == SL_FIXED_PIPS) { - sl = (dir > 0) ? entry - PipsToPrice(InpSLPips) - : entry + PipsToPrice(InpSLPips); + sl = fixedFallback; } else if(InpSLMode == SL_OB_BOUNDARY) { @@ -404,18 +422,31 @@ double CalcSL(int dir, double entry, double obBottom, double obTop) else if(dir < 0 && obTop > 0) sl = obTop + PipsToPrice(2.0); // 2 pip buffer above OB top else - sl = (dir > 0) ? entry - PipsToPrice(InpSLPips) - : entry + PipsToPrice(InpSLPips); + sl = fixedFallback; } - else // ATR + else if(InpSLMode == SL_ORB_OPPOSITE) + { + // Buy → SL at ORB Low; Sell → SL at ORB High (2 pip buffer beyond). + double raw = (dir > 0) ? orbLow - PipsToPrice(2.0) + : orbHigh + PipsToPrice(2.0); + bool valid = (dir > 0) ? (raw < entry) : (raw > entry); + sl = valid ? raw : fixedFallback; + } + else if(InpSLMode == SL_ORB_MID) + { + // SL at the midpoint of the opening range. + double mid = (orbHigh + orbLow) / 2.0; + bool valid = (dir > 0) ? (mid < entry) : (mid > entry); + sl = valid ? mid : fixedFallback; + } + else // SL_ATR_BASED { double atr[1]; if(CopyBuffer(g_ATRHandle, 0, 0, 1, atr) > 0) sl = (dir > 0) ? entry - atr[0] * InpATRMultiplier : entry + atr[0] * InpATRMultiplier; else - sl = (dir > 0) ? entry - PipsToPrice(InpSLPips) - : entry + PipsToPrice(InpSLPips); + sl = fixedFallback; } return NormalizeDouble(sl, (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); } @@ -451,8 +482,8 @@ void InitSessions() if(InpUseDailyOpen) { g_Sessions[g_SessionCount].name = "DailyOpen"; - g_Sessions[g_SessionCount].startHour = 22; - g_Sessions[g_SessionCount].startMin = 0; + g_Sessions[g_SessionCount].startHour = InpDailyOpenHour; + g_Sessions[g_SessionCount].startMin = InpDailyOpenMin; g_Sessions[g_SessionCount].enabled = true; g_Sessions[g_SessionCount].Reset(); g_SessionCount++; @@ -461,8 +492,8 @@ void InitSessions() if(InpUseTokyoSession) { g_Sessions[g_SessionCount].name = "Tokyo"; - g_Sessions[g_SessionCount].startHour = 0; - g_Sessions[g_SessionCount].startMin = 0; + g_Sessions[g_SessionCount].startHour = InpTokyoHour; + g_Sessions[g_SessionCount].startMin = InpTokyoMin; g_Sessions[g_SessionCount].enabled = true; g_Sessions[g_SessionCount].Reset(); g_SessionCount++; @@ -471,8 +502,8 @@ void InitSessions() if(InpUseLondonSession) { g_Sessions[g_SessionCount].name = "London"; - g_Sessions[g_SessionCount].startHour = 7; - g_Sessions[g_SessionCount].startMin = 0; + g_Sessions[g_SessionCount].startHour = InpLondonHour; + g_Sessions[g_SessionCount].startMin = InpLondonMin; g_Sessions[g_SessionCount].enabled = true; g_Sessions[g_SessionCount].Reset(); g_SessionCount++; @@ -481,8 +512,8 @@ void InitSessions() if(InpUseNYSession) { g_Sessions[g_SessionCount].name = "NY"; - g_Sessions[g_SessionCount].startHour = 12; - g_Sessions[g_SessionCount].startMin = 0; + g_Sessions[g_SessionCount].startHour = InpNYHour; + g_Sessions[g_SessionCount].startMin = InpNYMin; g_Sessions[g_SessionCount].enabled = true; g_Sessions[g_SessionCount].Reset(); g_SessionCount++; @@ -491,8 +522,8 @@ void InitSessions() if(InpUseNYOrbSession) { g_Sessions[g_SessionCount].name = "NYORB"; - g_Sessions[g_SessionCount].startHour = 13; - g_Sessions[g_SessionCount].startMin = 30; + g_Sessions[g_SessionCount].startHour = InpNYOrbHour; + g_Sessions[g_SessionCount].startMin = InpNYOrbMin; g_Sessions[g_SessionCount].enabled = true; g_Sessions[g_SessionCount].Reset(); g_SessionCount++; @@ -640,6 +671,33 @@ bool CheckSimpleBreakout(int dir, double orbLevel) else return (c1 >= orbLevel && c0 < orbLevel); } +//+------------------------------------------------------------------+ +//| Volume-spike confirmation for the breakout bar (bar[1]) | +//| Optional fakeout filter: requires the just-closed breakout bar's | +//| tick volume to exceed a multiple of the recent average volume. | +//| Returns true (pass) when the filter is disabled or data is thin. | +//+------------------------------------------------------------------+ +bool IsBreakoutVolumeConfirmed() +{ + if(!InpUseBreakoutVolFilter) return true; + + int n = InpBreakoutVolAvgBars; + if(n < 1) n = 1; + + int bars = Bars(g_Symbol, PERIOD_CURRENT); + if(bars < n + 2) return true; // not enough history — don't block + + double sum = 0; + for(int i = 2; i <= n + 1; i++) + sum += (double)iVolume(g_Symbol, PERIOD_CURRENT, i); + + double avg = sum / n; + if(avg <= 0) return true; + + double breakoutVol = (double)iVolume(g_Symbol, PERIOD_CURRENT, 1); + return (breakoutVol >= avg * InpBreakoutVolMult); +} + //+------------------------------------------------------------------+ //| Detect breakout for all sessions | //+------------------------------------------------------------------+ @@ -680,6 +738,16 @@ void DetectBreakouts() } } + // Optional volume-spike confirmation (fakeout filter). Applies to whichever + // direction is breaking out; a weak-volume breakout bar is rejected. + if((bullBO || bearBO) && !IsBreakoutVolumeConfirmed()) + { + PrintFormat("NANDR EA: [%s] Breakout rejected — volume on breakout bar below %.2fx average.", + g_Sessions[s].name, InpBreakoutVolMult); + bullBO = false; + bearBO = false; + } + if(bullBO) { // Bias filter note: do not suppress ORB breakout detection. @@ -1328,7 +1396,8 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot if(InpEntryMode == ENTRY_LIMIT) entry = orbLevel; - double sl = CalcSL(dir, entry, obBottom, obTop); + double sl = CalcSL(dir, entry, obBottom, obTop, + g_Sessions[sessIdx].orbHigh, g_Sessions[sessIdx].orbLow); double tp = CalcTP(dir, entry, sl); double slPips = PriceToPips(MathAbs(entry - sl)); double lots = CalcLotSize(slPips); diff --git a/README.md b/README.md index 13c3e3c..4b674d6 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,13 @@ All sessions are **disabled by default** — enable the ones you want to trade: |---------|-----------|-----------------| | Daily Open | 22:00 | Asian range setup | | Tokyo | 00:00 | Low volume, range-bound | -| London | 07:00 | High volatility ✓ | +| London | 08:00 | High volatility ✓ (8AM GMT) | | NY | 12:00 | High volatility ✓ | -| NY ORB (Stock open) | 13:30 | Strong momentum ✓ | +| NY ORB (Stock open) | 13:30 | Strong momentum ✓ (9:30 EDT) | + +All session start times are **UTC-based** and individually configurable via the +`InpHour` / `InpMin` inputs. The EA converts them to broker +server time internally. --- @@ -85,6 +89,11 @@ All sessions are **disabled by default** — enable the ones you want to trade: | `InpUseLondonSession` | `false` | Enable London session | | `InpUseNYSession` | `false` | Enable NY session | | `InpUseNYOrbSession` | `false` | Enable NY ORB session (13:30 UTC) | +| `InpDailyOpenHour` / `InpDailyOpenMin` | `22` / `0` | Daily Open start (UTC) | +| `InpTokyoHour` / `InpTokyoMin` | `0` / `0` | Tokyo start (UTC) | +| `InpLondonHour` / `InpLondonMin` | `8` / `0` | London start (UTC = 8AM GMT) | +| `InpNYHour` / `InpNYMin` | `12` / `0` | NY start (UTC) | +| `InpNYOrbHour` / `InpNYOrbMin` | `13` / `30` | NY ORB start (UTC = 9:30 EDT) | ### Order Block Settings | Parameter | Default | Description | @@ -100,6 +109,9 @@ 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) | +| `InpUseBreakoutVolFilter` | `false` | Require a volume spike on the breakout bar (fakeout filter) | +| `InpBreakoutVolMult` | `1.5` | Breakout bar volume must be ≥ this × average volume | +| `InpBreakoutVolAvgBars` | `20` | Number of bars used for the volume average | ### Session Level Ladder Settings | Parameter | Default | Description | @@ -132,7 +144,7 @@ All sessions are **disabled by default** — enable the ones you want to trade: ### Stop Loss | Parameter | Default | Description | |-----------|---------|-------------| -| `InpSLMode` | `OB Boundary` | SL placement: OB Boundary / Fixed Pips / ATR-based | +| `InpSLMode` | `OB Boundary` | SL placement: OB Boundary / Fixed Pips / ATR-based / ORB Opposite Boundary (buy=ORL, sell=ORH) / ORB Range Midpoint | | `InpSLPips` | `20.0` | SL in pips (Fixed mode) | | `InpATRMultiplier` | `1.5` | ATR multiplier for SL (ATR mode) | | `InpATRPeriod` | `14` | ATR period |