2026-03-28 · 7 min read engineering

Why your RSI divergence alert fires at the worst possible time.

RSI divergence is one of those signals that looks gorgeous in hindsight. Every backtest you see on Twitter features a perfect lower-low on price with a higher-low on RSI, followed by a textbook bounce. In live trading, it fires at exactly the wrong moment — the instant the price has already reversed. Here is why that happens, and what we changed in our loop to stop it.

The forming-candle problem

Most retail platforms recompute indicators on every tick. It feels responsive, but it means your RSI value for the current 15-minute window is being derived from an incomplete candle. That value will shift — sometimes substantially — as the remaining 11 minutes of order flow arrive. A divergence that appears at minute 4 often disappears by minute 15.

Which means the alert you received at minute 4 was based on a world-state that will no longer exist when your order fills.

A concrete example

15 March 2026, BTC-USDT, 15m timeframe. Price made a lower low at $81,430. The platform's live RSI showed 27.8 against a prior low of 29.1 — a textbook bullish divergence. Alert fires at minute 6 of the candle.

By the time the candle closed, an aggressive seller had pushed the low down another 0.4%, RSI closed at 26.4 — no longer a divergence, just a lower low confirming the downtrend. Users who acted on the minute-6 signal were long into a continuation leg.

The signal was never wrong. The timing was wrong. The indicator was reading from a snapshot that had not happened yet.

Why this is systemic, not a bug

It is systemic because every "live" RSI on every retail charting platform works this way by default. The reason is UX: users want to see the line move. They want to feel the market. Charts that only update on bar close feel dead. So platforms compute against the forming candle and hope the user has the discipline to wait.

Users do not have that discipline. They see the divergence form, they see their alert fire, they enter the trade. The platform did not lie to them — it just showed them a state that had a 40–60% probability of not surviving to bar close.

The fix

We rewrote the indicator loop around a single, boring rule:

Evaluate signals on closed candles only.

In our code this lives in one utility:

# backend/app/engine/utils.py

def strip_forming_candle(candles: list[dict], timeframe: str) -> list[dict]:
    """Drop the in-progress candle. Evaluation is done on closed bars only."""
    ...

Every adapter — RSI, MSS, OBI — calls this before handing the bar list to compute_mood or compute_events. There is no configuration knob. There is no "fast mode." The forming candle is always stripped.

Why it took three months

Because the hard part was not the strip function. The hard part was rebuilding every consumer of bar data — chart, screener, signals, RSI 2.0, MSS adapter — to share one source of truth. We call it the autonomous-adapter pattern:

Before: 40+ code paths recomputing RSI, each with their own forming-candle bug. After: one code path, one pub/sub event, one set of keys.

What you should do about it

If you are on another platform:

If you are on TradeFloor, you do not need to think about this. Every signal that fires in your inbox — RSI level, RSI divergence, MSS shift, OBI threshold — has already survived its own bar close. The signal that appears to you is the signal that survived.

// questions or corrections · [email protected] · more essays · /blog