RSI Indicator: Formula, Settings and How to Read It

RSI (Relative Strength Index) compares the average size of up closes with the average size of down closes over a lookback window and reports the balance on a 0 to 100 scale. Senzoukria computes it with Wilder's original smoothing on bar closes, with a default period of 14.

Senzoukria · Indicators · Updated September 2026


RSI ships with the Senzoukria desktop app, in the Momentum group of the indicator catalogue. It is drawn in its own panel below the chart.

What RSI measures

The number reports the proportion between average gain and average loss inside the smoothed window, not the distance price travelled. The series is seeded with the simple average of the first fourteen close-to-close changes, so nothing is plotted until those fourteen changes have been consumed; from then on each bar updates both averages with Wilder's recursion, which keeps every earlier change in memory with a decaying weight. Three boundary cases are defined explicitly: a window of gains only returns 100, a window of losses only returns 0, and a completely flat window, where both averages are zero, returns no value at all rather than 100, because a market that has not moved carries no directional information. The calculation runs continuously and is never reset at a session boundary.

The formula, as implemented

This is not a description of how the indicator is usually defined elsewhere — it is what the shipped code computes, documented next to the implementation:

Période entière ≥ `min` depuis les params (garde-fou UI). */ function intParam( params: Record<string, unknown>, key: string, fallback: number, min = 1, ): number { return Math.max(min, Math.floor(numParam(params, key, fallback))); } function toPoints( bars: readonly FootprintBar[], values: ReadonlyArray<number | null>, ): SeriesPoint[] { return bars.map((b, i) => ({ time: b.bucketTsNs, value: values[i] ?? null })); } /** SMA N sur série à trous : null dès qu'UNE valeur de la fenêtre est null — moyenner autour d'un trou mentirait sur la période effective. */ function smaNullable( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); for (let i = n - 1; i < values.length; i++) { let sum = 0; let ok = true; for (let j = i - n + 1; j <= i; j++) { const v = values[j]; if (v === null) { ok = false; break; } sum += v; } if (ok) out[i] = sum / n; } return out; } /** EMA N sur une série dont les valeurs définies forment un SUFFIXE contigu (sortie d'un lissage précédent) : l'EMA tourne sur le suffixe, le préfixe reste null. Série toute-null (ou trou interne — impossible dans nos usages) → tout-null, on ne devine pas. */ function suffixEma( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const out: Array<number | null> = new Array<number | null>(values.length).fill(null); const start = values.findIndex((v) => v !== null); if (start < 0) return out; const defined: number[] = []; for (let i = start; i < values.length; i++) { const v = values[i]; if (v === null) return out; // pas un suffixe contigu → dégradation propre defined.push(v); } const ema = emaSeries(defined, period); for (let j = 0; j < ema.length; j++) out[start + j] = ema[j]; return out; } /** Égalités RSI (documentées) : marché PLAT (les deux moyennes nulles) → null — pas d'information directionnelle (déviation assumée du Pine `down==0 → 100`, qui afficherait 100 sur du plat) ; que des gains → 100 ; que des pertes → 0. */ function rsiValue(avgGain: number, avgLoss: number): number | null { if (avgLoss === 0 && avgGain === 0) return null; if (avgLoss === 0) return 100; if (avgGain === 0) return 0; return 100 - 100 / (1 + avgGain / avgLoss); } /** RSI de Wilder sur une série de valeurs (closes ici, série RSI pour le StochRSI). Graine = moyenne SIMPLE des N premières variations, puis lissage Wilder ; premier point à l'indice N (N variations consommées). */ function wilderRsiSeries( values: readonly number[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); if (values.length < n + 1) return out; let avgGain = 0; let avgLoss = 0; for (let i = 1; i <= n; i++) { const ch = values[i] - values[i - 1]; if (ch > 0) avgGain += ch; else avgLoss -= ch; } avgGain /= n; avgLoss /= n; out[n] = rsiValue(avgGain, avgLoss); for (let i = n + 1; i < values.length; i++) { const ch = values[i] - values[i - 1]; avgGain = (avgGain * (n - 1) + Math.max(0, ch)) / n; avgLoss = (avgLoss * (n - 1) + Math.max(0, -ch)) / n; out[i] = rsiValue(avgGain, avgLoss); } return out; } /** %K brut : 100·(close − LL_N)/(HH_N − LL_N) sur high/low des barres. Fenêtre PLATE (HH == LL) → null : 0/0 = pas d'information (même règle que delta-percent). */ function rawStochK( bars: readonly FootprintBar[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(bars.length).fill(null); for (let i = n - 1; i < bars.length; i++) { let hh = Number.NEGATIVE_INFINITY; let ll = Number.POSITIVE_INFINITY; for (let j = i - n + 1; j <= i; j++) { if (bars[j].high > hh) hh = bars[j].high; if (bars[j].low < ll) ll = bars[j].low; } const range = hh - ll; out[i] = range > 0 ? (100 * (bars[i].close - ll)) / range : null; } return out; } function typicalPrice(b: FootprintBar): number { return (b.high + b.low + b.close) / 3; } /** True range de la barre i — TR[0] = high − low (pas de close précédent), même définition que `atrSeries` (averages.ts). */ function trueRangeAt(bars: readonly FootprintBar[], i: number): number { const b = bars[i]; if (i === 0) return b.high - b.low; const pc = bars[i - 1].close; return Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc)); } // ── Defs ──────────────────────────────────────────────────────────────────── /** RSI (Wilder, 1978) — lissage WILDER : gain = max(0, close − closeₚ) ; loss = max(0, closeₚ − close) graine = moyenne simple des N premiers gains/losses (à l'indice N) ; puis avg = (avg·(N−1) + x)/N ; RSI = 100 − 100/(1 + avgGain/avgLoss) Égalités : cf. `rsiValue` (plat → null, tout-gain → 100, tout-perte → 0). CONTINU — pas de reset session. Défaut N=14 (Wilder).

How to read it

  • A reading above 70 means up closes have dominated the smoothed window. In a sustained trend RSI can stay there for a long stretch, so read it as a description of what has already happened rather than as a reversal cue.
  • A reading below 30 describes the mirror case: down closes have dominated the window.
  • The 50 line separates windows where average gain exceeds average loss from windows where the reverse is true. A crossing changes that balance; it does not by itself establish a trend.
  • A divergence, where price prints a higher high while RSI prints a lower high, says the second leg was built from smaller up closes than the first. Check it against the footprint of the two legs before drawing a conclusion.
  • A break in the line means the window was flat and no value was defined. A gap is not a value of zero.

Parameters and defaults

Period defaults to 14, Wilder's own choice, and accepts 2 to 200. A shorter period reacts to each new close more strongly and touches the 30 and 70 areas far more often; a longer one flattens the curve and makes extremes rare. The colour setting only changes how the line is drawn in its pane.

RSI — parameters exposed in the app, with the values it ships with.
ParameterTypeDefaultRange
Periodnumber142 – 200

What it does not show

RSI reads closes and nothing else. Whether a leg was made by buyers lifting offers or by sellers stepping away is a distinction that lives in the bid by ask columns of a footprint bar, and no reading of this line recovers it. Because the series is continuous across sessions, an overnight gap enters the calculation as one large close-to-close change and then keeps influencing the averages for many bars. Wilder smoothing has a long memory by design, so a single outsize bar stays visible in the value well after it has printed. Flat stretches produce genuine gaps in the line rather than a default reading.

Using it in Senzoukria

Add it from the Indicators panel of any footprint chart or candle chart. It runs on futures data from Rithmic or Databento and on crypto pairs from Binance and Bybit, on the same engine — the calculation does not change with the venue, only the data feeding it does. Market data subscriptions are billed by the provider, separately from the app.

See the full indicator library, or start with the order flow guide if you are new to reading aggression, delta and absorption.

Frequently asked questions

What does an RSI of 70 mean?
An RSI of 70 means that over the lookback window the average up close was roughly 2.33 times the average down close. It describes a period in which buying closes dominated; it is not a measurement of how far price can still travel, and in a trending market the value can remain above 70 for many consecutive bars.
Is this RSI the same as the one on other charting platforms?
The smoothing is the standard Wilder recursion seeded with a simple average of the first N changes, which matches the usual implementations. One case differs on purpose: when both the average gain and the average loss are zero, meaning the market has been perfectly flat, Senzoukria returns no value, while some Pine-derived versions return 100 in that situation.
Does RSI reset at the start of each trading session?
No. The RSI series here is continuous: the smoothed averages carry over from one session to the next. The first change of a new session is therefore the gap between the previous close and the new one, which can be large on futures contracts that trade in separate sessions.

Keep reading