What it does
On-Balance Volume (OBV) is a running total of volume that adds a bar's volume when price closes higher and subtracts it when price closes lower. The absolute value of OBV has no meaning; what matters is its direction. A rising OBV suggests volume is heavier on up bars, a falling OBV suggests it is heavier on down bars.
This indicator adds a signal line and marks the bars where OBV crosses it. It deliberately uses only crosses: there is no automatic divergence detection, so every signal follows one clear rule that you can check by eye.
How it works
- If the close is higher than the previous close, the bar's volume is added to OBV. If lower, it is subtracted. If unchanged, OBV stays the same.
- Signal line = EMA or SMA of OBV over the signal length.
- Histogram = OBV − signal line.
- A bullish cross is a closed bar where OBV moves from at or below the signal line to above it. A bearish cross is the opposite.
- With "Min bars between signals" above zero, a cross is ignored if the previous accepted cross happened within that many bars.
How to use it
Think of the OBV cross as a volume-flow version of a moving-average cross. A bullish cross says OBV has moved above its own recent average, meaning volume has been leaning toward up closes; a bearish cross says the opposite.
Ways to use it:
- As a confirmation filter: take long setups from your main method only while OBV is above its signal line.
- To check breakouts: a price breakout with OBV crossing up in the same bar or shortly before is often treated as better supported than one where OBV is falling.
- On higher timeframes such as H4 and D1, where each bar holds more volume and the line is less noisy.
The technical analysis basics lesson covers how indicators like this fit into a broader process.
Settings
| Input | Default | What it changes |
|---|---|---|
| Signal type | EMA | EMA reacts faster, SMA is smoother. |
| Signal length | 20 | Longer = fewer crosses. |
| Min bars between signals | 0 | Thins out back-to-back crosses in sideways markets. |
| Show OBV minus signal histogram | on | Shows the spread as columns. |
| Show cross markers | on | Dots on the signal line at crosses. |
Limitations
- OBV assigns a bar's entire volume to one side based only on the close-to-close change. A bar that closes one tick higher counts the same as a strong rally.
- On spot forex, TradingView volume is usually tick volume from one feed, so OBV reflects activity on that feed only.
- OBV starts at zero on the first loaded bar, so its level depends on how much history is loaded. Only its shape and crosses are meaningful.
- Crosses lag price and can whipsaw when OBV moves sideways around the signal line.
- The script stops with an error on symbols with no volume data.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_OBVSignal.pine
56 lines
// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/obv-signal/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
indicator("FXP OBV Signal", shorttitle = "FXP OBV", overlay = false, format = format.volume)
// ── Inputs ─────────────────────────────────────────────────────────────
grpS = "Signal line"
maType = input.string("EMA", "Signal type", options = ["EMA", "SMA"], group = grpS)
sigLen = input.int(20, "Signal length", minval = 1, group = grpS)
minBars = input.int(0, "Min bars between signals", minval = 0, group = grpS, tooltip = "0 = every cross is shown. Raise it to thin out crosses in sideways markets.")
grpVis = "Display"
showHist = input.bool(true, "Show OBV minus signal histogram", group = grpVis)
showSig = input.bool(true, "Show cross markers", group = grpVis)
upCol = input.color(color.teal, "Bullish color", group = grpVis)
dnCol = input.color(color.red, "Bearish color", group = grpVis)
// ── Calculation ────────────────────────────────────────────────────────
// OBV adds the bar's volume when close rises, subtracts it when close falls, ignores unchanged closes.
float chg = ta.change(close)
float signed = chg > 0 ? nz(volume) : chg < 0 ? -nz(volume) : 0.0
float obv = ta.cum(signed)
float obvEma = ta.ema(obv, sigLen)
float obvSma = ta.sma(obv, sigLen)
float signal = maType == "EMA" ? obvEma : obvSma
float spread = obv - signal
bool rawUp = ta.crossover(obv, signal)
bool rawDn = ta.crossunder(obv, signal)
var int lastSigBar = na
bool crossUp = false
bool crossDn = false
if barstate.isconfirmed and (rawUp or rawDn)
bool spaced = na(lastSigBar) or bar_index - lastSigBar > minBars
if spaced
crossUp := rawUp
crossDn := rawDn
lastSigBar := bar_index
float cumVol = ta.cum(nz(volume))
if barstate.islast and cumVol == 0
runtime.error("This symbol provides no volume data, so OBV cannot be calculated.")
// ── Plots ──────────────────────────────────────────────────────────────
plot(showHist ? spread : na, "OBV - signal", style = plot.style_columns, color = spread >= 0 ? color.new(upCol, 75) : color.new(dnCol, 75))
plot(obv, "OBV", color = obv >= signal ? upCol : dnCol, linewidth = 2)
plot(signal, "Signal", color = color.new(color.gray, 10))
plotshape(showSig and crossUp ? signal : na, "Bullish cross", shape.circle, location.absolute, upCol, size = size.tiny)
plotshape(showSig and crossDn ? signal : na, "Bearish cross", shape.circle, location.absolute, dnCol, size = size.tiny)
// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(crossUp, "OBV crossed above signal", "FXP OBV: OBV crossed above its signal line on {{ticker}} {{interval}}")
alertcondition(crossDn, "OBV crossed below signal", "FXP OBV: OBV crossed below its signal line on {{ticker}} {{interval}}")

