← Back to Home
Technical Indicators On Balance Volume

Technical Indicators On Balance Volume

Developed by Joseph “Joe” Granville, On Balance Volume (OBV) is a compelling and straightforward cumulative indicator designed to measure buying and selling pressure by relating price changes to volume. It works on the principle that volume precedes price movement, meaning that significant changes in OBV can foreshadow major price shifts.

The core logic behind OBV calculation is as follows:

The OBV line thus rises when volume on up days (days where the price closes higher) outpaces volume on down days (days where the price closes lower). Conversely, the OBV line falls when volume on down days is heavier. The starting point for OBV is arbitrary (often the first day’s volume or zero); it’s the direction of the OBV line that matters, not its absolute numerical value.

Usage: OBV is primarily used to confirm price trends and spot potential reversals through divergences. The underlying assumption is that OBV reflects the activity of “smart money” – institutional investors and other large market participants.

TA-Lib Function: The Technical Analysis Library (TA-Lib) offers a direct function for OBV calculation:

talib.OBV(close_prices, volume)

Code Example (Calculation & Plot with yfinance Data):

The Python code below demonstrates fetching financial data using yfinance, calculating the On Balance Volume, and then plotting the asset’s price alongside its OBV in separate panels.

import yfinance as yf
import talib
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# --- 1. Data Fetching using yfinance ---
ticker_symbol = "MSFT"  # Example: Microsoft Corp.
data = yf.download(ticker_symbol, start="2023-01-01", end="2024-05-01", auto_adjust=False, progress=False)

if data.empty or 'Volume' not in data.columns or data['Volume'].isnull().all():
    print(f"No data or insufficient volume data found for {ticker_symbol}. Exiting.")
else:
    # Complying with user preference for droplevel
    if isinstance(data.columns, pd.MultiIndex):
        data.columns = data.columns.droplevel(level=1)

    close_prices = data['Close'].dropna()
    # Ensure volume corresponds to the close_prices index after dropna
    volume_data = data['Volume'].reindex_like(close_prices).dropna()
    # Ensure close_prices and volume_data have the same index after potential drops
    common_index = close_prices.index.intersection(volume_data.index)
    close_prices = close_prices.loc[common_index]
    volume_data = volume_data.loc[common_index]
    date_index = common_index


    # --- 2. OBV Calculation ---
    # OBV requires Close prices and Volume.
    # TA-Lib expects volume to be float or double. yfinance volume is typically int.
    if len(close_prices) > 1 and len(volume_data) > 1: # Needs at least two data points
        # Ensure volume is float for TA-Lib compatibility
        obv_values = talib.OBV(close_prices, volume_data.astype(float))
        indicator_name = "OBV"
        print(f"\n--- {indicator_name} - On Balance Volume for {ticker_symbol} ---")

        # TA-Lib's OBV might not start with NaN if first operation is valid.
        # For printing, we just use the raw output.
        if len(obv_values) >= 5:
            print(f"Output {indicator_name} (last 5): {obv_values[-5:].round(0)}") # OBV values can be large integers
        elif len(obv_values) > 0:
            print(f"Output {indicator_name} (all): {obv_values.round(0)}")
        else:
            print(f"Output {indicator_name}: No OBV values calculated.")

        # --- 3. Plotting ---
        fig, axes = plt.subplots(2, 1, figsize=(14, 10), sharex=True,
                                 gridspec_kw={'height_ratios': [3, 1]}) # Price chart 3x taller

        # Plot Price
        axes[0].plot(date_index, close_prices, label='Close Price', color='blue')
        axes[0].set_title(f'{ticker_symbol} Price and {indicator_name}')
        axes[0].set_ylabel('Price')
        axes[0].legend(loc='upper left')
        axes[0].grid(True)

        # Plot OBV
        # For better visualization, OBV is often plotted without forcing y-axis to start at 0
        # if values are consistently large positive or negative. Matplotlib handles this by default.
        axes[1].plot(date_index, obv_values, label=indicator_name, color='green')
        axes[1].set_ylabel('On Balance Volume (OBV)')
        axes[1].set_xlabel('Date')
        axes[1].ticklabel_format(style='sci', axis='y', scilimits=(0,0)) # Use scientific notation for large OBV values
        axes[1].legend(loc='upper left')
        axes[1].grid(True)

        plt.tight_layout() # Adjust layout to prevent overlap
        plt.show()

    else:
        print(f"\nSkipping OBV plot for {ticker_symbol}: Insufficient data or volume data missing after alignment.")
        if not data.empty:
             print(f"Data available for price from {data['Close'].dropna().index.min().date()} to {data['Close'].dropna().index.max().date()}.")
             print(f"Data available for volume from {data['Volume'].dropna().index.min().date()} to {data['Volume'].dropna().index.max().date()}.")
Pasted image 20250602205837.png

Explanation of the Code:

  1. Import Libraries: Imports yfinance, talib, numpy, matplotlib.pyplot, and pandas.
  2. Data Fetching:
    • yf.download() fetches historical data (including ‘Close’ and ‘Volume’) for the chosen ticker_symbol. User preferences auto_adjust=False and droplevel are applied.
    • A check is performed for missing or entirely null volume data.
    • close_prices and volume_data are extracted. Crucially, volume_data is reindexed to match close_prices after dropna() to ensure they align perfectly. Any dates not common to both are dropped using common_index.
  3. OBV Calculation:
    • A check if len(close_prices) > 1 and len(volume_data) > 1: ensures there’s enough data.
    • volume_data.astype(float) explicitly converts the volume series to float, as TA-Lib expects this type for its calculations.
    • talib.OBV(close_prices, volume_data.astype(float)) computes the OBV.
    • The last few OBV values are printed. Since OBV can result in large numbers, they are rounded to 0 decimal places for printing.
  4. Plotting:
    • plt.subplots(2, 1, ...) creates a figure with two vertically stacked subplots.
    • Price Plot (axes[0]): The top subplot shows the closing prices.
    • OBV Plot (axes[1]): The bottom subplot displays the OBV.
      • axes[1].ticklabel_format(style='sci', axis='y', scilimits=(0,0)) is used to format the y-axis of the OBV plot with scientific notation if the numbers are very large, improving readability.
    • Standard plotting elements like titles, labels, and legends are included.
    • plt.tight_layout() ensures the plot elements are well-arranged.
    • plt.show() displays the chart.
  5. Insufficient Data Handling: If data is insufficient, a message is printed.

By tracking the cumulative volume flow, OBV provides a unique perspective on market dynamics, helping traders confirm trends and identify potential reversals that might not be immediately obvious from price action alone.