cd /news/developer-tools/three-bugs-one-pattern-how-my-tradin… · home topics developer-tools article
[ARTICLE · art-66131] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Three Bugs, One Pattern: How My Trading Bot Put Stop-Losses Below Entries on Short Trades

A developer's trading engine, the AI Rook Trading Engine, contained three bugs that caused stop-losses to be placed below entry prices on short trades, leading to immediate stop-outs. The bugs, all in the same code path, stemmed from short-trade logic being written as if for long trades, including incorrect FVG detection, an inverted validation guard, and premature phase transitions. The developer fixed the issues by correcting the FVG filter, flipping the comparison operator, and adding a guard requiring breakeven activation before anchoring.

read4 min views2 publishedJul 20, 2026

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 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.

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.

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.

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.

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.

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.This fix was part of commit 6207318 in the Rook Engine — an open-source algorithmic trading system.

── more in #developer-tools 4 stories · sorted by recency
── more on @bdubs 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/three-bugs-one-patte…] indexed:0 read:4min 2026-07-20 ·