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.2 KiB
OBV (On-Balance Volume)
A cumulative signed-volume series: each candle adds its volume on an up close, subtracts on a down close, and leaves the running total unchanged on a flat close. The shape of the OBV curve, not its absolute level, is what carries information.
Quick reference
| Item | Value |
|---|---|
| Family | Volume |
| Input type | Candle (uses close and volume) |
| Output type | f64 |
| Output range | unbounded (signed, integer-of-volume in spirit) |
| Default parameters | none |
| Warmup period | 1 |
| Interpretation | divergence vs price signals accumulation / distribution |
Formula
For each candle t > 0 (after the seed):
if close_t > close_{t-1}: OBV_t = OBV_{t-1} + volume_t
if close_t < close_{t-1}: OBV_t = OBV_{t-1} - volume_t
if close_t == close_{t-1}: OBV_t = OBV_{t-1}
The first candle initialises the running total to 0.0 and emits
that value (crates/wickra-core/src/indicators/obv.rs:42-55).
Parameters
Obv::new() takes no parameters. Python: wickra.OBV(). Node:
new w.OBV().
Inputs / Outputs
impl Indicator for Obv {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64>;
fn warmup_period(&self) -> usize { 1 }
}
- Python streaming. Accepts a 6-tuple or dict candle; returns
float | None. - Python batch.
OBV.batch(close, volume)takes two equal-length 1-Dnumpy.ndarraycolumns and returns a 1-Dnp.ndarray. The first value is0.0, neverNaN. - Node streaming. Not exposed; the Node binding ships only
batchforOBV. - Node batch.
obv.batch(close, volume)returnsArray<number>of the same length.
Warmup
warmup_period() == 1. The very first candle emits 0.0 by
convention (the "baseline" — there is no prior close to compare
against, so the indicator starts the running total at zero). Every
subsequent candle emits the updated cumulative total.
Edge cases
- First bar. Always emits
0.0(pinned testfirst_candle_baseline_zero). This is the canonical OBV convention used by Granville's original formulation. - Equal closes. A candle with
close_t == close_{t-1}does not change the running total — the volume is discarded. (obv.rs:46-50). - Down close. Subtracts the bar's volume, so OBV can go strongly negative on a sustained downtrend; that is expected and meaningful.
- Zero volume. A zero-volume bar adds or subtracts
0, so OBV is unchanged regardless of close direction. - NaN / infinity.
Candle::newrejects non-finite OHLCV values before they reach OBV. - Reset.
reset()zeroes the running total and clears thehas_emitted/prev_closestate.
Examples
Rust
use wickra::{BatchExt, Candle, Indicator, Obv};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let candles = vec![
Candle::new(10.0, 10.0, 10.0, 10.0, 100.0, 0)?, // baseline -> 0
Candle::new(10.0, 11.0, 10.0, 11.0, 20.0, 0)?, // up -> +20
Candle::new(11.0, 11.0, 10.5, 10.5, 30.0, 0)?, // down -> -30
Candle::new(10.5, 10.5, 10.5, 10.5, 40.0, 0)?, // flat -> 0
Candle::new(10.5, 12.0, 10.5, 12.0, 10.0, 0)?, // up -> +10
];
let mut obv = Obv::new();
println!("{:?}", obv.batch(&candles));
Ok(())
}
Output:
[Some(0.0), Some(20.0), Some(-10.0), Some(-10.0), Some(0.0)]
Hand check: baseline 0, then 0 + 20 = 20, then 20 - 30 = -10,
then -10 (flat close discards the 40), then -10 + 10 = 0.
Python
import numpy as np
import wickra as ta
obv = ta.OBV()
c = np.array([10.0, 11.0, 10.5, 10.5, 12.0])
v = np.array([100.0, 20.0, 30.0, 40.0, 10.0])
print(obv.batch(c, v))
Output:
[ 0. 20. -10. -10. 0.]
Node
const w = require('wickra');
const obv = new w.OBV();
console.log(obv.batch(
[10, 11, 10.5, 10.5, 12],
[100, 20, 30, 40, 10],
));
Output:
[ 0, 20, -10, -10, 0 ]
Interpretation
- Divergence is the signal. OBV's absolute level depends entirely on where the series started and is therefore meaningless on its own. The interpretable signal is the shape of OBV relative to price: a new price high without a new OBV high (bearish divergence) suggests the rally is not being confirmed by accumulating buy volume, and vice versa.
- Trend confirmation. A rising OBV that tracks a rising price is confirmation of the trend; a flattening OBV under a still-rising price is the canonical warning of distribution.
- Smoothing. Many traders apply an SMA or EMA to OBV (e.g. 20-period SMA) and treat crossings of that smoothed line as buy/sell triggers.
Common pitfalls
- Absolute value is arbitrary. Comparing OBV values across different start times or different instruments is meaningless — only slopes, divergences, and crossings of derived smoothers carry signal.
- Flat closes discard volume. A candle that closes exactly at the previous close contributes nothing to OBV no matter how heavy its volume. Some practitioners prefer A/D-style alternatives (e.g. Chaikin Money Flow) that distribute the volume according to where in the bar's range the close landed, precisely to avoid this discontinuity.
References
- Joseph Granville, Granville's New Strategy of Daily Stock Market Timing for Maximum Profit, Prentice-Hall, 1976. The OBV construction was first popularised in Granville's earlier 1963 work and refined in his subsequent books.