Индикатор On Balance Volume: формула, настройки и чтение
On Balance Volume, опубликованный Джозефом Гранвиллом в 1963 году, прибавляет весь объём бара к текущей сумме, когда закрытие растёт, и вычитает его, когда закрытие падает. Неизменное закрытие оставляет сумму ровно на месте.
Senzoukria · Индикаторы · Updated September 2026
On Balance Volume — Объём, в отдельной панели под графиком.
Что измеряет On Balance Volume
Направление определяется строгим сравнением соседних закрытий: выше — прибавляем, ниже — вычитаем, равно — ничего не делаем, поэтому бары-доджи никогда не считаются бычьими. Объём каждого бара берётся со знаком целиком — индикатор не различает закрытие на тик выше и на тридцать тиков выше. Senzoukria задаёт начальное значение серии 0 на первом загруженном баре: линия воспроизводима, но её абсолютный уровень лишён смысла. Накопление непрерывно и не сбрасывается на открытии в 17:00 CT — этот якорь оставлен для сессионных мер вроде накопленной дельты.
Формула — так, как она реализована
Это не привычное определение из других источников, а то, что вычисляет поставляемый код, задокументированное рядом с реализацией:
Période entière ≥ `min` depuis les params (garde-fou UI). */ function intParam( params: Record<string, unknown>, key: string, fallback: number, min = 1, ): number { return Math.max(min, Math.floor(numParam(params, key, fallback))); } function toPoints( bars: readonly FootprintBar[], values: ReadonlyArray<number | null>, ): SeriesPoint[] { return bars.map((b, i) => ({ time: b.bucketTsNs, value: values[i] ?? null })); } /** Points d'histogramme : `f` rend `null` quand la barre n'a pas d'information — la barre n'émet alors PAS de point (contrat `HistogramPoint` : pas de trou, pas de valeur inventée). */ function histPoints( bars: readonly FootprintBar[], f: (bar: FootprintBar, i: number) => number | null, ): HistogramPoint[] { const out: HistogramPoint[] = []; for (let i = 0; i < bars.length; i++) { const v = f(bars[i], i); if (v !== null) out.push({ time: bars[i].bucketTsNs, value: v }); } return out; } function histOut( points: HistogramPoint[], positiveColor: string, negativeColor: string, ): SeriesOutput { return { shape: "histogram", points, positiveColor, negativeColor }; } /** SMA N sur série à trous : null dès qu'UNE valeur de la fenêtre est null — moyenner autour d'un trou mentirait sur la période effective (même règle que `smaNullable` de momentum.ts). */ function smaNullable( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); for (let i = n - 1; i < values.length; i++) { let sum = 0; let ok = true; for (let j = i - n + 1; j <= i; j++) { const v = values[j]; if (v === null) { ok = false; break; } sum += v; } if (ok) out[i] = sum / n; } return out; } /** EMA N par segment de valeurs définies consécutives. Un trou reste null et réinitialise uniquement la graine suivante : les valeurs antérieures sont conservées. Chaque reprise attend N valeurs pour sa SMA initiale, puis suit la même récurrence que `emaSeries`, sans zéro synthétique. */ function segmentedEma( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const alpha = 2 / (n + 1); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); let count = 0; let sum = 0; let ema = 0; for (let i = 0; i < values.length; i++) { const v = values[i]; if (v === null) { count = 0; sum = 0; continue; } if (count < n) { sum += v; count += 1; if (count < n) continue; ema = sum / n; } else { ema += alpha * (v - ema); } out[i] = ema; } return out; } /** Money Flow Volume de Chaikin pour UNE barre : MFM = ((close − low) − (high − close)) / (high − low) ∈ [−1, +1] MFV = MFM × volume MFM est la position du close dans le range, recentrée : +1 = close au plus haut (toute la barre a été achetée), −1 = close au plus bas. Range NUL (high = low) → `null` : le multiplicateur est un 0/0, la barre ne porte aucune information directionnelle. Les appelants décident quoi en faire — cumul → contribution 0 (cf. en-tête), non cumulé → pas de point. / function moneyFlowVolume(bar: FootprintBar): number | null { const range = bar.high - bar.low; if (!(range > 0)) return null; const mfm = (bar.close - bar.low - (bar.high - bar.close)) / range; return mfm * bar.totalVolume; } /** Ligne A/D (Chaikin) : cumul du Money Flow Volume depuis la première barre chargée. Barre à range nul → contribution 0 (exception documentée en en-tête : un accumulateur ne peut pas porter de trou). CONTINU — pas de reset session (l'A/D est une ligne d'accumulation de long terme). */ function accumDistValues(bars: readonly FootprintBar[]): number[] { const out: number[] = new Array<number>(bars.length); let running = 0; for (let i = 0; i < bars.length; i++) { running += moneyFlowVolume(bars[i]) ?? 0; out[i] = running; } return out; } /** VWMA N sur une fenêtre glissante : Σ(close·volume) / Σ(volume). Σvolume = 0 sur la fenêtre (barres placeholder sans trade) → null : 0/0 n'est pas « 0 ». Warm-up (< N barres) → null. Même formule que `vwma` (averages2.ts) — redéclarée ici plutôt que d'élargir la surface publique d'un autre fichier de famille (sa primitive n'est pas exportée). */ function vwmaSeries( bars: readonly FootprintBar[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(bars.length).fill(null); let sumPV = 0; let sumV = 0; for (let i = 0; i < bars.length; i++) { sumPV += bars[i].close * bars[i].totalVolume; sumV += bars[i].totalVolume; if (i >= n) { sumPV -= bars[i - n].close * bars[i - n].totalVolume; sumV -= bars[i - n].totalVolume; } if (i >= n - 1) out[i] = sumV > 0 ? sumPV / sumV : null; } return out; } /** Indices de volume de Fosback/Dysart (NVI / PVI) — même moteur, deux déclencheurs : index[0] = base index[i] = index[i−1] × (1 + (close[i] − close[i−1]) / close[i−1]) si `trigger(volume[i], volume[i−1])`, sinon index[i−1] NVI : trigger = volume EN BAISSE (Fosback — l'« argent malin » se positionne les jours calmes). PVI : trigger = volume EN HAUSSE (la foule). Volume ÉGAL → aucun des deux ne bouge (inégalités STRICTES, convention d'origine : une journée sans changement de participation n'est ni l'un ni l'autre). close[i−1] = 0 → contribution 0, l'indice est GELÉ (exception documentée en en-tête : le ratio n'existe pas, et un null perdrait l'ancrage de toute la suite de l'indice — un indice base 1000 sans ancrage ne vaut rien). / function volumeIndexValues( bars: readonly FootprintBar[], base: number, trigger: (vol: number, prevVol: number) => boolean, ): number[] { const out: number[] = new Array<number>(bars.length); let idx = base; for (let i = 0; i < bars.length; i++) { if (i > 0) { const prev = bars[i - 1]; const prevClose = prev.close; if (trigger(bars[i].totalVolume, prev.totalVolume) && prevClose !== 0) { idx *= 1 + (bars[i].close - prevClose) / prevClose; } } out[i] = idx; } return out; } // ── Defs ──────────────────────────────────────────────────────────────────── /** OBV — On Balance Volume (Granville, 1963) : OBV[0] = 0 (graine, cf. convention) OBV[i] = OBV[i−1] + volume[i] si close[i] > close[i−1] = OBV[i−1] − volume[i] si close[i] < close[i−1] = OBV[i−1] si close[i] = close[i−1] POURQUOI : la thèse de Granville est que le volume précède le prix. L'OBV signe le volume ENTIER de la barre par la direction du close — sa PENTE et ses divergences avec le prix sont l'information, jamais son niveau absolu. Convention GRAINE : OBV[0] = 0. L'indicateur n'a pas d'origine absolue (Granville lui-même part d'un nombre arbitraire) ; ancrer à 0 sur la première barre CHARGÉE rend la série reproductible et lisible. Corollaire assumé : charger plus d'historique décale toute la courbe — c'est vrai de tout OBV, ATAS et TradingView compris. Convention ÉGALITÉ : close inchangé → OBV inchangé (règle de Granville, inégalités STRICTES — pas de « ≥ » qui compterait les dojis comme haussiers). CONTINU : pas de reset de session — l'ancrage 17:00 CT ne concerne que les cumuls orderflow type CVD, l'OBV est une accumulation de long terme.
Как его читать
- Гранвилл утверждал, что объём опережает цену; как бы вы к этому ни относились, полезное содержание — это наклон линии и её расхождения с ценой, но никогда не уровень.
- Серия растущих минимумов OBV при плоской цене означает, что на этом отрезке закрытия вниз приходили на меньшем объёме, чем закрытия вверх.
- Когда цена обновляет максимум, а OBV — нет, закрытия вверх за этим максимумом несли меньше объёма, чем предыдущие: это факт об этих барах, а не сигнал.
- Поскольку загрузка большей истории сдвигает всю кривую, не стройте ничего на пороговом значении OBV — стройте на форме.
- На внутридневных фьючерсах сравнивайте OBV с накопленной дельтой: OBV берёт объём со знаком по направлению закрытия, дельта — по стороне агрессора, и расходятся они ровно там, где было поглощение.
Параметры и значения по умолчанию
Сглаживание по умолчанию 1 — сырая накопительная линия остаётся нетронутой; значения до 200 применяют дополнительное сглаживание, которое облегчает чтение наклона на длинном горизонте, но прячет отдельные бары, его создавшие. Цвет по умолчанию синий (#2962ff).
| Параметр | Тип | По умолчанию | Диапазон |
|---|---|---|---|
| Smoothing | number | 1 | 1 – 200 |
Чего он не показывает
OBV работает по принципу «всё или ничего»: продвижение на один тик и большое движение вносят одинаковый вклад, из-за чего линия дёргается на инструментах, часто закрывающихся около прежней цены. Он использует только закрытие, поэтому всё, что происходило внутри бара, остаётся невидимым, включая поглощение и сторону агрессора. Нулевое начальное значение делает абсолютные величины бессмысленными и несравнимыми между графиками и глубинами истории — это свойство любой реализации OBV, включая ATAS и TradingView. Равные закрытия замораживают линию, поэтому сильно округлённые данные или данные с низким разрешением цены уплощают её по чисто механическим причинам.
Похожие индикаторы
- Volume — Объём
- Buy/Sell Volume — Объём
- Average Trade Size — Объём
- Volume ROC % — Объём
- Relative Volume — Объём
- Trade Count — Объём
Эта страница на других языках
Frequently asked questions
- Как рассчитывается On Balance Volume?
- От начального значения к сумме прибавляется весь объём бара, если его закрытие выше предыдущего, и вычитается, если ниже. При равных закрытиях сумма не меняется. В Senzoukria начальное значение равно 0 на первом баре, загруженном в график.
- Сбрасывается ли On Balance Volume на открытии сессии?
- Нет. OBV идёт непрерывно через сессии, поскольку Гранвилл задумывал его как накопление на длинном горизонте. Сессионный якорь 17:00 CT в приложении относится к сессионным накоплениям — накопленной дельте или объёму сессии, — но не к OBV.
- Чем OBV отличается от накопленной дельты?
- OBV берёт объём бара со знаком по направлению его закрытия, поэтому зависит только от данных OHLC. Накопленная дельта берёт объём со знаком по стороне агрессора каждой сделки и требует классификации по bid/ask из потока данных. Они расходятся там, где агрессивный объём не сдвинул закрытие, — это подпись поглощения.