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

작성자

카테고리:

← 피드로
DEV Community · Bryan Woods · 2026-07-21 개발(SW)

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);

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

// AFTER — requires breakeven to have activated first
if (state.phase === 1 && state.be_activated) {
  const anchorResult = tryFVGAnchor(state, candles, currentPrice);

Enter fullscreen mode Exit fullscreen mode

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.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다