Files
wickra/docs/wiki/indicators/volume/Indicator-ChaikinOscillator.md
T
kingchenc 0b11a523a0 F10: add Chaikin Money Flow, Chaikin Oscillator, Force Index and Ease of Movement
- Rust core: cmf.rs (Chaikin Money Flow — summed money-flow volume over
  summed volume, bounded to [-1, +1]), chaikin_oscillator.rs (Chaikin
  Oscillator — the MACD of the ADL, EMA(ADL, fast) - EMA(ADL, slow)),
  force_index.rs (Elder's Force Index — EMA of price change scaled by
  volume), ease_of_movement.rs (Arms' Ease of Movement — SMA of distance
  travelled per unit of volume). Each with a full Indicator impl,
  runnable doctest and reference / property / warmup / reset /
  batch==streaming tests.
- Python: PyChaikinMoneyFlow / PyChaikinOscillator / PyForceIndex /
  PyEaseOfMovement PyO3 classes + module registration + .pyi stubs.
- Node: explicit ChaikinMoneyFlowNode / ChaikinOscillatorNode /
  ForceIndexNode / EaseOfMovementNode; index.d.ts and index.js updated.
- WASM: WasmChaikinMoneyFlow / WasmChaikinOscillator / WasmForceIndex /
  WasmEaseOfMovement.
- Wiki: Indicator-ChaikinMoneyFlow/ChaikinOscillator/ForceIndex/
  EaseOfMovement.md plus a new "Oscillators" sub-table in
  Indicators-Overview.md and entries in Home.md.

cargo fmt + clippy (core/wickra/data/wasm/node) clean; 402 core tests,
25 data tests and 57 doctests green.
2026-05-22 19:25:32 +02:00

4.7 KiB
Raw Blame History

ChaikinOscillator

Chaikin Oscillator — the MACD of the Accumulation/Distribution Line: a fast EMA of the ADL minus a slow EMA of the ADL.

Quick reference

Field Value
Family Volume
Sub-category Oscillators
Input type Candle (uses high, low, close, volume)
Output type f64
Output range unbounded around zero
Default parameters fast = 3, slow = 10 (Python)
Warmup period slow
Interpretation Momentum of accumulation/distribution; zero-line crossings are the signal.

Formula

ChaikinOsc_t = EMA(ADL, fast)_t  EMA(ADL, slow)_t

The Adl is an unbounded line that drifts with cumulative volume — useful for its slope but awkward to trade directly. The Chaikin Oscillator applies the MACD construction to it: difference a fast and a slow EMA of the ADL to get a zero-centred momentum reading. Positive values mean short-term accumulation is outrunning the longer trend; negative values mean distribution leads.

Parameters

  • fast — period of the fast EMA on the ADL (classic 3).
  • slow — period of the slow EMA on the ADL (classic 10).

fast must be strictly less than slow. ChaikinOscillator::classic() returns the (3, 10) configuration.

Inputs / Outputs

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

impl Indicator for ChaikinOscillator {
    type Input = Candle;
    type Output = f64;
    // update(&mut self, input: Candle) -> Option<f64>
}

It is a candle-input indicator (the ADL inside it needs high, low, close, volume). Python's streaming update accepts a 6-tuple or a dict; the batch helper takes high, low, close, volume numpy arrays. Node and WASM expose update(high, low, close, volume) and the matching batch.

Warmup

ChaikinOscillator::classic().warmup_period() == 10. The ADL emits a value from the very first candle, so both EMAs are fed every bar and the slow EMA gates the first output — the warmup is exactly slow.

Edge cases

  • Flat market. A flat candle has zero money-flow volume, so the ADL never moves and both EMAs of the constant-zero series stay at zero — the oscillator sits at 0.0 (flat_market_yields_zero pins this).
  • fast >= slow. Rejected at construction with an error.
  • Reset. osc.reset() clears the ADL and both EMAs.

Examples

Rust

use wickra::{BatchExt, Candle, Indicator, ChaikinOscillator};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut osc = ChaikinOscillator::classic();   // EMA(ADL, 3)  EMA(ADL, 10)
    // A flat market: the ADL never moves, so the oscillator sits at zero.
    let candles: Vec<Candle> = (0..20)
        .map(|i| Candle::new(10.0, 10.0, 10.0, 10.0, 100.0, i).unwrap())
        .collect();
    let out = osc.batch(&candles);
    println!("{:?}", out.last().unwrap());
    Ok(())
}

Output:

Some(0.0)

A flat series produces a flat ADL and therefore a zero oscillator. This matches the flat_market_yields_zero test in crates/wickra-core/src/indicators/chaikin_oscillator.rs.

Python

import numpy as np
import wickra as ta

osc = ta.ChaikinOscillator(3, 10)
n = 20
flat = np.full(n, 10.0)
print(osc.batch(flat, flat, flat, np.full(n, 100.0))[-1])

Output:

0.0

Node

const ta = require('wickra');
const osc = new ta.ChaikinOscillator(3, 10);
const flat = Array(20).fill(10);
const vol = Array(20).fill(100);
const out = osc.batch(flat, flat, flat, vol);
console.log(out[out.length - 1]);

Output:

0

Interpretation

Trade the Chaikin Oscillator like any MACD-style line: a cross above zero is a bullish accumulation signal, a cross below is bearish. Divergence between the oscillator and price is the higher-conviction setup — for example, price making a new high while the oscillator does not is the same warning the raw ADL gives, but packaged as a bounded, zero-centred series.

Common pitfalls

  • Treating the level as meaningful. Only the sign and the slope carry information; the magnitude scales with the instrument's volume.
  • Feeding it scalar prices. It needs the full OHLCV bar.

References

Marc Chaikin's Chaikin Oscillator — the MACD construction applied to his Accumulation/Distribution Line (StockCharts).

See also