Back To Top
Experience the Future of Intelligent Investing Today
We derive an analysis to show where price conviction concentrates. We segment price into bullish/bearish buckets and aggregate volume directionally.
The aim is to see where market participants, both bears and bulls, commit capital and show dominance in key price zones.
This approach helps in spotting support & resistance. We borrow the visualization from liquidity dynamics, i.e. where transactions occur.
The complete Python notebook for the analysis is provided below.
Traditional volume bars show how much was traded, but not where or in which direction.
We structure a price-volume map that splits volume into two directional layers:
Each trading day contributes to one of these groups. We then aggregate volume by price bucket, rather than by time.
The time-series becomes a price-distribution model to show the capital concetrated across the price ladder.
To create consistent volume profiles, we round each closing price to a fixed tick size δ:
Pt​ is the closing price on day t, and δ is the tick size (e.g. $0.01). Each bucket holds aggregated volume from days that closed near that price.
Once bucketed, we compute:
To quantify dominance, we normalize each side:
This cumulative fraction lets us interpret how deep into the price range buyers or sellers are committing.
A steeper curve means volume is front-loaded around a narrow zone, i.e. it shows conviction.
We start by importing libraries and defining user parameters.
These include the ticker symbol, date range, time interval, and number of bars to display.
The tick size controls the resolution of price bucketing.
We then use yfinance to fetch adjusted historical OHLCV data and retain the most recent VISIBLE_BARS.
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from matplotlib.dates import AutoDateLocator, AutoDateFormatter
# ── USER PARAMS
TICKER = "PLTR"
START = "2024-01-01"
END = "2025-07-13"
INTERVAL = "1d"
VISIBLE_BARS = 180 # decrease for fewer datapoints or increase for broader history
TICK_SIZE = 0.01 # smaller values give finer resolution, larger smooth the chart
# color definitions
BULL_EDGE_COLOR = "#089981"
BULL_FILL_COLOR = (8/255,153/255,129/255,0.90)
BEAR_EDGE_COLOR = "#F23645"
BEAR_FILL_COLOR = (242/255,54/255,69/255,0.90)
# ── FETCH DATA
df = yf.download(
TICKER,
start=START,
end=END,
interval=INTERVAL,
auto_adjust=True,
progress=False
)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
vis = df.tail(VISIBLE_BARS).copy()
We label each row as either bullish or bearish based on the candle body. A day is bullish if it closes above the open, bearish if it closes below.
We then round each closing price to the nearest tick size to create discrete price buckets.
These buckets define the y-axis for our chart. For each side, bullish and bearish, we aggregate total volume by price bucket.
vis['bucket'] = (vis['Close']/TICK_SIZE).round().astype(int) * TICK_SIZE
bull = vis[vis['Close'] > vis['Open']]
bear = vis[vis['Close'] < vis['Open']]
bull_map = bull.groupby('bucket')['Volume'].sum()
bear_map = bear.groupby('bucket')['Volume'].sum()
We normalize raw volume to prepare it for visual scaling. This avoids distortion from outlier bars.
Next, we calculate the total volume on each side and identify the price with the highest directional volume.
We sort price levels in descending order, then compute cumulative volume shares. These show how volume stacks up across the price ladder.
From the cumulative distribution, we extract the median price where volume share crosses 50 percent.
We also compute VWAP as a volume-weighted average across buckets. We draw these reference lines to mark where conviction is most concentrated.
Lastly, we define the price axis range using the min and max of all directional levels and raw price data.
Newsletter