Skip to content

A/D Oscillator (Williams)

Larry Williams' Accumulation / Distribution. A cumulative volume-less price flow that classifies each bar as accumulation or distribution based on its close relative to the previous close, then sums the directional component. Uses a true high / low that includes the prior close as an anchor — the same idea that motivates true range.

Quick reference

ItemValue
FamilyVolume
Input typeCandle (uses high, low, close)
Output typef64 — cumulative
Output rangeunbounded
Default parametersnone — AdOscillator::new()
Warmup period2
InterpretationCumulative accumulation/distribution flow

Formula

TR_h_t = max(close_{t-1}, high_t)
TR_l_t = min(close_{t-1}, low_t)

AD_t = AD_{t-1} + (close_t - TR_l_t)    if close_t > close_{t-1}  (accumulation)
AD_t = AD_{t-1} + (close_t - TR_h_t)    if close_t < close_{t-1}  (distribution)
AD_t = AD_{t-1}                          if close_t == close_{t-1} (no change)

See crates/wickra-core/src/indicators/ad_oscillator.rs.

Parameters

None — AdOscillator::new() takes no arguments.

Inputs / Outputs

Indicator<Input = Candle, Output = f64>. Python: AdOscillator().batch(high, low, close) returns a 1-D np.ndarray. Node: same shape; update(candle) returns number | null.

Warmup

warmup_period() == 2. First bar seeds the prior close; bar 2 produces the first non-zero output.

Edge cases

  • No volume input. Williams' original A/D Oscillator is volume-less. Don't confuse with Adl (Chaikin's volume-weighted version).
  • Cumulative unbounded. Output drifts without bound; useful for divergence detection vs price, not as a level signal.
  • Reset. Clears the accumulator and the prior close.

Examples

Rust

rust
use wickra::{AdOscillator, BatchExt, Candle, Indicator};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let candles: Vec<Candle> = (0..30).map(|i| {
        let b = 100.0 + f64::from(i) * 0.5;
        Candle::new(b - 0.3, b + 1.0, b - 1.0, b + 0.3, 1.0, i as i64).unwrap()
    }).collect();
    let mut ad = AdOscillator::new();
    println!("row 20 = {:?}", ad.batch(&candles)[20]);
    Ok(())
}

Python

python
import numpy as np
import wickra as ta

n = 30
base = 100 + np.arange(n, dtype=float) * 0.5
ad = ta.AdOscillator()
print('row 20:', ad.batch(base + 1, base - 1, base + 0.3)[20])

Node

javascript
const wickra = require('wickra');
const ad = new wickra.AdOscillator();
const n = 30;
const base = Array.from({ length: n }, (_, i) => 100 + i * 0.5);
console.log('row 20:',
  ad.batch(base.map(b => b + 1), base.map(b => b - 1), base.map(b => b + 0.3))[20]);

Streaming

rust
use wickra::{AdOscillator, Candle, Indicator};

let mut ad = AdOscillator::new();
let candle_stream: Vec<wickra::Candle> = Vec::new(); // your live OHLCV candle feed
for bar in candle_stream {
    if let Some(v) = ad.update(bar) {
        // v is cumulative; watch for divergences vs price
    }
}

Interpretation

  • Direction-only signal. A/D Oscillator captures the cumulative direction of close-to-close moves, anchored by true range bounds. Without volume, the slope reflects only price flow.
  • Divergence detection. Like all cumulative flow indicators, the most-useful signal is divergence: price makes a new high, A/D Oscillator doesn't → potential top.
  • Vs Adl. Adl is the Chaikin-style volume-weighted version. A/D Oscillator is volume-less — for instruments where volume data is poor or unavailable, this is the alternative.

Common pitfalls

  • Confused with Adl. Different indicator family; A/D Oscillator is volume-less, Adl is volume-weighted.
  • Treating absolute level as meaningful. Cumulative output depends on series length; only the changes (or divergences) carry signal.

References

  • Larry Williams, How I Made One Million Dollars Last Year Trading Commodities (1973) — first practical exposition.

See also