Files
wickra/docs/wiki/indicators/momentum-oscillators/Indicator-Roc.md
T
kingchenc d2f99efd78 F13c: restructure the indicator catalogue into eight families
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.
2026-05-22 21:21:56 +02:00

5.1 KiB
Raw Blame History

ROC

Rate of Change — the percent change between the current close and the close period bars ago.

Quick reference

Field Value
Family Momentum Oscillators
Input type f64 (close)
Output type f64
Output range unbounded (centred on 0; expressed as a percent)
Default parameters none — period is required in every binding
Warmup period period + 1 (13 for period = 12)
Interpretation sign and magnitude of momentum; zero-line crossover for direction changes

Formula

ROC_t  =  (close_t  close_{t  period}) / close_{t  period} · 100

When close_{t period} is exactly zero, the implementation returns 0.0 rather than dividing by zero. The unit test known_value pins the basic case: with period = 3, inputs [100, 105, 108, 110] produce ROC = 10 at index 3 (because (110 100) / 100 · 100 = 10).

Parameters

Name Type Default Valid range Description
period usize required >= 1 Lookback distance for the comparison close.

Roc::new(0) returns Error::PeriodZero. The Python and Node bindings do not assign a default for period; you must pass it explicitly.

Inputs / Outputs

From impl Indicator for Roc:

type Input  = f64;
type Output = f64;
fn update(&mut self, input: f64) -> Option<f64>;

Python's ROC.batch(prices) returns a 1-D float64 np.ndarray. Node's ROC.batch(prices) returns a flat number[]. Streaming update(price) returns a scalar (float / number) or None / null during warmup.

Warmup

warmup_period() returns period + 1. The reason is the same off-by-one as RSI: ROC compares against the close period bars ago, so at the period-th input we still have nothing to look back at — the (period + 1)-th input is the first one for which close_{t period} exists. Internally the rolling buffer is sized period + 1.

Edge cases

  • Constant input. Every diff is zero, so ROC == 0 for every emitted value (test constant_series_yields_zero).
  • Reference close of zero. Treated as 0.0 rather than producing NaN/±∞ — see the prev == 0.0 early return in update. This matters for assets quoted with zero as a legitimate value (rare for prices, but possible for, e.g., yield spreads).
  • Non-finite input. update(NaN) or update(±∞) returns None without advancing the rolling buffer.
  • Reset. reset() clears the rolling buffer; the next period + 1 updates return None.

Examples

Rust

use wickra::{BatchExt, Indicator, Roc};

let mut roc = Roc::new(3)?;
let out = roc.batch(&[100.0, 105.0, 108.0, 110.0]);
println!("ROC(3) at idx 3 = {}", out[3].unwrap());
# Ok::<(), wickra::Error>(())

Verified output:

ROC(3) at idx 3 = 10

Python

import wickra as ta

roc = ta.ROC(3)
print('warmup:', roc.warmup_period())
for p in [100.0, 105.0, 108.0, 110.0]:
    print(p, '->', roc.update(p))

Verified output:

warmup: 4
100.0 -> None
105.0 -> None
108.0 -> None
110.0 -> 10.0

Node

const wickra = require('wickra');

const roc = new wickra.ROC(3);
console.log('warmup:', roc.warmupPeriod());
for (const p of [100, 105, 108, 110]) {
  console.log(p, '->', roc.update(p));
}

Verified output:

warmup: 4
100 -> null
105 -> null
108 -> null
110 -> 10

Interpretation

  • Sign. Positive ROC means price is higher than period bars ago; negative means lower. The magnitude is the percent move.
  • Zero-line crossover. A move through zero signals a regime change in the period-bar horizon. Combined with a longer-period ROC, this gives you a poor-man's trend filter.
  • Divergence. A new price high paired with a lower ROC high is the same bearish-divergence pattern as RSI/Stochastic, with the unbounded-oscillator caveat that "lower high" is unambiguous (no saturation against a 100 ceiling).

Common pitfalls

  • ROC is unbounded. A 10× price spike over period bars produces ROC = 900. Don't pipe ROC directly into rule sets designed for bounded oscillators (RSI, %K, %R) without an explicit clamp or a log-return transformation upstream.
  • Off-by-one on the warmup. The first non-None value lands at the (period + 1)-th input, not the period-th. A common bug is sizing an output array as len(prices) - period and getting an off-by-one empty row at the end.

References

  • Robert Colby, The Encyclopedia of Technical Market Indicators, 2nd ed., McGraw-Hill, 2002 — Chapter on Rate of Change / Momentum, covering the canonical percent and ratio formulations.

See also