feat: Coupled ORB risk methods — new InpOrbRiskMethod input with CalcOrbRisk() that sets SL and reward:risk togethe, Daily bias control — new InpDailyBiasMode replacing the old InpUseSessionBiasFilter bool, with IsDirectionAllowed(), Defaults flipped OFF — InpUseOBRetestEntry and InpUseSessionLevelLadder now default false (kept as optional add-ons). InpRRRatio default bumped to 2.0, README updated, Bumped the version to 1.40

This commit is contained in:
Naji El Chemaly
2026-06-11 13:03:35 +03:00
parent 338eead522
commit 8240222ba5
2 changed files with 170 additions and 48 deletions
+123 -34
View File
@@ -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 <Trade\Trade.mqh>
@@ -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;
+47 -14
View File
@@ -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