Files
wickra/docs/wiki/indicators/volume/Indicator-Obv.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

190 lines
6.2 KiB
Markdown

# 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
```rust
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-D `numpy.ndarray` columns and returns a 1-D `np.ndarray`. The
first value is `0.0`, never `NaN`.
- **Node streaming.** Not exposed; the Node binding ships only
`batch` for `OBV`.
- **Node batch.** `obv.batch(close, volume)` returns `Array<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 test
`first_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::new` rejects non-finite OHLCV values
before they reach OBV.
- **Reset.** `reset()` zeroes the running total and clears the
`has_emitted` / `prev_close` state.
## Examples
### Rust
```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
```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
```js
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.
## See also
- [VWAP](../volume/Indicator-Vwap.md) — volume-weighted price benchmark; OBV and
VWAP are the two canonical volume-aware indicators in the panel.
- [MFI](../momentum-oscillators/Indicator-Mfi.md) — money-flow index, an oscillator blending
typical price with volume.
- [SMA](../moving-averages/Indicator-Sma.md) / [EMA](../moving-averages/Indicator-Ema.md) — the smoothers
most commonly layered on top of OBV to define trade triggers.