Skip to content

fix(strategy): make a flat RSI window neutral instead of oversold - #149

Merged
kcrmin merged 1 commit into
developfrom
fix/rsi-flat-window-parity
Aug 8, 2026
Merged

fix(strategy): make a flat RSI window neutral instead of oversold#149
kcrmin merged 1 commit into
developfrom
fix/rsi-flat-window-parity

Conversation

@kcrmin

@kcrmin kcrmin commented Aug 8, 2026

Copy link
Copy Markdown
Member

The divergence

Live and backtest computed RSI with two different implementations that disagreed on a documented edge case.

flat window (no gains, no losses)
data-pipeline (producer) 50"a market that did not move is not a market that only rose"
backtest-engine (consumer) 50"It must be reproduced verbatim"
trading-engine (live) 0 ← the outlier

Backtest reads the published RSI_14 series and never recomputes; live computes in-process from closes. So the same strategy was oversold live and neutral in its own backtest.

A flat window has no relative strength to divide, so the value is a convention rather than a result — and both other implementations chose 50 with the reasoning written down. As an indicator bounded at 0 and 100, returning 0 also means maximum oversold, which is the wrong reading of a market that did not move at all.

Why it mattered in the dangerous direction

With an UP crossing at threshold 30:

  • old: previous = 0 → sat below the threshold, so a single later rise crossed it → candidate emitted
  • new: previous = 50 → never below the threshold → no crossing

Live therefore entered trades the strategy's own backtest never showed. That is precisely the guarantee the official backtest exists to provide, so this was a reproducibility break rather than a rounding nit.

Test

Sixteen closes whose previous window is perfectly flat and whose current window rises once — the minimal shape that separates the two values. It emits a candidate under the old value and none under the pinned one.

Note on scope

Only the flat-window convention is changed. A second, much smaller divergence remains: the pipeline quantizes each running average to eight decimal places at every step, while the live interpreter sums gains and losses and divides once. That can differ in the low digits and only matters for a threshold landing exactly on the boundary. Aligning it means reproducing the pipeline's quantization recursion, which is worth its own change rather than being smuggled in here.

Verification

:modules:strategy-runtime:test --tests "*BasicPlanInterpreterTest*" passes locally (JDK 21).

Context

Found while auditing the strategy domain for business-logic correctness against catalog basic-elements:2026-08-08.

🤖 Generated with Claude Code

The live interpreter returned 0 for a window with neither gains nor losses.
The official RSI_14 definition pins that case to 50, the data pipeline
computes 50, and the backtest reads the published series rather than
recomputing, so the same strategy was oversold live and neutral in its own
backtest.

A flat window has no relative strength to divide, so the value is a
convention, not a result. Both other implementations chose 50 and wrote down
why — a market that did not move is not a market that only rose — and the
definition says it must be reproduced verbatim. As an indicator bounded at 0
and 100, 0 also means maximum oversold, which is the wrong reading of a market
that did not move at all.

The divergence ran in the dangerous direction. With an UP crossing at
threshold 30, a previous window of 0 sat below the threshold, so a single
later rise crossed it and emitted a candidate; at 50 no crossing exists. Live
therefore entered trades the strategy's own backtest never showed, which is
exactly the guarantee the official backtest is supposed to provide.

The test pins that case: sixteen closes whose previous window is flat and
whose current window rises once. It emits a candidate under the old value and
none under the pinned one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kcrmin
kcrmin merged commit b3bc0cf into develop Aug 8, 2026
2 checks passed
@kcrmin
kcrmin deleted the fix/rsi-flat-window-parity branch August 8, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant