diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 3bb7927..c63e3ed 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.20" +#property version "1.30" #property strict #include @@ -68,11 +68,11 @@ input int InpBreakoutConfBars = 0; // Breakout Confirm Bars // --- Sessions --- input group "═══ Session Settings ═══" -input bool InpUseDailyOpen = false; // Daily Open Session (00:00 UTC) -input bool InpUseTokyoSession = false; // Tokyo Session (00:00 UTC) -input bool InpUseLondonSession = false; // London Session (07:00 UTC) -input bool InpUseNYSession = false; // NY Session (12:00 UTC) -input bool InpUseNYOrbSession = false; // NY ORB Session (13:30 UTC) +input bool InpUseDailyOpen = true; // Daily Open Session (00:00 UTC) +input bool InpUseTokyoSession = true; // Tokyo Session (00:00 UTC) +input bool InpUseLondonSession = true; // London Session (07:00 UTC) +input bool InpUseNYSession = true; // NY Session (12:00 UTC) +input bool InpUseNYOrbSession = true; // NY ORB Session (13:30 UTC) // --- Order Block Settings --- input group "═══ Order Block Settings ═══" @@ -86,40 +86,41 @@ input double InpOBProximityPips = 50.0; // OB Proximity (pips) input group "═══ Entry Settings ═══" input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode input bool InpUseStrictFilter = false; // Use Strict Breakout Filter (false=simple close-cross) -input bool InpWaitForRetest = false; // Wait for ORB Retest Before Entry -input int InpBreakoutExpireBars = 8; // Bars Before Breakout Expires (0=never) +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) // --- Lot Size & Risk --- input group "═══ Lot Size & Risk ═══" input ENUM_LOT_MODE InpLotMode = LOT_RISK_PERCENT; // Lot Mode -input double InpFixedLotSize = 0.01; // Fixed Lot Size +input double InpFixedLotSize = 0.05; // Fixed Lot Size input double InpRiskPercent = 1.0; // Risk % per Trade // --- Stop Loss --- input group "═══ Stop Loss ═══" input ENUM_SL_MODE InpSLMode = SL_OB_BOUNDARY; // SL Mode -input double InpSLPips = 20.0; // SL Fixed Pips +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 double InpTPPips = 40.0; // TP Fixed Pips -input double InpRRRatio = 2.0; // Risk:Reward Ratio +input double InpTPPips = 100.0; // TP Fixed Pips +input double InpRRRatio = 1.0; // Risk:Reward Ratio // --- Trade Management --- input group "═══ Trade Management ═══" -input double InpBreakevenTrigPips = 15.0; // Breakeven Trigger (pips) +input double InpBreakevenTrigPips = 50.0; // Breakeven Trigger (pips) input double InpBreakevenOffPips = 2.0; // Breakeven Offset (pips) input double InpTrailingStopPips = 0.0; // Trailing Stop Pips (0=off) -input int InpMaxTradesPerDay = 3; // Max Trades per Day -input int InpMaxPosPerSession = 1; // Max Positions per Session +input int InpMaxTradesPerDay = 8; // Max Trades per Day +input int InpMaxPosPerSession = 2; // Max Positions per Session // --- Risk Management --- input group "═══ Risk Management ═══" -input double InpMaxDailyLossUSD = 0.0; // Max Daily Loss USD (0=off) -input double InpMaxDailyLossPct = 0.0; // Max Daily Loss % (0=off) +input double InpMaxDailyLossUSD = 150.0; // Max Daily Loss USD (0=off) +input double InpMaxDailyLossPct = 5.0; // Max Daily Loss % (0=off) // --- Display --- input group "═══ Display Settings ═══" @@ -179,6 +180,7 @@ struct SOrderBlock double mid; datetime time; bool active; + bool traded; // Prevents re-entering the same OB zone twice in one day string objName; }; @@ -605,35 +607,51 @@ bool DetectRetest(int sessIdx, int &dir) double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1); - // Bullish retest: bar[2] confirmed above ORB high, bar[1] dipped to ORB and closed back above + double orbMid = (g_Sessions[sessIdx].orbHigh + g_Sessions[sessIdx].orbLow) / 2.0; + + // Bullish retest: standard ORB high retest OR mid-level retest if(g_Sessions[sessIdx].breakoutDir == 1) { - bool retest = (c1 > g_Sessions[sessIdx].orbHigh) - && (l0 <= g_Sessions[sessIdx].orbHigh) - && (c0 >= g_Sessions[sessIdx].orbHigh); - if(retest) { dir = 1; return true; } + // Standard: previous bar above ORB high, current bar wick dips to ORB high, closes back above + bool retestHigh = (c1 > g_Sessions[sessIdx].orbHigh) + && (l0 <= g_Sessions[sessIdx].orbHigh) + && (c0 >= g_Sessions[sessIdx].orbHigh); + // Mid retest: price bounced back down to ORB mid after breakout, closes back above mid + bool retestMid = (c1 > orbMid) + && (l0 <= orbMid) + && (c0 > orbMid); + if(retestHigh || retestMid) { dir = 1; return true; } } - // Bearish retest: bar[2] confirmed below ORB low, bar[1] ticked back to ORB and closed back below + // Bearish retest: standard ORB low retest OR mid-level retest else if(g_Sessions[sessIdx].breakoutDir == -1) { - bool retest = (c1 < g_Sessions[sessIdx].orbLow) - && (h0 >= g_Sessions[sessIdx].orbLow) - && (c0 <= g_Sessions[sessIdx].orbLow); - if(retest) { dir = -1; return true; } + // Standard: previous bar below ORB low, current bar wick ticks back to ORB low, closes back below + bool retestLow = (c1 < g_Sessions[sessIdx].orbLow) + && (h0 >= g_Sessions[sessIdx].orbLow) + && (c0 <= g_Sessions[sessIdx].orbLow); + // Mid retest: price bounced above ORB low up to the mid, closes back below mid + // This captures entries like a 14:45 retest of the opening-day ORB mid after the break + bool retestMid = (c1 < orbMid) + && (h0 >= orbMid) + && (c0 < orbMid); + if(retestLow || retestMid) { dir = -1; return true; } } - // Failed retest — only on a COMPLETED bar, never on bar open + // Failed retest — threshold raised to orbMid so a simple bounce above orbLow/below orbHigh + // does NOT cancel the breakout prematurely; only a close through the mid invalidates it. if(g_Sessions[sessIdx].breakoutDir == 1 - && c0 < g_Sessions[sessIdx].orbHigh && c1 > g_Sessions[sessIdx].orbHigh) + && c0 < orbMid && c1 > orbMid) { - PrintFormat("NANDR EA: [%s] Failed bullish retest", g_Sessions[sessIdx].name); + PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)", + g_Sessions[sessIdx].name, orbMid); g_Sessions[sessIdx].inBreakout = false; g_Sessions[sessIdx].breakoutDir = 0; } else if(g_Sessions[sessIdx].breakoutDir == -1 - && c0 > g_Sessions[sessIdx].orbLow && c1 < g_Sessions[sessIdx].orbLow) + && c0 > orbMid && c1 < orbMid) { - PrintFormat("NANDR EA: [%s] Failed bearish retest", g_Sessions[sessIdx].name); + PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)", + g_Sessions[sessIdx].name, orbMid); g_Sessions[sessIdx].inBreakout = false; g_Sessions[sessIdx].breakoutDir = 0; } @@ -701,6 +719,7 @@ void ScanOrderBlocks() g_BullOBs[g_BullOBCount].mid = (hl2 + pivotLow) / 2.0; g_BullOBs[g_BullOBCount].time = obTime; g_BullOBs[g_BullOBCount].active = true; + g_BullOBs[g_BullOBCount].traded = false; g_BullOBs[g_BullOBCount].objName= "NANDR_BullOB_" + IntegerToString(obTime); g_BullOBCount++; if(InpShowOBZones) DrawOBZone(g_BullOBCount - 1, true); @@ -719,6 +738,7 @@ void ScanOrderBlocks() g_BearOBs[g_BearOBCount].mid = (pivotHigh + hl2) / 2.0; g_BearOBs[g_BearOBCount].time = obTime; g_BearOBs[g_BearOBCount].active = true; + g_BearOBs[g_BearOBCount].traded = false; g_BearOBs[g_BearOBCount].objName= "NANDR_BearOB_" + IntegerToString(obTime); g_BearOBCount++; if(InpShowOBZones) DrawOBZone(g_BearOBCount - 1, false); @@ -922,6 +942,99 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot } } +//+------------------------------------------------------------------+ +//| Standalone OB retest entries (Entry 3 type: 18:00 Bear OB low) | +//| Fires when price retests the boundary of an active OB zone and | +//| closes back on the rejection side — independent of ORB sessions.| +//+------------------------------------------------------------------+ +void CheckOBRetestEntries() +{ + if(!InpUseOBRetestEntry) return; + if(g_TradingHalted) return; + if(g_TodayTrades >= InpMaxTradesPerDay) return; + if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return; + + double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar + double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // previous closed bar + double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); + double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1); + + // --- Bearish OB retest → SELL --- + // Setup: price was below OB bottom (already rejected), wick retraces back up + // to OB bottom, candle closes back below it → confirmed rejection sell + for(int i = 0; i < g_BearOBCount; i++) + { + if(!g_BearOBs[i].active || g_BearOBs[i].traded) continue; + + double obBottom = g_BearOBs[i].bottom; + double obTop = g_BearOBs[i].top; + + bool retest = (c1 < obBottom) // prev bar was below OB bottom (breakdown confirmed) + && (h0 >= obBottom) // this bar's wick touched the OB bottom + && (c0 < obBottom); // closed back below OB bottom (rejection confirmed) + if(!retest) continue; + + double entry = SymbolInfoDouble(g_Symbol, SYMBOL_BID); + double sl = NormalizeDouble(obTop + PipsToPrice(2.0), + (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); + double slPips = PriceToPips(MathAbs(entry - sl)); + double tp = CalcTP(-1, entry, sl); + double lots = CalcLotSize(slPips); + + bool result = g_Trade.Sell(lots, g_Symbol, 0, sl, tp, + StringFormat("NANDR|OBRetest|SELL|%.2f", obBottom)); + if(result) + { + g_TodayTrades++; + g_BearOBs[i].traded = true; + PrintFormat("NANDR EA: OB Retest SELL. Entry=%.2f SL=%.2f TP=%.2f OB=[%.2f-%.2f]", + entry, sl, tp, obBottom, obTop); + if(InpShowTradeLabels) DrawTradeLabel(-1, entry, sl, tp); + } + else + PrintFormat("NANDR EA: OB Retest SELL failed. Error=%d", GetLastError()); + break; // One OB entry per bar + } + + if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return; + + // --- Bullish OB retest → BUY --- + // Setup: price was above OB top, wick dips into OB top zone, closes back above + for(int i = 0; i < g_BullOBCount; i++) + { + if(!g_BullOBs[i].active || g_BullOBs[i].traded) continue; + + double obTop = g_BullOBs[i].top; + double obBottom = g_BullOBs[i].bottom; + + bool retest = (c1 > obTop) // prev bar was above OB top (breakout confirmed) + && (l0 <= obTop) // this bar's wick touched the OB top + && (c0 > obTop); // closed back above OB top (rejection confirmed) + if(!retest) continue; + + double entry = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); + double sl = NormalizeDouble(obBottom - PipsToPrice(2.0), + (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); + double slPips = PriceToPips(MathAbs(entry - sl)); + double tp = CalcTP(1, entry, sl); + double lots = CalcLotSize(slPips); + + bool result = g_Trade.Buy(lots, g_Symbol, 0, sl, tp, + StringFormat("NANDR|OBRetest|BUY|%.2f", obTop)); + if(result) + { + g_TodayTrades++; + g_BullOBs[i].traded = true; + PrintFormat("NANDR EA: OB Retest BUY. Entry=%.2f SL=%.2f TP=%.2f OB=[%.2f-%.2f]", + entry, sl, tp, obBottom, obTop); + if(InpShowTradeLabels) DrawTradeLabel(1, entry, sl, tp); + } + else + PrintFormat("NANDR EA: OB Retest BUY failed. Error=%d", GetLastError()); + break; + } +} + //+------------------------------------------------------------------+ //| Check all sessions for entry signals | //+------------------------------------------------------------------+ @@ -1064,6 +1177,10 @@ void CheckDailyReset() for(int s = 0; s < g_SessionCount; s++) g_Sessions[s].Reset(); + // Reset OB traded flags so the same zones can re-trigger on the new day + for(int i = 0; i < g_BullOBCount; i++) g_BullOBs[i].traded = false; + for(int i = 0; i < g_BearOBCount; i++) g_BearOBs[i].traded = false; + Print("NANDR EA: Daily reset. Equity=", g_DayStartEquity); } } @@ -1517,6 +1634,7 @@ void OnTick() // Entry signals CheckEntrySignals(); + CheckOBRetestEntries(); // Update closed trade stats & dashboard UpdateClosedTrades();