Orderbook functions read a book snapshot to measure depth, the
distribution of resting liquidity, and bid-versus-ask pressure.
kScript (legacy) shipped six accessors over an orderbook source, each
windowed
by a depth percentage around the mid price. In Indicators the book is a
celled input (book.cells) delivering one [price, size, side] tuple
per level, and each accessor is a scan over those tuples with the same
depthPct window, worked below and compiled in one module.
| Category | What the scans give you |
|---|---|
| Volume analysis | bid and ask size summed within the depth window, and their imbalance |
| Order size analysis | the largest and smallest resting orders on each side, to spot size |
| Market depth | how far liquidity reaches and where it clusters, for support and resistance reads |
Opening the source
input("close", ohlcv.close);
input("book", book.cells, { max_cells: 200, block_size: 10, max_depth: 100 });block_sizeis REQUIRED: the venue's price-bucket width the snapshot is aggregated at.om block-sizeslists the legal widths per exchange and symbol; a width the venue does not serve is refused at fetch.max_depthis optional: the top N levels per side. With it, the block holds at most2 * max_depthtuples, which is whatmax_cellsshould cover.max_cellsis the cap in tuples, required like on every celled input.- An optional
symbol+exchangepin pair reads a fixed market's book.
om block-sizes --exchange BINANCE_FUTURESThe block is ordered: bids first, descending from the best bid, then asks
ascending from the best ask. side is +1 for a bid level and -1 for
an ask level, and size is the absolute resting quantity. The generated
accessors are the celled trio (in_book_cells(), in_book_read(ptr),
in_book_capacity, three cells per tuple), callable in state() only.
The depth window
Every kScript accessor took depthPct (default 10): only levels within
that percentage of the mid price count. The mid is the average of the
best bid and the best ask, both read straight off the ordering above (the
first bid tuple and the first ask tuple), and a level is inside the window
when abs(price - mid) <= mid * depthPct / 100. Smaller percentages (1
to 5) focus on top-of-book activity; larger ones (10 to 20) read overall
depth.
// The average of the best bid and the best ask, or NaN when either side is missing.
function bookMid(cells: StaticArray<f64>, n: i32): f64 {
let bestBid = NaN;
let bestAsk = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] > 0.0) {
if (isNaN(bestBid)) bestBid = cells[i];
} else if (isNaN(bestAsk)) bestAsk = cells[i];
}
return isNaN(bestBid) || isNaN(bestAsk) ? NaN : (bestBid + bestAsk) / 2.0;
}
// True when a level sits within depthPct percent of the mid.
function inWindow(price: f64, mid: f64, depthPct: f64): bool {
return Math.abs(price - mid) <= (mid * depthPct) / 100.0;
}Accessor functions
| kScript | Indicator | Returns |
|---|---|---|
sumBids(book, depthPct) | sumSide(cells, n, 1.0, depthPct) | total bid size within the window |
sumAsks(book, depthPct) | sumSide(cells, n, -1.0, depthPct) | total ask size within the window |
maxBidAmount(book, depthPct) | maxSide(cells, n, 1.0, depthPct) | the largest single bid within the window |
maxAskAmount(book, depthPct) | maxSide(cells, n, -1.0, depthPct) | the largest single ask |
minBidAmount(book, depthPct) | minSide(cells, n, 1.0, depthPct) | the smallest bid within the window |
minAskAmount(book, depthPct) | minSide(cells, n, -1.0, depthPct) | the smallest ask |
Three scans parameterized by side replace six builtins; each returns
NaN when no level of that side sits inside the window, and the sums
return 0 on an empty window.
function sumSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let total = 0.0;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) total += cells[i + 1];
}
return total;
}
function maxSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let best = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) {
if (isNaN(best) || cells[i + 1] > best) best = cells[i + 1];
}
}
return best;
}
function minSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let best = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) {
if (isNaN(best) || cells[i + 1] < best) best = cells[i + 1];
}
}
return best;
}Every accessor in one module
The six accessors plus the imbalance they are usually combined into,
(bids - asks) / (bids + asks), smoothed with the shipped Ema so the
per-snapshot noise reads as pressure. depth_pct is a param, so the
window is a setting the chart user can change.
import { book, input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_book_capacity, in_book_cells, in_book_read, in_close } from "./gen/inputs";
import {
emitRow,
out_ask_volume,
out_bid_volume,
out_imbalance,
out_imbalance_ema,
out_max_ask,
out_max_bid,
out_min_ask,
out_min_bid,
} from "./gen/outputs";
import { p_depth_pct, p_smooth } from "./gen/params";
import { Ema } from "./sdk/ta";
param("depth_pct", 10, { min: 0.1, max: 50, description: "Depth window as a percent of the mid price" });
param("smooth", 21, { min: 1, max: 200, description: "EMA window over the imbalance" });
input("close", ohlcv.close);
input("book", book.cells, { max_cells: 200, block_size: 10, max_depth: 100 });
output("bid_volume", line, lower, { color: "#26a69a", width: 2, description: "Bid size within the window" });
output("ask_volume", line, lower, { color: "#ef5350", width: 2, description: "Ask size within the window" });
output("max_bid", line, lower, { color: "#0f766e", width: 1, description: "Largest resting bid within the window" });
output("max_ask", line, lower, { color: "#be123c", width: 1, description: "Largest resting ask within the window" });
output("min_bid", line, lower, { color: "#5eead4", width: 1, description: "Smallest resting bid within the window" });
output("min_ask", line, lower, { color: "#fda4af", width: 1, description: "Smallest resting ask within the window" });
output("imbalance", line, lower, { color: "#94a3b8", width: 1, description: "(bids - asks) / (bids + asks), -1..1" });
output("imbalance_ema", line, lower, { color: "#2563eb", width: 2, description: "Smoothed imbalance" });
// The average of the best bid and the best ask, or NaN when either side is missing.
function bookMid(cells: StaticArray<f64>, n: i32): f64 {
let bestBid = NaN;
let bestAsk = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] > 0.0) {
if (isNaN(bestBid)) bestBid = cells[i];
} else if (isNaN(bestAsk)) bestAsk = cells[i];
}
return isNaN(bestBid) || isNaN(bestAsk) ? NaN : (bestBid + bestAsk) / 2.0;
}
// True when a level sits within depthPct percent of the mid.
function inWindow(price: f64, mid: f64, depthPct: f64): bool {
return Math.abs(price - mid) <= (mid * depthPct) / 100.0;
}
function sumSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let total = 0.0;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) total += cells[i + 1];
}
return total;
}
function maxSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let best = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) {
if (isNaN(best) || cells[i + 1] > best) best = cells[i + 1];
}
}
return best;
}
function minSide(cells: StaticArray<f64>, n: i32, side: f64, depthPct: f64): f64 {
const mid = bookMid(cells, n);
if (isNaN(mid)) return NaN;
let best = NaN;
for (let i = 0; i + 2 < n; i += 3) {
if (cells[i + 2] == side && inWindow(cells[i], mid, depthPct)) {
if (isNaN(best) || cells[i + 1] < best) best = cells[i + 1];
}
}
return best;
}
const cells = new StaticArray<f64>(in_book_capacity);
let depthPct: f64 = 10.0;
let ema = new Ema(21);
let n: i32 = -1;
let imbalance: f64 = NaN;
let smoothed: f64 = NaN;
export function init(): void {
depthPct = p_depth_pct();
ema = new Ema(i32(p_smooth()));
}
export function state(): i32 {
in_close();
n = in_book_cells();
if (n <= 0) return 0; // no snapshot, or an empty one: nothing to measure
in_book_read(i32(changetype<usize>(cells)));
const bids = sumSide(cells, n, 1.0, depthPct);
const asks = sumSide(cells, n, -1.0, depthPct);
imbalance = bids + asks > 0.0 ? (bids - asks) / (bids + asks) : NaN;
smoothed = isNaN(imbalance) ? NaN : ema.update(imbalance);
return 1;
}
export function finalize(): void {
out_bid_volume(sumSide(cells, n, 1.0, depthPct));
out_ask_volume(sumSide(cells, n, -1.0, depthPct));
out_max_bid(maxSide(cells, n, 1.0, depthPct));
out_max_ask(maxSide(cells, n, -1.0, depthPct));
out_min_bid(minSide(cells, n, 1.0, depthPct));
out_min_ask(minSide(cells, n, -1.0, depthPct));
out_imbalance(imbalance);
out_imbalance_ema(smoothed);
emitRow();
}
export function reset(): void {
ema.reset();
n = -1;
imbalance = NaN;
smoothed = NaN;
}The Ema is fed in state() (once per bar, in order) and read in
finalize(); feeding a class from finalize() would fold the value a
second time on the forming bar's replays.
Where it runs
The chart lane serves book snapshots, so the module draws on the chart;
on your machine, alerts, om metric get, and om metric series evaluate
it live (an imbalance metric is an alert operand like any other). Screens
and backtests refuse celled packages by name
(wrun_celled_metric_unsupported). A bar with no snapshot delivers a
present empty block, never a carried-forward one, so a stale book never
masquerades as a fresh read (Data sources).
Practices
- Depth analysis. Small windows read the top of book, large ones
overall depth; make
depth_pcta param and let the chart user choose. - Imbalance. A large bid-over-ask imbalance often precedes an upward move and the reverse a downward one; smooth it before alerting on it, since a single snapshot is noisy.
- Large orders.
max_bidandmax_askjumping is size arriving; pair them withmin_bid/min_askto tell a thin book from a deep one. - Bucket width.
block_sizedecides how many levels a snapshot holds at a given depth; pick it withom block-sizesand sizemax_cellsfrommax_depth.