Data Analytic Investments
Volume & PriceAdvanced

Volume Profile

Shows where the most trading activity has occurred at each price level — revealing true S/R zones.

4H, Daily, Weekly
Crypto, Equities, Futures
Python 3

What is it?

Volume Profile is a charting tool that displays a horizontal histogram of volume traded at each price level over a specified period, rather than over time. The Point of Control (POC) is the price level with the highest traded volume — it acts as a strong magnet for price. The Value Area (VA) contains 70% of all traded volume and defines the 'fair value' range. The Value Area High (VAH) and Value Area Low (VAL) are key support/resistance levels. Low Volume Nodes (LVNs) are price levels with little traded volume — price tends to move through them quickly. High Volume Nodes (HVNs) are areas of heavy trading — price tends to consolidate around them.

When to use it

  • Identifying key support/resistance: the POC and Value Area boundaries (VAH/VAL) are the most significant levels for entries, stops, and targets.
  • Predicting price movement speed: price moves quickly through Low Volume Nodes (thin areas) and slowly through High Volume Nodes (thick areas).
  • Value area re-entry trades: when price breaks out of the Value Area and then returns to it, the re-entry often leads to a move to the opposite VA boundary.
  • Naked POC: a POC from a prior session that price has not revisited is a 'naked POC' — price has a strong tendency to return to it.
  • Composite Volume Profile: building a VP over a longer period (e.g. the entire bull cycle) reveals the macro support/resistance structure.

Common pitfalls

  • Volume Profile requires reliable, high-quality volume data. On crypto, exchange-specific VP differs significantly from aggregated multi-exchange VP.
  • The choice of time period for the VP dramatically changes the result. A daily VP, weekly VP, and cycle VP will show completely different POC and VA levels.
  • Volume Profile is a static tool — it shows where volume has been, not where it will be. It must be combined with real-time order flow for high-probability entries.
  • On TradingView, the free plan has limited VP functionality. The full fixed range and session VP tools require a paid subscription.
  • VP on low-liquidity altcoins is unreliable — thin order books mean a few large trades can create artificial HVNs that do not represent genuine market consensus.

Free code template

Requires Python 3.9+ and the listed pip packages. See inline comments for usage.

Python 3
# Volume Profile — DAI Python Template
# Requires: pandas, numpy, matplotlib
# Data source: any OHLCV feed (Binance, Coinbase, etc.)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

def compute_volume_profile(df: pd.DataFrame, n_bins: int = 50) -> pd.DataFrame:
    """
    Compute Volume Profile from OHLCV DataFrame.
    df must have columns: open, high, low, close, volume
    Returns DataFrame with columns: price_level, volume, is_poc, in_value_area
    """
    price_min = df['low'].min()
    price_max = df['high'].max()
    bins = np.linspace(price_min, price_max, n_bins + 1)
    bin_centers = (bins[:-1] + bins[1:]) / 2
    vol_at_price = np.zeros(n_bins)

    for _, row in df.iterrows():
        # Distribute candle volume evenly across price range
        candle_bins = np.where((bin_centers >= row['low']) & (bin_centers <= row['high']))[0]
        if len(candle_bins) > 0:
            vol_at_price[candle_bins] += row['volume'] / len(candle_bins)

    # Point of Control
    poc_idx = np.argmax(vol_at_price)
    poc_price = bin_centers[poc_idx]

    # Value Area (70% of total volume)
    total_vol = vol_at_price.sum()
    target_vol = total_vol * 0.70
    sorted_idx = np.argsort(vol_at_price)[::-1]
    cumvol = 0
    va_indices = set()
    for idx in sorted_idx:
        cumvol += vol_at_price[idx]
        va_indices.add(idx)
        if cumvol >= target_vol:
            break

    result = pd.DataFrame({
        'price_level': bin_centers,
        'volume': vol_at_price,
        'is_poc': [i == poc_idx for i in range(n_bins)],
        'in_value_area': [i in va_indices for i in range(n_bins)]
    })
    print(f'POC: {poc_price:.4f} | VAH: {bin_centers[max(va_indices)]:.4f} | VAL: {bin_centers[min(va_indices)]:.4f}')
    return result

def plot_volume_profile(df: pd.DataFrame, vp: pd.DataFrame):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 8), gridspec_kw={'width_ratios': [3, 1]})
    ax1.plot(df.index, df['close'], color='#3b82f6', linewidth=1.5)
    ax1.set_title('Price', color='white'); ax1.set_facecolor('#0d1f3c')
    fig.patch.set_facecolor('#0d1f3c')

    colors = ['#fbbf24' if r['is_poc'] else ('#22c55e' if r['in_value_area'] else '#334155')
              for _, r in vp.iterrows()]
    ax2.barh(vp['price_level'], vp['volume'], height=(vp['price_level'].iloc[1]-vp['price_level'].iloc[0])*0.9,
             color=colors, alpha=0.85)
    ax2.set_title('Volume Profile', color='white'); ax2.set_facecolor('#0d1f3c')
    ax2.tick_params(colors='white'); ax1.tick_params(colors='white')
    plt.tight_layout()
    plt.savefig('volume_profile.png', dpi=150, bbox_inches='tight')
    plt.show()

# Example usage:
# df = pd.read_csv('btc_ohlcv.csv', parse_dates=['timestamp'], index_col='timestamp')
# vp = compute_volume_profile(df, n_bins=60)
# plot_volume_profile(df, vp)