OBV — On-Balance Volume
Granville’s running total of volume signed by the close’s direction. Unbounded and path-dependent: the level is meaningless — only the slope and divergences against price carry information.
Signature
OBV(period, on=<source>)| Argument | Type | Required | Notes |
|---|---|---|---|
period |
int Param or literal | Yes | SMA smoothing of the line. 1 returns the raw accumulator. Bounds 1–200. |
on= |
symbol | No | Source symbol. Defaults to the trade symbol. Reads close and volume. |
Formula
$$ \text{OBV}t = \text{OBV}{t-1} + \begin{cases} +V_t & C_t > C_{t-1} \ -V_t & C_t < C_{t-1} \ 0 & C_t = C_{t-1} \end{cases} $$
Warmup
Starts at 0 on the first bar. As an accumulator it resets per run — and per walk-forward window, which is real behaviour worth knowing before reading it inside one.
Reading the value
obv.valueobv.value > obv.value_at(20) # slope over 20 barsExample
class ObvConfirm(Strategy): """Breakout confirmed by rising on-balance volume.""" trade = Symbol() sizing = FixedCapital()
def execute(self): obv = OBV(1, on=self.trade) dh = DonchianHighTrue(20, on=self.trade) if self.trade.close >= dh.value_at(1) and obv.value > obv.value_at(10) and self.trade.is_flat: self.buy(self.trade, reason="Breakout with volume") if obv.value < obv.value_at(10) and self.trade.is_long: self.sell(self.trade, self.trade.position, reason="Volume rolled over")Notes
- Joe Granville, Granville’s New Key to Stock Market Profits (1963).
- The running total starts at zero where TA-Lib seeds OBV with the first bar’s volume, so ours is TA-Lib’s minus that one constant on every bar — every increment is identical, which leaves a comparison against OBV’s own past unaffected and one against an absolute level not.