fix: UpdateRetestState() — bar-close only, state management (sweep flip + invalidation), no trade firing, CheckOBRetestEntries() Merged into CheckRetestEntriesTick(), CheckRetestEntriesTick() called on every tick, before if(!g_IsNewBar) return, The old single condition is replaced by a 3-pattern check covering all valid entry configurations
This commit is contained in:
+152
-86
@@ -5,7 +5,7 @@
|
||||
//| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "NANDR"
|
||||
#property version "1.32"
|
||||
#property version "1.33"
|
||||
#property strict
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
@@ -608,15 +608,16 @@ void DetectBreakouts()
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Detect retest for sessions that had a breakout |
|
||||
//| Update retest state on bar close: sweep reversals + invalidation |
|
||||
//| Called once per new bar. Does NOT fire any trades. |
|
||||
//+------------------------------------------------------------------+
|
||||
bool DetectRetest(int sessIdx, int &dir)
|
||||
void UpdateRetestState(int sessIdx)
|
||||
{
|
||||
if(!g_Sessions[sessIdx].inBreakout) return false;
|
||||
if(!g_Sessions[sessIdx].inBreakout) return;
|
||||
|
||||
// Use bar[1] (just-closed bar) as the retest candidate and bar[2] as context.
|
||||
double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar
|
||||
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // bar before it
|
||||
// Use bar[1] (just-closed) and bar[2] for bar-close confirmation.
|
||||
double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1);
|
||||
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2);
|
||||
double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1);
|
||||
double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1);
|
||||
|
||||
@@ -624,31 +625,18 @@ bool DetectRetest(int sessIdx, int &dir)
|
||||
double orbLow = g_Sessions[sessIdx].orbLow;
|
||||
double orbMid = (orbHigh + orbLow) / 2.0;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// BULLISH BREAKOUT (breakoutDir == 1)
|
||||
// ----------------------------------------------------------------
|
||||
if(g_Sessions[sessIdx].breakoutDir == 1)
|
||||
{
|
||||
// Standard: prev bar above ORB high, current bar wick dips to ORB high, closes back above
|
||||
bool retestHigh = (c1 > orbHigh) && (l0 <= orbHigh) && (c0 >= orbHigh);
|
||||
// Mid retest: price dipped to ORB mid, closes back above mid
|
||||
bool retestMid = (c1 > orbMid) && (l0 <= orbMid) && (c0 > orbMid);
|
||||
if(retestHigh || retestMid) { dir = 1; return true; }
|
||||
|
||||
// Sweep reversal: wick swept BELOW ORB high (back into range), closed between orbMid and orbHigh.
|
||||
// Sell-side liquidity grab above orbHigh failed → flip to bearish.
|
||||
// Sweep reversal: bullish breakout failed — bar closed below orbHigh → flip to bearish
|
||||
bool sweepRev = (c1 > orbHigh) && (l0 < orbHigh) && (c0 < orbHigh) && (c0 >= orbMid);
|
||||
if(sweepRev)
|
||||
{
|
||||
g_Sessions[sessIdx].breakoutDir = -1;
|
||||
PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH",
|
||||
g_Sessions[sessIdx].name, orbHigh);
|
||||
dir = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Full invalidation: closed below mid
|
||||
if(c0 < orbMid && c1 > orbMid)
|
||||
else if(c0 < orbMid && c1 > orbMid)
|
||||
{
|
||||
PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)",
|
||||
g_Sessions[sessIdx].name, orbMid);
|
||||
@@ -656,33 +644,18 @@ bool DetectRetest(int sessIdx, int &dir)
|
||||
g_Sessions[sessIdx].breakoutDir = 0;
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------
|
||||
// BEARISH BREAKOUT (breakoutDir == -1)
|
||||
// ----------------------------------------------------------------
|
||||
else if(g_Sessions[sessIdx].breakoutDir == -1)
|
||||
{
|
||||
// Standard: prev bar below ORB low, current bar wick ticks back to ORB low, closes back below
|
||||
bool retestLow = (c1 < orbLow) && (h0 >= orbLow) && (c0 <= orbLow);
|
||||
// Mid retest: price bounced back up to ORB mid, closes back below mid
|
||||
bool retestMid = (c1 < orbMid) && (h0 >= orbMid) && (c0 < orbMid);
|
||||
if(retestLow || retestMid) { dir = -1; return true; }
|
||||
|
||||
// Sweep reversal: wick swept ABOVE ORB low (back into range), closed between orbLow and orbMid.
|
||||
// Buy-side liquidity sweep below orbLow failed → flip to bullish.
|
||||
// This is the "liquidity sweep + reversal" pattern (e.g. 18:15 retest after 17:45 breakout).
|
||||
// After this flip, subsequent mid retests are detected as BUY signals (Entry 2 scenario).
|
||||
// Sweep reversal: bearish breakout failed — bar closed above orbLow → flip to bullish
|
||||
bool sweepRev = (c1 < orbLow) && (h0 > orbLow) && (c0 > orbLow) && (c0 < orbHigh);
|
||||
if(sweepRev)
|
||||
{
|
||||
g_Sessions[sessIdx].breakoutDir = 1;
|
||||
PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH",
|
||||
g_Sessions[sessIdx].name, orbLow);
|
||||
dir = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Full invalidation: closed above mid
|
||||
if(c0 > orbMid && c1 < orbMid)
|
||||
else if(c0 > orbMid && c1 < orbMid)
|
||||
{
|
||||
PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)",
|
||||
g_Sessions[sessIdx].name, orbMid);
|
||||
@@ -690,8 +663,6 @@ bool DetectRetest(int sessIdx, int &dir)
|
||||
g_Sessions[sessIdx].breakoutDir = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -980,39 +951,138 @@ 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.|
|
||||
//| Tick-based retest entries — fires at the wick, not on bar close |
|
||||
//| Covers both ORB session retests and OB boundary retests. |
|
||||
//| Uses live bar[0] high/low + last closed bar[1] for confirmation. |
|
||||
//| Direction: determined by breakoutDir for ORB, OB type for OBs. |
|
||||
//| Sweep reversals: detected via ask/bid relative to the level. |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckOBRetestEntries()
|
||||
void CheckRetestEntriesTick()
|
||||
{
|
||||
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);
|
||||
double h_cur = iHigh(g_Symbol, PERIOD_CURRENT, 0); // current bar live high so far
|
||||
double l_cur = iLow(g_Symbol, PERIOD_CURRENT, 0); // current bar live low so far
|
||||
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 1); // last closed bar close
|
||||
double open0 = iOpen(g_Symbol, PERIOD_CURRENT, 0); // current bar open
|
||||
double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(g_Symbol, SYMBOL_BID);
|
||||
double tol = PipsToPrice(1.0); // 1-pip tolerance for direction detection
|
||||
|
||||
// --- 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
|
||||
// ── ORB Session Retest Entries ──────────────────────────────────
|
||||
// Fires at the moment the wick touches the ORB level, not one bar later.
|
||||
// Direction uses ask/bid to distinguish continuation vs sweep reversal:
|
||||
// bid <= level + tol → price is AT or below the level → continuation in breakout dir
|
||||
// ask >= level - tol → price has passed back through the level → sweep reversal, flip dir
|
||||
if(InpWaitForRetest)
|
||||
{
|
||||
for(int s = 0; s < g_SessionCount; s++)
|
||||
{
|
||||
if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue;
|
||||
if(!g_Sessions[s].inBreakout) continue;
|
||||
if(g_Sessions[s].tradesThisSession >= InpMaxRetestsPerSession) continue;
|
||||
if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) continue;
|
||||
if(InpBreakoutExpireBars > 0 && g_Sessions[s].breakoutBarsAgo >= InpBreakoutExpireBars) continue;
|
||||
|
||||
double orbHigh = g_Sessions[s].orbHigh;
|
||||
double orbLow = g_Sessions[s].orbLow;
|
||||
double orbMid = (orbHigh + orbLow) / 2.0;
|
||||
int dir = 0;
|
||||
double orbLevel = 0;
|
||||
|
||||
if(g_Sessions[s].breakoutDir == 1) // Bullish
|
||||
{
|
||||
// Standard: prev close above orbHigh, wick dipped to orbHigh
|
||||
if(c1 > orbHigh && l_cur <= orbHigh)
|
||||
{
|
||||
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;
|
||||
PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL",
|
||||
g_Sessions[s].name, orbHigh);
|
||||
}
|
||||
}
|
||||
// Mid retest: prev close above mid, wick dipped to mid
|
||||
else if(c1 > orbMid && l_cur <= orbMid)
|
||||
{
|
||||
orbLevel = orbMid;
|
||||
if(ask >= orbMid - tol)
|
||||
dir = 1;
|
||||
else
|
||||
{
|
||||
dir = -1;
|
||||
g_Sessions[s].breakoutDir = -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)
|
||||
{
|
||||
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;
|
||||
PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY",
|
||||
g_Sessions[s].name, orbLow);
|
||||
}
|
||||
}
|
||||
// Mid retest: prev close below mid, wick came back up to mid
|
||||
else if(c1 < orbMid && h_cur >= orbMid)
|
||||
{
|
||||
orbLevel = orbMid;
|
||||
if(bid <= orbMid + tol)
|
||||
dir = -1;
|
||||
else
|
||||
{
|
||||
dir = 1;
|
||||
g_Sessions[s].breakoutDir = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dir == 0) continue;
|
||||
|
||||
double obTop = 0, obBottom = 0;
|
||||
IsOBNearLevel(orbLevel, dir, obTop, obBottom);
|
||||
if(InpOBRequireConf && obTop == 0) continue;
|
||||
OpenTrade(dir, s, orbLevel, obTop, obBottom);
|
||||
}
|
||||
}
|
||||
|
||||
if(!InpUseOBRetestEntry) return;
|
||||
|
||||
// ── Bearish OB Retest → SELL at wick touch ───────────────────────
|
||||
// Fires the moment the current bar's live high reaches the OB bottom.
|
||||
// prev bar[1] (or current open) must have been at/below OB bottom.
|
||||
for(int i = 0; i < g_BearOBCount; i++)
|
||||
{
|
||||
if(!g_BearOBs[i].active || g_BearOBs[i].traded) continue;
|
||||
if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) break;
|
||||
if(g_TodayTrades >= InpMaxTradesPerDay) break;
|
||||
|
||||
double obBottom = g_BearOBs[i].bottom;
|
||||
double obTop = g_BearOBs[i].top;
|
||||
double obTop2 = 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;
|
||||
bool prevBelow = (c1 <= obBottom || open0 <= obBottom);
|
||||
bool wickTouched = (h_cur >= obBottom);
|
||||
if(!prevBelow || !wickTouched) continue;
|
||||
|
||||
double entry = SymbolInfoDouble(g_Symbol, SYMBOL_BID);
|
||||
double sl = NormalizeDouble(obTop + PipsToPrice(2.0),
|
||||
double entry = bid;
|
||||
double sl = NormalizeDouble(obTop2 + PipsToPrice(2.0),
|
||||
(int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS));
|
||||
double slPips = PriceToPips(MathAbs(entry - sl));
|
||||
double tp = CalcTP(-1, entry, sl);
|
||||
@@ -1025,45 +1095,43 @@ void CheckOBRetestEntries()
|
||||
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);
|
||||
entry, sl, tp, obBottom, obTop2);
|
||||
if(InpShowTradeLabels) DrawTradeLabel(-1, entry, sl, tp);
|
||||
}
|
||||
else
|
||||
PrintFormat("NANDR EA: OB Retest SELL failed. Error=%d", GetLastError());
|
||||
break; // One OB entry per bar
|
||||
break;
|
||||
}
|
||||
|
||||
if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return;
|
||||
|
||||
// --- Bullish OB retest → BUY ---
|
||||
// Setup: price was above OB top, wick dips into OB top zone, closes back above
|
||||
// ── Bullish OB Retest → BUY at wick touch ────────────────────────
|
||||
for(int i = 0; i < g_BullOBCount; i++)
|
||||
{
|
||||
if(!g_BullOBs[i].active || g_BullOBs[i].traded) continue;
|
||||
if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) break;
|
||||
if(g_TodayTrades >= InpMaxTradesPerDay) break;
|
||||
|
||||
double obTop = g_BullOBs[i].top;
|
||||
double obBottom = g_BullOBs[i].bottom;
|
||||
double obTop3 = g_BullOBs[i].top;
|
||||
double obBottom2 = 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;
|
||||
bool prevAbove = (c1 >= obTop3 || open0 >= obTop3);
|
||||
bool wickTouched = (l_cur <= obTop3);
|
||||
if(!prevAbove || !wickTouched) continue;
|
||||
|
||||
double entry = SymbolInfoDouble(g_Symbol, SYMBOL_ASK);
|
||||
double sl = NormalizeDouble(obBottom - PipsToPrice(2.0),
|
||||
double entry = ask;
|
||||
double sl = NormalizeDouble(obBottom2 - 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));
|
||||
StringFormat("NANDR|OBRetest|BUY|%.2f", obTop3));
|
||||
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);
|
||||
entry, sl, tp, obBottom2, obTop3);
|
||||
if(InpShowTradeLabels) DrawTradeLabel(1, entry, sl, tp);
|
||||
}
|
||||
else
|
||||
@@ -1128,13 +1196,9 @@ void CheckEntrySignals()
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retest-based entry: wait for price to pull back to ORB level
|
||||
int retestDir = 0;
|
||||
if(!DetectRetest(s, retestDir)) continue;
|
||||
double obTop = 0, obBottom = 0;
|
||||
IsOBNearLevel(orbLevel, retestDir, obTop, obBottom);
|
||||
if(InpOBRequireConf && obTop == 0) continue;
|
||||
OpenTrade(retestDir, s, orbLevel, obTop, obBottom);
|
||||
// Retest entries fire tick-based in CheckRetestEntriesTick().
|
||||
// Here we only update state: sweep reversals and failed retest cancellation.
|
||||
UpdateRetestState(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1668,6 +1732,9 @@ void OnTick()
|
||||
ManageOpenTrades();
|
||||
CheckRiskLimits();
|
||||
|
||||
// Retest entries fire on tick (at wick touch), not on bar close
|
||||
CheckRetestEntriesTick();
|
||||
|
||||
if(!g_IsNewBar) return;
|
||||
|
||||
// Daily reset
|
||||
@@ -1689,9 +1756,8 @@ void OnTick()
|
||||
// Breakout detection
|
||||
DetectBreakouts();
|
||||
|
||||
// Entry signals
|
||||
// Entry signals (direct breakout / limit orders on new bar)
|
||||
CheckEntrySignals();
|
||||
CheckOBRetestEntries();
|
||||
|
||||
// Update closed trade stats & dashboard
|
||||
UpdateClosedTrades();
|
||||
|
||||
Reference in New Issue
Block a user