Skip to content

Anchored RSI ​

A cumulative Relative Strength Index whose averaging begins at a runtime-chosen anchor bar instead of over a fixed Wilder period.

Quick reference ​

FieldValue
FamilyMomentum Oscillators
Input typef64 (single close)
Output typef64
Output range0.0 ..= 100.0
Default parametersnone — re-anchor at runtime with set_anchor()
Warmup period2 (needs one price change)
InterpretationRSI of the entire move since the anchor, not a rolling window.

Formula ​

For every bar since the current anchor:

gain_i = max(close_i - close_{i-1}, 0)
loss_i = max(close_{i-1} - close_i, 0)
RSI_t  = 100 - 100 / (1 + Σ gain_i / Σ loss_i)

The relative strength is the ratio of cumulative gains to cumulative losses since the anchor; the per-bar count cancels, so this equals avg_gain / avg_loss over the anchored window. Where Rsi applies Wilder's period-length smoothing (α = 1/period), Anchored RSI gives equal weight to every move since the anchor — so it answers "what is the RSI of the whole leg that started at my anchor?". See crates/wickra-core/src/indicators/anchored_rsi.rs.

Saturation follows the standard convention: a window with no losses yet (and at least one gain) reads 100, no gains yet reads 0, and a perfectly flat window reads the neutral 50 — identical to the convention used by Rsi.

Parameters ​

None. The averaging window is opened at construction (the first close is the anchor) and re-opened at runtime:

MethodDescription
set_anchor()Marks a re-anchor; the next update clears the running sums and previous close before folding in its own bar, starting a fresh anchored window. Mirrors AnchoredVwap's "click to anchor" workflow.

Inputs / Outputs ​

From crates/wickra-core/src/indicators/anchored_rsi.rs:

rust
use wickra::{AnchoredRsi, Indicator};
// AnchoredRsi: Input = f64, Output = f64
const _: fn(&mut AnchoredRsi, f64) -> Option<f64> = <AnchoredRsi as Indicator>::update;

Python streams as float | None, batches a close column to a 1-D array.array('d') (NaN for warmup). Node streams as number | null, batches as Array<number> with NaN placeholders.

Warmup ​

AnchoredRsi::new().warmup_period() == 2. The first close only seeds the previous-close reference and returns None; the first value follows on the second close (one price change is enough). After a set_anchor(), the first bar of the new window likewise re-seeds and returns None. The unit test first_bar_seeds_and_returns_none pins this contract.

Edge cases ​

  • Pure uptrend → 100. Monotonically rising closes have zero cumulative loss, so RSI saturates at 100. The unit test pure_uptrend_saturates_at_100 pins this.
  • Pure downtrend → 0. Monotonically falling closes give zero cumulative gain → 0. Pinned by pure_downtrend_saturates_at_0.
  • Flat window → 50. No movement at all reads the neutral 50. Pinned by flat_window_reads_50.
  • Re-anchor. set_anchor() discards the old window; a downtrend that read 0 followed by a re-anchored uptrend reads a fresh 100. Pinned by set_anchor_clears_old_window.
  • Non-finite inputs. NaN / ±inf are ignored: they do not advance the window and the last value is returned unchanged. Pinned by ignores_non_finite_input and non_finite_before_any_bar_returns_none.
  • Reset. reset() clears the window and previous close; the next close re-seeds. Pinned by reset_clears_state.

Examples ​

Rust ​

rust
use wickra::{AnchoredRsi, BatchExt, Indicator};

fn main() {
    // prices 10 -> 11 (+1) -> 9 (-2) -> 12 (+3)
    let mut rsi = AnchoredRsi::new();
    let out: Vec<Option<f64>> = rsi.batch(&[10.0, 11.0, 9.0, 12.0]);
    println!("{:?}", out);
}

Output:

[None, Some(100.0), Some(33.333333333333336), Some(66.66666666666667)]

After bar 2 the cumulative gain/loss is 1 / 2 → 100 − 100/1.5 = 33.33; after bar 3 it is 4 / 2 → 100 − 100/3 = 66.67. This matches the cumulative_reference_values unit test.

Python ​

python
import wickra as ta

rsi = ta.AnchoredRSI()
for x in [10.0, 11.0, 9.0, 12.0]:
    print(x, '->', rsi.update(x))
print('value:', rsi.value)
rsi.set_anchor()  # next update opens a fresh anchored window

Output:

10.0 -> None
11.0 -> 100.0
9.0 -> 33.333333333333336
12.0 -> 66.66666666666667
value: 66.66666666666667

Node ​

javascript
const ta = require('wickra');
const rsi = new ta.AnchoredRSI();
for (const x of [10, 11, 9, 12]) {
  console.log(x, '->', rsi.update(x));
}
rsi.setAnchor();

Output:

10 -> null
11 -> 100
9 -> 33.333333333333336
12 -> 66.66666666666667

Streaming with a re-anchor ​

python
import wickra as ta

rsi = ta.AnchoredRSI()
for x in [20.0, 19.0, 18.0, 17.0]:  # downtrend -> 0
    rsi.update(x)
assert rsi.value == 0.0
rsi.set_anchor()
assert rsi.update(50.0) is None     # re-seeds
assert rsi.update(51.0) == 100.0    # fresh up-leg

Interpretation ​

Anchor the index at a structurally meaningful bar — a swing low, an earnings gap, a session open — and Anchored RSI reports the momentum of the whole leg since that point, with no period to tune. Because it is cumulative, it is steadier than a rolling Rsi deep into a trend (one more bar barely moves a long-running average), which makes divergence against the anchored leg easy to read. Use the rolling Rsi when you want a fixed-lookback oscillator; use Anchored RSI when the relevant question is "how strong is this move from this point".

Common pitfalls ​

  • Expecting Wilder values. Anchored RSI is not Rsi with a reset — it uses simple cumulative averages, not Wilder's α = 1/period smoothing, and it has no period. Numbers will not match an Rsi(14) line.
  • Forgetting the re-seed after set_anchor(). Because RSI needs a price change, the first bar after an anchor returns None; the first value of the new leg appears on the second bar, not the first (unlike AnchoredVwap, which emits immediately).

References ​

The Relative Strength Index is due to J. Welles Wilder Jr., New Concepts in Technical Trading Systems (1978). The "anchored" variant applies the same runtime-anchor idea popularised for VWAP by Brian Shannon to the RSI ratio.

See also ​