On this page
Volume Orderbook - Indie Port Guide
Volume accumulated per price level, drawn as an order book of rows around the current price.
| Language | Indie Script v5 |
| Platform | TakeProfit |
| Category | Volume |
| Type | Indicator, port from Pine Script |
| Original | Volume Orderbook (Zeiierman) (Pine Script v6) |
| License | CC BY-NC-SA 4.0 (see the header of the source files) |
| Original source | Volume Orderbook.pinescript6 |
| Source file | Volume Orderbook.indie5 |
Overview
Builds a histogram of traded volume per price level over the whole chart history. The level height is set by the first candle and the Width input. On the last bar the rows around the current price are drawn as an order book: red rows below, lime above, grey at the current level, with the width of each row showing its volume rank.
On the platform the drawing budget is limited, so the port labels only the three biggest rows with their volume.
How it works
- Level step = size of the first candle times
Width. - Each bar adds its volume to the level that contains the source price; if the price leaves the known range, new levels are added.
- On the last bar find the level of the current price and take
Rowslevels on each side. - Rank the volumes of these levels; the row width is proportional to the rank and its colour alpha follows a gradient.
Parameters
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
src |
source | source.CLOSE | Source | |
rows |
int | 10 | ≥ 0, ≤ 50 | Rows |
mult |
float | 0.5 | ≥ 0.1, ≤ 10.0 | Width |
left |
int | 5 | ≥ 0, ≤ 50 | Left |
Port notes
Differences and decisions in the Indie port (taken from the header of Volume Orderbook.indie5):
- Logic ported 1:1: price levels with step = first candle size * width, volume accumulated per level over the whole
- history, the order book of
rowslevels around the current price drawn on the last bar, box width by volume rank - Engine limit: 100 drawing changes per bar (a rollback of the previous recalculation counts too), so volume labels
- are drawn only for the MAX_LABELS biggest rows (the width of every box still shows its volume rank)
- Not ported: POC extension, table frame and grid (all off by default in the original)
- Colours: red/lime/gray rows with a rank gradient of the alpha (0.2 -> 0.6) as in color.from_gradient
Verification
The order book was checked against an independent re-implementation of the Pine algorithm on the same candles (BTC 30-minute candles exported from TradingView (2,473 bars, 15 Aug - 5 Oct 2026)): the level histogram and the 21 rows around the last price matched on both price levels and width rank (21 of 21), and the labelled volumes are the three biggest (1.97K, 1.54K, 1.52K). Rows are labelled only for the three biggest volumes because of the platform’s 100 drawing changes per bar; the original labels every row. POC extension, the table frame and the grid (off by default) are not ported.
Full source code
Indie Script v5. Copy it into the platform’s script editor. The original Pine Script is published next to it as Volume Orderbook.pinescript6.
# indie:lang_version = 5
# Volume Orderbook (Zeiierman) — Indie port
# Original Pine Script v6 by Zeiierman (CC BY-NC-SA 4.0)
# Migration notes:
# Logic ported 1:1: price levels with step = first candle size * width, volume accumulated per level over the whole
# history, the order book of `rows` levels around the current price drawn on the last bar, box width by volume rank
# Engine limit: 100 drawing changes per bar (a rollback of the previous recalculation counts too), so volume labels
# are drawn only for the MAX_LABELS biggest rows (the width of every box still shows its volume rank)
# Not ported: POC extension, table frame and grid (all off by default in the original)
# Colours: red/lime/gray rows with a rank gradient of the alpha (0.2 -> 0.6) as in color.from_gradient
from math import isnan, nan
from indie import indicator, param, MainContext, color, Color, Optional, source
from indie.drawings import Rectangle, LabelAbs, AbsolutePosition, callout_position
GRAY = color.rgba(120, 123, 134, 1.0)
INVIS = color.TRANSPARENT
MAX_LABELS = 3
@indicator('Volume Orderbook (Zeiierman)', overlay_main_pane=True)
@param.source('src', default=source.CLOSE, title='Source')
@param.int('rows', default=10, min=0, max=50, title='Rows')
@param.float('mult', default=0.5, min=0.1, max=10.0, step=0.05, title='Width')
@param.int('left', default=5, min=0, max=50, title='Left')
class Main(MainContext):
def __init__(self):
empty_f: list[float] = []
empty_r: list[Optional[Rectangle]] = []
empty_b: list[Optional[LabelAbs]] = []
self._levels = self.new_var(empty_f)
self._volumes = self.new_var(empty_f)
self._step = self.new_var(0.0)
self._h_r = self.new_var(empty_r)
self._h_b = self.new_var(empty_b)
def _fmt(self, v: float) -> str:
if v >= 1000000.0:
return str(round(v / 1000000.0, 2)) + 'M'
if v >= 1000.0:
return str(round(v / 1000.0, 2)) + 'K'
return str(round(v, 2))
def calc(self, src, rows, mult, left):
n: int = self.bar_index
s: float = src[0]
vol: float = self.volume[0]
t0 = self.time[0]
t_step = t0 - self.time[1]
step: float = self._step.get()
if n == 0:
step = (self.high[0] - self.low[0]) * mult
levels: list[float] = []
volumes: list[float] = []
src_l = self._levels.get()
k = 0
while k < len(src_l):
levels.append(src_l[k])
k += 1
src_v = self._volumes.get()
k = 0
while k < len(src_v):
volumes.append(src_v[k])
k += 1
# --- accumulate the volume of this candle on its price level
if len(levels) <= 0:
levels.append(s + step)
levels.append(s - step)
volumes.append(vol)
else:
found: bool = False
i = 0
while i <= len(levels) - 2 and not found:
if s < levels[i] and s > levels[i + 1]:
volumes[i] = volumes[i] + vol
found = True
i += 1
if not found:
if s > levels[0]:
lvl: float = levels[0]
while s > lvl:
levels.insert(0, lvl + step)
volumes.insert(0, 0.0)
lvl = lvl + step
levels.insert(0, lvl + step)
volumes.insert(0, vol)
elif s < levels[len(levels) - 1]:
lvl2: float = levels[len(levels) - 1]
while s < lvl2:
levels.append(lvl2 - step)
volumes.append(0.0)
lvl2 = lvl2 - step
levels.append(lvl2 - step)
volumes.append(vol)
self._levels.set(levels)
self._volumes.set(volumes)
self._step.set(step)
# --- erase the previous order book, redraw it on the last bar
old_r = self._h_r.get()
k = 0
while k < len(old_r):
ho = old_r[k]
if ho is not None:
self.chart.erase(ho.value())
k += 1
old_b = self._h_b.get()
k = 0
while k < len(old_b):
hb = old_b[k]
if hb is not None:
self.chart.erase(hb.value())
k += 1
new_r: list[Optional[Rectangle]] = []
new_b: list[Optional[LabelAbs]] = []
if self.is_last_bar:
pos: int = -1
i = 0
while i <= len(levels) - 2:
if s < levels[i] and s > levels[i + 1]:
pos = i
i += 1
if pos >= 0:
width: int = rows * 2 + 1
vols: list[float] = []
x = 0
while x < width:
vols.append(0.0)
x += 1
x = 0
while x <= min(len(volumes) - 1, rows * 2):
if pos - rows + x < len(volumes) - 1:
vols[x] = volumes[max(0, pos - rows + x)]
else:
vols[x] = 0.0
x += 1
srt: list[float] = []
x = 0
while x < width:
srt.append(vols[x])
x += 1
a = 1
while a < width: # insertion sort (Indie lists have no sort())
key_v: float = srt[a]
b2 = a - 1
while b2 >= 0 and srt[b2] > key_v:
srt[b2 + 1] = srt[b2]
b2 -= 1
srt[b2 + 1] = key_v
a += 1
# the MAX_LABELS biggest volumes get a text label
label_cut: float = srt[width - MAX_LABELS] if width >= MAX_LABELS else 0.0
labels_left: int = MAX_LABELS
x = 0
while x <= rows * 2:
if pos - rows + x < len(levels) - 1:
v: float = vols[x]
rank: int = 0
q = 0
while q < width:
if srt[q] == v:
rank = q
q = width
q += 1
alpha: float = 0.2 + 0.4 * float(rank) / float(width)
col: Color = color.rgba(255, 82, 82, alpha)
if x > rows:
col = color.rgba(0, 230, 118, alpha)
if x == rows:
col = color.rgba(120, 123, 134, alpha)
top: float = levels[max(0, pos - rows + x)]
bot: float = levels[max(1, pos - rows + x + 1)]
t_l = t0 + t_step * float(left + rows * 2 - rank)
t_r = t0 + t_step * float(left + rows * 2 + rank)
box: Rectangle = Rectangle(AbsolutePosition(t_l, top), AbsolutePosition(t_r, bot),
line_color=INVIS, line_width=1, bg_color=col)
self.chart.draw(box)
new_r.append(box)
if v >= label_cut and v > 0.0 and labels_left > 0:
labels_left -= 1
lab: LabelAbs = LabelAbs(self._fmt(v), AbsolutePosition(t_r, (top + bot) / 2.0),
text_color=GRAY, bg_color=INVIS,
callout_position=callout_position.BOTTOM_RIGHT, font_size=9)
self.chart.draw(lab)
new_b.append(lab)
x += 1
self._h_r.set(new_r)
self._h_b.set(new_b)