Indie Script Developer Portal
Volume%20Orderbook
Volume 0 source files
README.md

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

  1. Level step = size of the first candle times Width.
  2. Each bar adds its volume to the level that contains the source price; if the price leaves the known range, new levels are added.
  3. On the last bar find the level of the current price and take Rows levels on each side.
  4. 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 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

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)