diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 717203d..9185b94 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.396" +#property version "1.40" #property strict #include @@ -61,6 +61,25 @@ enum ENUM_LADDER_SL_MODE LADDER_SL_LEVEL_BUFFER // Buffer beyond source level }; +// Coupled ORB risk methods (per ORB strategy spec). Each option fixes BOTH the +// stop-loss placement and the reward:risk ratio together. +enum ENUM_ORB_RISK_METHOD +{ + ORB_RISK_LEGACY, // Use InpSLMode / InpTPMode / InpRRRatio (manual) + ORB_RISK_BOUNDARY_1_2, // SL at broken range boundary → 1:2 RR + ORB_RISK_MID_1_1_5, // SL at range midpoint (50%) → 1:1.5 RR + ORB_RISK_OPPOSITE_1_1 // SL at opposite range boundary→ 1:1 RR +}; + +// Daily directional bias control. +enum ENUM_BIAS_MODE +{ + BIAS_AUTO, // Auto: first ORB breakout of the day sets the tradeable direction + BIAS_OFF, // Flat: take no trades today (news / high-uncertainty days) + BIAS_LONG_ONLY, // Manual: only long entries allowed + BIAS_SHORT_ONLY // Manual: only short entries allowed +}; + //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ @@ -107,9 +126,9 @@ input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode input bool InpUseStrictFilter = false; // Use Strict Breakout Filter (false=simple close-cross) input bool InpWaitForRetest = true; // Wait for ORB Retest Before Entry input int InpBreakoutExpireBars = 20; // Bars Before Breakout Expires (0=never) -input bool InpUseOBRetestEntry = true; // OB Retest Entry (enter on OB boundary retest) +input bool InpUseOBRetestEntry = false; // OB Retest Entry (optional add-on, default OFF) input int InpMaxRetestsPerSession = 3; // Max Retest Entries Per Session (per breakout) -input bool InpUseSessionBiasFilter = true; // Session Bias Filter: block signals against day bias +input ENUM_BIAS_MODE InpDailyBiasMode = BIAS_AUTO; // Daily Bias: AUTO / OFF(no trades) / LONG-only / SHORT-only 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 @@ -142,16 +161,17 @@ input double InpRiskPercent = 0.05; // Risk % per Trade // --- Stop Loss --- input group "═══ Stop Loss ═══" -input ENUM_SL_MODE InpSLMode = SL_OB_BOUNDARY; // SL Mode +input ENUM_ORB_RISK_METHOD InpOrbRiskMethod = ORB_RISK_BOUNDARY_1_2; // ORB Risk Method (sets SL + RR together) +input ENUM_SL_MODE InpSLMode = SL_OB_BOUNDARY; // SL Mode (used only when Risk Method = LEGACY) input double InpSLPips = 100.0; // SL Fixed Pips input double InpATRMultiplier = 1.5; // ATR Multiplier (for SL_ATR) input int InpATRPeriod = 14; // ATR Period // --- Take Profit --- input group "═══ Take Profit ═══" -input ENUM_TP_MODE InpTPMode = TP_RR_RATIO; // TP Mode +input ENUM_TP_MODE InpTPMode = TP_RR_RATIO; // TP Mode (used only when Risk Method = LEGACY) input double InpTPPips = 100.0; // TP Fixed Pips -input double InpRRRatio = 1.0; // Risk:Reward Ratio +input double InpRRRatio = 2.0; // Risk:Reward Ratio (LEGACY mode) // --- Trade Management --- input group "═══ Trade Management ═══" @@ -269,9 +289,10 @@ SOrderBlock g_BearOBs[]; int g_BullOBCount = 0; int g_BearOBCount = 0; -// Day bias: direction of the first confirmed ORB breakout of the day (0=none, 1=bull, -1=bear). -// InpUseSessionBiasFilter keeps bias context for diagnostics and selective filters. -// ORB breakout detection is not suppressed by bias; OB retest entries may still be filtered. +// Day bias: in AUTO mode this holds the direction of the first confirmed ORB +// breakout of the day (0=none, 1=bull, -1=bear). In manual bias modes it is unused. +// Breakout DETECTION is never suppressed (levels/state keep updating); entries are +// gated by IsDirectionAllowed() so the bias only filters trade execution. int g_GlobalBiasDir = 0; // Tracks tickets that have already had partial close executed so it only fires once per position @@ -471,6 +492,78 @@ double CalcTP(int dir, double entry, double sl) return NormalizeDouble(tp, (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); } +//+------------------------------------------------------------------+ +//| Coupled ORB risk: derive SL and TP together from the opening | +//| range, per the selected risk method. | +//| BOUNDARY → SL at broken boundary (long=ORH, short=ORL), 1:2 | +//| MID → SL at range midpoint, 1:1.5 | +//| OPPOSITE → SL at opposite boundary (long=ORL, short=ORH), 1:1 | +//| Returns false when the method is LEGACY or the geometry is | +//| invalid (caller then falls back to CalcSL/CalcTP). | +//+------------------------------------------------------------------+ +bool CalcOrbRisk(int dir, double entry, double orbHigh, double orbLow, + double &slOut, double &tpOut) +{ + if(InpOrbRiskMethod == ORB_RISK_LEGACY) return false; + if(orbHigh <= 0 || orbLow >= DBL_MAX || orbHigh <= orbLow) return false; + + double buf = PipsToPrice(2.0); + double mid = (orbHigh + orbLow) / 2.0; + double slLevel = 0.0; + double rr = 0.0; + + switch(InpOrbRiskMethod) + { + case ORB_RISK_BOUNDARY_1_2: + slLevel = (dir > 0) ? orbHigh - buf : orbLow + buf; + rr = 2.0; + break; + case ORB_RISK_MID_1_1_5: + slLevel = mid; + rr = 1.5; + break; + case ORB_RISK_OPPOSITE_1_1: + slLevel = (dir > 0) ? orbLow - buf : orbHigh + buf; + rr = 1.0; + break; + default: + return false; + } + + // SL must sit on the correct side of entry. + if((dir > 0 && slLevel >= entry) || (dir < 0 && slLevel <= entry)) + return false; + + double slDist = MathAbs(entry - slLevel); + if(slDist <= 0) return false; + + double tpLevel = (dir > 0) ? entry + slDist * rr : entry - slDist * rr; + + int digits = (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS); + slOut = NormalizeDouble(slLevel, digits); + tpOut = NormalizeDouble(tpLevel, digits); + return true; +} + +//+------------------------------------------------------------------+ +//| Daily bias gate: is a trade in this direction allowed today? | +//+------------------------------------------------------------------+ +bool IsDirectionAllowed(int dir) +{ + switch(InpDailyBiasMode) + { + case BIAS_OFF: return false; // skip the day entirely + case BIAS_LONG_ONLY: return (dir > 0); + case BIAS_SHORT_ONLY: return (dir < 0); + case BIAS_AUTO: + default: + // First breakout of the day establishes the direction; afterwards only + // trades aligned with that direction are permitted. + if(g_GlobalBiasDir == 0) return true; + return (dir == g_GlobalBiasDir); + } +} + //+------------------------------------------------------------------+ //| Initialize sessions | //+------------------------------------------------------------------+ @@ -750,35 +843,25 @@ void DetectBreakouts() if(bullBO) { - // Bias filter note: do not suppress ORB breakout detection. - if(InpUseSessionBiasFilter && g_GlobalBiasDir == -1) - { - PrintFormat("NANDR EA: [%s] Bullish breakout detected while day bias is BEARISH (not suppressed)", - g_Sessions[s].name); - } g_Sessions[s].breakoutDir = 1; 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 + // In AUTO bias mode, the first breakout of the day sets the tradeable direction. + if(InpDailyBiasMode == BIAS_AUTO && g_GlobalBiasDir == 0) g_GlobalBiasDir = 1; PrintFormat("NANDR EA: [%s] Bullish ORB breakout at %.2f [DayBias=%d]", g_Sessions[s].name, g_Sessions[s].orbHigh, g_GlobalBiasDir); } else if(bearBO) { - // Bias filter note: do not suppress ORB breakout detection. - if(InpUseSessionBiasFilter && g_GlobalBiasDir == 1) - { - PrintFormat("NANDR EA: [%s] Bearish breakout detected while day bias is BULLISH (not suppressed)", - g_Sessions[s].name); - } g_Sessions[s].breakoutDir = -1; 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 + // In AUTO bias mode, the first breakout of the day sets the tradeable direction. + if(InpDailyBiasMode == BIAS_AUTO && g_GlobalBiasDir == 0) g_GlobalBiasDir = -1; PrintFormat("NANDR EA: [%s] Bearish ORB breakout at %.2f [DayBias=%d]", g_Sessions[s].name, g_Sessions[s].orbLow, g_GlobalBiasDir); } @@ -812,7 +895,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 + if(InpDailyBiasMode == BIAS_AUTO) g_GlobalBiasDir = -1; // swept & closed below → bias bearish PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH [DayBias=%d]", g_Sessions[sessIdx].name, orbHigh, g_GlobalBiasDir); } @@ -836,7 +919,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 + if(InpDailyBiasMode == BIAS_AUTO) g_GlobalBiasDir = 1; // swept & closed above → bias bullish PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH [DayBias=%d]", g_Sessions[sessIdx].name, orbLow, g_GlobalBiasDir); } @@ -1291,6 +1374,7 @@ bool TrySessionLadderEntry(int sessIdx, if(!IsLadderEnabledForSession(sessIdx)) return false; if(!g_Sessions[sessIdx].enabled || !g_Sessions[sessIdx].orbComplete) return false; if(!g_Sessions[sessIdx].inBreakout) return false; + if(!IsDirectionAllowed(g_Sessions[sessIdx].breakoutDir)) return false; if(HasActiveExposureForSession(sessIdx)) return false; if(g_Sessions[sessIdx].retestFiredThisBar) return false; if(InpBreakoutExpireBars > 0 && g_Sessions[sessIdx].breakoutBarsAgo >= InpBreakoutExpireBars) return false; @@ -1383,6 +1467,7 @@ void CancelAllPending() void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBottom) { if(g_TradingHalted) return; + if(!IsDirectionAllowed(dir)) return; if(g_TodayTrades >= InpMaxTradesPerDay) return; if(g_Sessions[sessIdx].tradesThisSession >= InpMaxRetestsPerSession) return; if(HasActiveExposureForSession(sessIdx)) return; @@ -1396,9 +1481,15 @@ 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, - g_Sessions[sessIdx].orbHigh, g_Sessions[sessIdx].orbLow); - double tp = CalcTP(dir, entry, sl); + // Risk: prefer the coupled ORB risk method (SL+TP from the range); fall back + // to the legacy SL/TP inputs when the method is LEGACY or the geometry fails. + double sl, tp; + if(!CalcOrbRisk(dir, entry, g_Sessions[sessIdx].orbHigh, g_Sessions[sessIdx].orbLow, sl, tp)) + { + sl = CalcSL(dir, entry, obBottom, obTop, + g_Sessions[sessIdx].orbHigh, g_Sessions[sessIdx].orbLow); + tp = CalcTP(dir, entry, sl); + } double slPips = PriceToPips(MathAbs(entry - sl)); double lots = CalcLotSize(slPips); @@ -1538,9 +1629,8 @@ void CheckRetestEntriesTick() if(!InpUseOBRetestEntry) return; // ── Bearish OB Retest → SELL at wick touch ─────────────────────── - // Suppressed when day bias is bullish (session takes priority). - if(InpUseSessionBiasFilter && g_GlobalBiasDir == 1) return; - + // Gated by the daily bias (only short entries when bias permits). + if(IsDirectionAllowed(-1)) for(int i = 0; i < g_BearOBCount; i++) { if(!g_BearOBs[i].active || g_BearOBs[i].traded) continue; @@ -1579,9 +1669,8 @@ void CheckRetestEntriesTick() } // ── Bullish OB Retest → BUY at wick touch ──────────────────────── - // Suppressed when day bias is bearish (session takes priority). - if(InpUseSessionBiasFilter && g_GlobalBiasDir == -1) return; - + // Gated by the daily bias (only long entries when bias permits). + if(IsDirectionAllowed(1)) for(int i = 0; i < g_BullOBCount; i++) { if(!g_BullOBs[i].active || g_BullOBs[i].traded) continue; diff --git a/README.md b/README.md index 4b674d6..6a6b53c 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,36 @@ A MetaTrader 5 Expert Advisor for scalping **XAUUSD (Gold/USD)** on **M15** (als ### Opening Range Breakout 1. At the start of each enabled session, accumulate the **ORB High** and **ORB Low** over the first 15 real-time minutes -2. Detect a **confirmed breakout** using a strict N-bar filter (no false breaks) +2. Detect a **confirmed breakout** (candle closing beyond the range; optional strict N-bar filter to avoid false breaks) 3. Wait for price to **retest** the broken ORB level -4. Enter on the retest +4. Enter on the retest, sized and stopped by the selected **ORB Risk Method** + +### ORB Risk Methods (coupled SL + Reward:Risk) +The `InpOrbRiskMethod` input selects a risk profile that fixes **both** the stop-loss +placement and the reward:risk target together, per the ORB strategy specification: + +| Method | Stop Loss | Reward:Risk | +|--------|-----------|-------------| +| `Boundary 1:2` (default) | Broken range boundary (long=ORH, short=ORL) | 1:2 | +| `Mid 1:1.5` | Range midpoint (50%) | 1:1.5 | +| `Opposite 1:1` | Opposite range boundary (long=ORL, short=ORH) | 1:1 | +| `Legacy` | Uses `InpSLMode` / `InpTPMode` / `InpRRRatio` manually | — | + +If the chosen method produces an invalid stop (wrong side of entry), the EA falls +back to the legacy `InpSLMode` / `InpTPMode` settings. + +### Daily Bias +The `InpDailyBiasMode` input controls which trade directions are permitted each day: + +| Mode | Behaviour | +|------|-----------| +| `Auto` (default) | The first confirmed ORB breakout of the day sets the tradeable direction; later trades must align with it | +| `Off` | No trades taken today (use on news / high-uncertainty days) | +| `Long only` | Only long entries processed | +| `Short only` | Only short entries processed | + +Breakout **detection** is never suppressed — only trade **execution** is gated, so +levels and structure keep updating regardless of bias. ### Order Block Confirmation - A **volume pivot** is identified where the bar at index `[length]` has the highest volume in a symmetric window @@ -22,11 +49,11 @@ A MetaTrader 5 Expert Advisor for scalping **XAUUSD (Gold/USD)** on **M15** (als - **Bearish OB zone**: `[hl2, high]` of the pivot bar - OBs are removed (mitigated) when price penetrates the zone -Entry is taken when the ORB retest aligns with a nearby Order Block (optional — configurable). +Entry is taken when the ORB retest aligns with a nearby Order Block (optional add-on, **disabled by default** via `InpUseOBRetestEntry`). Active exposure is session-scoped: one open trade is allowed per session, and different sessions may each hold one trade on the same day. -### Session Level Ladder (New) +### Session Level Ladder (optional, disabled by default) - 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` @@ -108,7 +135,12 @@ server time internally. | Parameter | Default | Description | |-----------|---------|-------------| | `InpEntryMode` | `Market` | Market order on retest close, or Limit at ORB level | -| `InpUseStrictFilter` | `true` | Strict N-bar breakout filter (recommended) | +| `InpUseStrictFilter` | `false` | Strict N-bar breakout filter (`false` = simple close-cross) | +| `InpWaitForRetest` | `true` | Wait for an ORB retest before entering | +| `InpBreakoutExpireBars` | `20` | Bars before an armed breakout expires (`0` = never) | +| `InpUseOBRetestEntry` | `false` | Order Block retest entry add-on (optional, default OFF) | +| `InpMaxRetestsPerSession` | `3` | Max retest entries per session (per breakout) | +| `InpDailyBiasMode` | `Auto` | Daily bias: Auto / Off (no trades) / Long-only / Short-only | | `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 | @@ -116,7 +148,7 @@ server time internally. ### Session Level Ladder Settings | Parameter | Default | Description | |-----------|---------|-------------| -| `InpUseSessionLevelLadder` | `true` | Enable level-to-level ladder entries after breakout direction confirmation | +| `InpUseSessionLevelLadder` | `false` | Enable level-to-level ladder entries after breakout direction confirmation (optional, default OFF) | | `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 | @@ -138,23 +170,24 @@ server time internally. | Parameter | Default | Description | |-----------|---------|-------------| | `InpLotMode` | `Risk %` | Fixed lot or auto-calculated from risk % | -| `InpFixedLotSize` | `0.01` | Fixed lot (used when LotMode = Fixed) | -| `InpRiskPercent` | `1.0` | % of balance to risk per trade | +| `InpFixedLotSize` | `0.05` | Fixed lot (used when LotMode = Fixed) | +| `InpRiskPercent` | `0.05` | % of balance to risk per trade | ### Stop Loss | Parameter | Default | Description | |-----------|---------|-------------| -| `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) | +| `InpOrbRiskMethod` | `Boundary 1:2` | Coupled risk method (sets SL + RR together); `Legacy` uses the manual settings below | +| `InpSLMode` | `OB Boundary` | SL placement (Legacy only): OB Boundary / Fixed Pips / ATR-based / ORB Opposite Boundary / ORB Range Midpoint | +| `InpSLPips` | `100.0` | SL in pips (Fixed mode) | | `InpATRMultiplier` | `1.5` | ATR multiplier for SL (ATR mode) | | `InpATRPeriod` | `14` | ATR period | ### Take Profit | Parameter | Default | Description | |-----------|---------|-------------| -| `InpTPMode` | `RR Ratio` | TP by fixed pips or Risk:Reward ratio | -| `InpTPPips` | `40.0` | TP in pips (Fixed mode) | -| `InpRRRatio` | `2.0` | Risk:Reward ratio (RR mode) — 2.0 = 1:2 | +| `InpTPMode` | `RR Ratio` | TP by fixed pips or Risk:Reward ratio (Legacy only) | +| `InpTPPips` | `100.0` | TP in pips (Fixed mode) | +| `InpRRRatio` | `2.0` | Risk:Reward ratio (Legacy RR mode) — 2.0 = 1:2 | ### Trade Management | Parameter | Default | Description | @@ -162,7 +195,7 @@ server time internally. | `InpBreakevenTrigPips` | `15.0` | Move SL to breakeven when profit reaches this | | `InpBreakevenOffPips` | `2.0` | Breakeven SL offset above/below entry | | `InpTrailingStopPips` | `0.0` | Trailing stop distance — `0` = disabled | -| `InpMaxTradesPerDay` | `3` | Max trades opened per calendar day | +| `InpMaxTradesPerDay` | `8` | Max trades opened per calendar day | | `InpMaxPosPerSession` | `1` | Max simultaneous positions per session | ### Risk Management