The original taxonomy was four classical families plus a statistics group, with the F1-F12 expansion slotted in as sub-categories. This regroups the whole 71-indicator catalogue into eight top-level families, each with at least five members: Moving Averages (12), Momentum Oscillators (13), Trend & Directional (9), Price Oscillators (5), Volatility & Bands (12), Trailing Stops (5), Volume (9), Price Statistics (7). - Wiki: docs/wiki/indicators/ reorganised into eight family folders; all 71 indicator pages moved with `git mv`. Every internal cross-link is normalised to `../<family>/Indicator-X.md`, each page's `Family` field is set to its new family, and two pre-existing `../Indicator-Chaining.md` links (should have been `../../`) are corrected. A link check confirms every relative wiki link resolves. - Indicators-Overview.md fully rewritten around the eight families; Home.md indicator reference and the README family table follow suit. - Warmup-Periods.md gains the eight F13 indicators; CHANGELOG records the 46-indicator expansion (25 -> 71) and the eight-family taxonomy. - Tests: Node indicators.test.js and Python test_new_indicators.py cover all eight new indicators (Node 91/91, Python 117/117 green). cargo fmt + clippy (core/wickra/data/wasm/node) clean; 508 core tests, 25 data tests and 74 doctests green.
6.7 KiB
RSI
Relative Strength Index — Wilder's bounded momentum oscillator that maps the ratio of average gains to average losses onto the
[0, 100]range.
Quick reference
| Field | Value |
|---|---|
| Family | Momentum Oscillators |
| Input type | f64 (close) |
| Output type | f64 |
| Output range | [0, 100] |
| Default parameters | period = 14 (Python) |
| Warmup period | period + 1 (15 for period = 14) |
| Interpretation | overbought above 70, oversold below 30 (Wilder's thresholds) |
Formula
diff_t = close_t − close_{t-1}
gain_t = max(diff_t, 0)
loss_t = max(−diff_t, 0)
Seed (Wilder, at t = period):
avg_gain_p = (gain_1 + … + gain_p) / p
avg_loss_p = (loss_1 + … + loss_p) / p
Recursive smoothing (t > period), with α = 1 / period:
avg_gain_t = (avg_gain_{t-1} · (period − 1) + gain_t) / period
avg_loss_t = (avg_loss_{t-1} · (period − 1) + loss_t) / period
RS_t = avg_gain_t / avg_loss_t
RSI_t = 100 − 100 / (1 + RS_t)
When avg_loss_t == 0 and avg_gain_t > 0, RSI is 100 directly; when both
are zero (a perfectly flat series) the implementation returns the standard
50 convention.
Parameters
| Name | Type | Default (Python) | Valid range | Description |
|---|---|---|---|---|
period |
usize |
14 |
>= 1 |
Wilder smoothing length. Rsi::new(0) returns Error::PeriodZero. |
Inputs / Outputs
From impl Indicator for Rsi in crates/wickra-core/src/indicators/rsi.rs:
type Input = f64;
type Output = f64;
fn update(&mut self, input: f64) -> Option<f64>;
The output is a scalar in [0, 100]. In Python batch(prices) returns a
1-D np.ndarray of float64, with NaN in the warmup positions. In Node
batch(prices) returns a flat number[], also NaN during warmup.
Warmup
warmup_period() returns period + 1. The reason is that RSI consumes
diffs, not prices: with period prices you only have period − 1 diffs,
so you need exactly one extra price before Wilder's seed average is well
defined. The Rust test warmup_period_is_period_plus_one pins this:
let rsi = Rsi::new(14).unwrap();
assert_eq!(rsi.warmup_period(), 15);
In streaming terms, the first period calls to update() return None;
the (period + 1)-th call returns the first Some(value).
Edge cases
- Flat input. When every input price is identical, every
gainand everylossis zero, soavg_loss == avg_gain == 0. The implementation returns50.0by convention (seeRsi::rsi_from_avgs). The unit testflat_series_yields_rsi_50pins this behaviour. - Pure uptrend / pure downtrend.
avg_loss == 0withavg_gain > 0short-circuits to100; the mirror case returns0. Testspure_uptrend_yields_rsi_100andpure_downtrend_yields_rsi_0cover this. - Non-finite input.
update()returns the previously emitted value (orNoneif no value has been emitted yet) when the input isNaNor infinite — the internal state is not advanced. - Reset.
reset()returns the indicator to the freshly-constructed state:prev_close, both seed buffers, both averages, andlast_valueare cleared.
Examples
Rust
use wickra::{BatchExt, Indicator, Rsi};
let prices = [
44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42,
45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28, 46.00,
46.03, 46.41, 46.22, 45.64,
];
let mut rsi = Rsi::new(14)?;
let out = rsi.batch(&prices);
println!("first = {}", out[14].unwrap());
println!("last = {}", out[19].unwrap());
# Ok::<(), wickra::Error>(())
Verified output:
first = 70.46413502109705
last = 57.91502067008556
Python
import numpy as np
import wickra as ta
prices = np.array([
44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42,
45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28, 46.00,
46.03, 46.41, 46.22, 45.64,
], dtype=float)
rsi = ta.RSI(14)
v = rsi.batch(prices)
print("warmup:", rsi.warmup_period())
print("first :", float(v[14]))
print("last :", float(v[-1]))
Verified output:
warmup: 15
first : 70.46413502109705
last : 57.91502067008556
Node
const wickra = require('wickra');
const rsi = new wickra.RSI(14);
const prices = [
44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42,
45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28, 46.00,
46.03, 46.41, 46.22, 45.64,
];
const v = rsi.batch(prices);
console.log('warmup:', rsi.warmupPeriod());
console.log('first :', v[14]);
console.log('last :', v[19]);
Verified output:
warmup: 15
first : 70.46413502109705
last : 57.91502067008556
Interpretation
- Overbought / oversold zones. Wilder's classic thresholds are
70(overbought) and30(oversold). Many crypto and FX desks tighten them to80 / 20for trending markets and loosen to60 / 40for range-bound markets. - Midline cross. A move through
50is sometimes used as a directional signal; above 50 means average gains exceed average losses over the smoothing window. - Divergence. A higher price high paired with a lower RSI high (bearish divergence) is a classic Wilder signal; the symmetric pattern at lows is bullish.
Common pitfalls
- RSI on flat input is
50, not undefined. The implementation returns50.0when both averages are zero. Do not interpret this as a neutral signal — it is a placeholder that means "the indicator has no opinion yet". Pair RSI with a volatility filter (e.g. ATR) if your strategy is sensitive to ranging markets. period + 1warmup, notperiod. A common bug is sizing the result array againstperiodand indexing into the warmup region. The firstSomearrives at the (period + 1)-thupdate; in batch form, indices0..periodareNone/NaN. See Warmup Periods.- Non-finite inputs are absorbed silently.
update(f64::NAN)does not advance the state and returns the previous value. If you depend on a 1:1 input-to-output mapping, pre-validate your data before feeding it in.
References
- J. Welles Wilder, New Concepts in Technical Trading Systems, Trend Research, 1978. The original publication that defines both RSI and the Wilder smoothing scheme used internally.
See also
- Indicator: MacdIndicator — also momentum, but trend-following and unbounded.
- Indicator: Stochastic — sibling bounded oscillator, faster and noisier than RSI.
- Warmup Periods — the canonical
period + 1off-by-one explained. - Quickstart: Python — full RSI batch / streaming walk-through.