Indicateur On Balance Volume : formule, réglages et lecture
On Balance Volume, publié par Joseph Granville en 1963, ajoute tout le volume d'une barre à un total courant quand la clôture monte et le retranche quand elle baisse. Une clôture inchangée laisse le total exactement là où il est.
Senzoukria · Indicateurs · Updated September 2026
On Balance Volume — Volume, dans son propre panneau sous le graphique.
Ce que mesure On Balance Volume
Le sens est décidé par comparaison stricte des clôtures consécutives : supérieure ajoute, inférieure retranche, égale ne fait rien — les dojis ne sont donc jamais comptés comme haussiers. Le volume de chaque barre est signé en totalité : l'indicateur ne fait aucune différence entre une clôture un tick plus haut et trente ticks plus haut. Senzoukria amorce la série à 0 sur la première barre chargée, ce qui rend la ligne tracée reproductible tout en privant son niveau absolu de sens. Le cumul est continu et n'est pas remis à zéro à l'ouverture de 17:00 CT, une ancre réservée aux mesures de séance comme le delta cumulé.
La formule, telle qu’implémentée
Ce n'est pas la définition habituelle ailleurs : c'est ce que calcule le code livré, documenté à côté de son implémentation :
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.
Comment le lire
- La thèse de Granville était que le volume précède le prix ; quoi qu'on en pense, le contenu exploitable est la pente de la ligne et ses désaccords avec le prix, jamais son niveau.
- Une série de creux OBV de plus en plus hauts sous un prix plat signifie que les clôtures baissières sont arrivées sur moins de volume que les haussières sur cette portion.
- Quand le prix fait un nouveau plus haut et pas l'OBV, les clôtures haussières derrière ce plus haut portaient moins de volume que les précédentes — un fait sur ces barres, pas un signal.
- Puisque charger plus d'historique déplace toute la courbe, ne bâtissez rien sur un seuil d'OBV ; bâtissez sur la forme.
- Sur les futures intraday, comparez l'OBV au delta cumulé : l'OBV signe le volume par le sens de la clôture, le delta par le côté agresseur, et ils divergent exactement là où il y a eu absorption.
Paramètres et valeurs par défaut
Le lissage est à 1 par défaut, ce qui laisse la ligne cumulée brute intacte ; jusqu'à 200, il applique un lissage a posteriori qui rend la pente de long terme plus lisible tout en masquant les barres individuelles qui l'ont produite. Le bleu (#2962ff) est la couleur par défaut.
| Paramètre | Type | Par défaut | Bornes |
|---|---|---|---|
| Smoothing | number | 1 | 1 – 200 |
Ce qu’il ne montre pas
L'OBV fonctionne en tout ou rien : une avance d'un tick et une avance large contribuent à l'identique, ce qui rend la ligne nerveuse sur les instruments qui clôturent souvent près de l'inchangé. Il n'utilise que la clôture, donc tout ce qui s'est passé à l'intérieur de la barre reste invisible, y compris l'absorption et le côté agresseur. L'amorce à zéro prive les valeurs absolues de sens et les rend incomparables d'un graphique ou d'une profondeur d'historique à l'autre — une propriété commune à toutes les implémentations d'OBV, y compris celles d'ATAS et de TradingView. Les clôtures égales figent la ligne : des prix très arrondis ou de faible résolution l'aplatissent pour des raisons purement mécaniques.
Indicateurs liés
- Volume — Volume
- Buy/Sell Volume — Volume
- Average Trade Size — Volume
- Volume ROC % — Volume
- Relative Volume — Volume
- Trade Count — Volume
Cette page dans d’autres langues
Frequently asked questions
- Comment se calcule l'On Balance Volume ?
- À partir d'une valeur d'amorce, le volume entier de chaque barre est ajouté quand sa clôture est au-dessus de la précédente et retranché quand elle est en dessous. Quand les deux clôtures sont égales, le total ne bouge pas. Dans Senzoukria l'amorce vaut 0 sur la première barre chargée dans le graphique.
- L'On Balance Volume se remet-il à zéro à l'ouverture de séance ?
- Non. L'OBV court en continu d'une séance à l'autre, Granville l'ayant conçu comme une accumulation de long terme. L'ancre de séance de 17:00 CT dans l'application s'applique aux cumuls de séance comme le delta cumulé ou le volume de séance, pas à l'OBV.
- Quelle différence entre l'OBV et le delta cumulé ?
- L'OBV signe le volume d'une barre par le sens de sa clôture : il ne dépend donc que de l'OHLC. Le delta cumulé signe le volume par le côté agresseur de chaque transaction et exige un classement bid / ask fourni par le flux. Ils divergent là où le volume agressif n'a pas fait bouger la clôture, ce qui est la signature de l'absorption.