By BDubs · AI Rook Trading Engine
My trading engine has a feature called "FVG anchoring." When a trade moves in your favor, the engine looks for a Fair Value Gap (a structural support/resistance zone from price action theory) and anchors the stop-loss just beyond it. This widens the stop from a tight breakeven level to a structurally meaningful one — giving the trade room to breathe while still protecting capital.
It worked great for long trades. For short trades, it did the exact opposite: it placed the stop-loss below the entry price. A stop below your entry on a short means the trade can lose money before the stop even triggers. Three separate bugs, all in the same code path, all caused by the same mistake: the short-trade logic was written as if it were a long trade.
The Context
The Rook Engine manages trade exits in phases. Phase 1 is the initial breakeven guard. Phase 2 tries to anchor the stop to a reverse FVG. Phase 3 switches to trailing. The FVG anchoring code lives across two files: fvg-detector.js (finds the FVG) and exit-manager.js (uses it to set the stop).
The bugs only manifested on short trades because the engine had been primarily backtested and paper-traded on longs. The short path was never properly validated until a live S10 short entry at $75,359 got its stop anchored to $75,354 — five points below entry. The trade was stopped out immediately.
Bug 1: Wrong FVG Type for Shorts
The findReverseFVG() function finds the nearest structural zone to anchor the stop. For longs, you want a bearish FVG above price (resistance). For shorts, you want… also a bearish FVG above price (resistance). The code was finding a bullish FVG below price instead:
// BEFORE — finds bullish FVG below price (wrong for shorts!)
const candidates = active
.filter(f => f.type === 'bullish' && f.top < currentPrice)
.sort((a, b) => b.top - a.top);
A bullish FVG below price is a support zone. Placing a stop just above support when you're short is like placing a stop below entry — it gives the trade no protection at all.
// AFTER — finds bearish FVG above price (resistance above entry)
const candidates = active
.filter(f => f.type === 'bearish' && f.bottom > currentPrice)
.sort((a, b) => a.bottom - b.bottom);
For shorts, the stop goes just above the bearish FVG's top. If price returns to that supply zone, the short thesis is invalidated. That's correct.
Bug 2: Flipped Validation Guard
Even if Bug 1 was somehow acceptable, the validation guard in exit-manager.js was inverted. It was supposed to reject anchors that weren't above entry for shorts:
// BEFORE — rejects valid anchors, accepts invalid ones
if (anchoredSL > state.entry_price) return null;
This reads: "if the anchored stop is above entry, reject it." For a short trade, the stop MUST be above entry. This guard was doing the exact opposite — rejecting every valid anchor and accepting every invalid one.
// AFTER — rejects anchors at or below entry (correct)
if (anchoredSL <= state.entry_price) return null;
One character change: > → <=. The kind of bug that makes you question every comparison operator you've ever written.
Bug 3: FVG Anchor Fires Before Breakeven Activates
The phase transition from Phase 1 to Phase 2 could trigger on the very first candle close, even if the trade had never moved in the engine's favor. This means the FVG anchor could collapse the stop before the trade had any breathing room:
// BEFORE — no guard, fires on first candle close regardless
if (state.phase === 1) {
const anchorResult = tryFVGAnchor(state, candles, currentPrice);
// AFTER — requires breakeven to have activated first
if (state.phase === 1 && state.be_activated) {
const anchorResult = tryFVGAnchor(state, candles, currentPrice);
The fix: check state.be_activated before attempting the FVG anchor. The trade must have moved in our favor and hit breakeven before we start restructuring the stop. Same guard applies to the Phase 3 skip path.
The Pattern
All three bugs share a root cause: the short-trade code path was either copied from the long-trade logic without proper inversion, or never written at all and assumed to "just work." In trading systems, direction matters. Long and short are not symmetric — they're mirror images, and every comparison, every filter, every guard needs to reflect that.
The commit (6207318) touches two files, 22 insertions, 10 deletions. Three bugs, one pattern, one lesson.
Lessons
- Test both directions. If your trading code handles longs and shorts, test the short path as rigorously as the long path. They're not symmetric.
-
Read your comparison operators aloud.
if (anchoredSL > entry) return null— say it out loud. Does "reject if above entry" make sense for a short trade? If you have to think about it twice, the operator is probably wrong. - Phase gates need preconditions. Don't let exit management phases advance until the trade has proven itself. Breakeven activation should be a hard gate before any stop restructuring.
This fix was part of commit 6207318 in the Rook Engine — an open-source algorithmic trading system.
Top comments (0)