---
title: "Data sources"
description: "The market feeds an Indicator can read, the fields each one serves, the knobs each one takes or requires, and the celled source classes that serve a whole…"
order: 22
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/data-sources.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Data sources

The market feeds an Indicator can read, the fields each one serves, the
knobs each one takes or requires, and the celled source classes that serve
a whole block of rows per bar. A source is one feed (candles, funding, a
book snapshot, a footprint) for one market on one venue, declared as an
`input`; the module itself has no network, and every number it sees
arrives through one of these. If you know kScript (legacy), the mapping
table below names the Indicator source for each of its twenty source types.

## Loading a source

One declaration per field you read. The source is a namespace from
`./sdk/declare`, the field is a member, and the options object carries the
knobs:

```text
input("close", ohlcv.close);                                        // the selector's market
input("funding", funding.rate_close);                               // another feed, same market
input("buy", trades.volume, { side: "BUY" });                       // a required knob
input("iv", implied_volatility.implied_volatility, { tenor: "ONE_M" });
input("btc", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES" }); // a pinned market
input("daily", ohlcv.close, { interval: "1d" });                    // a coarser interval, as of its close
input("liqs", liquidations.liquidations, { side: "SELL", missing: "zero" });
input("bar_t", time.bar_open_sec);                                  // the bar's open, epoch seconds
input("profile", volume_profile.cells, { max_cells: 512 });         // a celled class
```

In the sheet the same declarations are `inputSources` entries keyed by
input name (`{ "source": "trades", "field": "volume", "side": "BUY" }`);
the declaration form derives them. Every value is read in `state()`
through `in_<name>()`, celled blocks through `in_<name>_cells()` and
`in_<name>_read(ptr)`.

### Symbol and exchange are literals

kScript resolved a source's `symbol` and `exchange` before the bar loop
and refused a script variable there. An Indicator is stricter in the same
direction: every option is a string literal, read from the text without
running it. An unpinned input follows the selector (the chart's market, or
the `--symbol` / `--exchange` you evaluate with on your machine); a pinned
input names `symbol` **and** `exchange` together, since half a pin names a
market that does not exist. There is no `currentSymbol`: leaving the pin
off is how an input follows the chart.

## kScript sources, mapped

| kScript (legacy) source | Indicator |
| --- | --- |
| `ohlcv` (`open`, `high`, `low`, `close`, `volume`) | `ohlcv.open` .. `ohlcv.volume` |
| `open_interest` (`open`, `high`, `low`, `close`) | `oi.open` .. `oi.close` |
| `buy_sell_volume` (`buy`, `sell`) | `trades.volume` with `side: "BUY"` and a second input with `side: "SELL"` |
| `funding_rate` (`value`) | `funding.rate_close` (plus `rate_open`, `rate_high`, `rate_low`, and the `predicted_*` fields) |
| `liquidations` (`buy`, `sell`) | `liquidations.liquidations` with `side: "BUY"` / `"SELL"`, or no side for the total |
| `orderbook` (`bids`, `asks`, array-celled) | `book.cells` with `block_size` (and `max_depth`) |
| `volume_profile` (array-celled) | `volume_profile.cells` |
| `deribit_implied_volatility` (`one_week`, `one_month`, `three_months`) | `implied_volatility.implied_volatility` with `tenor: "ONE_W"`, `"ONE_M"`, `"THREE_M"` |
| `skew` (`one_week`, `onemonth`, `three_months`) | `skew.skew` with the same tenors |
| `time` (a member of every source) | `time.bar_open_sec`, its own source |
| `trade_volume_by_size` (tape by order size) | **Not in Indicators yet.** The `trade_volume_by_size` class is declared for cross-host parity and refused by name at fetch (`wrun_cells_unavailable`); the buy/sell split per price lives in `volume_profile`. |
| `cme_oi`, `deribit_volatility_index`, `etf_flow`, `etf_holding`, `etf_premium_rate`, `options_volume`, `options_open_interest`, `ethena_positions`, `long_short_ratio`, `binance_treasury_balance` | **Not in Indicators yet.** No feed source serves them; a kScript that leans on one stays on kScript. |
| (no kScript counterpart) | `token_supply` (market cap, dominance, supply, TVL, by `token`), `odds` (Polymarket probabilities, pinned or bound) |

## Feed sources

All feed sources require `field`. Pins (`symbol` + `exchange` together,
`interval`) work on every feed source per `functions/script-definition.md`;
the extra knobs below are per-source.

| Source | Fields | Required knobs | Notes |
| --- | --- | --- | --- |
| `ohlcv` | `open`, `high`, `low`, `close`, `volume` | none | Candles on the selector's (or pinned) market. Close prices are `ohlcv` + `close`; there is no "market"/"price" source. |
| `trades` | `open`, `high`, `low`, `close`, `volume` | `side` (`BUY` or `SELL`) | Side-split per-period trade aggregates. |
| `funding` | `rate_open`, `rate_high`, `rate_low`, `rate_close`, `predicted_open`, `predicted_high`, `predicted_low`, `predicted_close` | none | Perp funding rate, realized and predicted. Units differ by lane (the chart serves `rate_close` as a percent), so keep thresholds as params. |
| `oi` | `open`, `high`, `low`, `close` | none | Open interest. |
| `liquidations` | `liquidations` | none (`side` optional: `BUY` or `SELL`) | Liquidation volume per period. The feed serves one series per side; with `side` pinned the scalar is that side's volume, with `side` absent it is the per-bar TOTAL over both sides (a bar present on one side only contributes that side). Naturally sparse: rows exist only where liquidations happened, so as a primary it usually declares `missing: "zero"`. |
| `implied_volatility` | `implied_volatility` | `tenor` | Options IV at a tenor (`ONE_D`, `THREE_D`, `ONE_W`, `ONE_M`, `TWO_M`, `THREE_M`, `SIX_M`, `ONE_Y`). |
| `skew` | `skew` | `tenor` | Options skew at the same tenor vocabulary. |
| `token_supply` | `marketcap`, `first_marketcap`, `marketcap_dominance_percent`, `circulating_supply`, `total_supply`, `max_supply`, `total_value_locked`, `fully_diluted_valuation`, `cg_marketcap_rank`, `total_volume`, `usd_price` | `token` | Supply and cap series keyed by token name. |
| `odds` | `open`, `high`, `low`, `close` (default), `volume` | none | Polymarket probabilities; own pin rules (`functions/script-definition.md`): conditionId as `symbol` or `binding: "required"`, `outcome` YES/NO, exchange implicit. |

A sheet that exercises the per-source requirements together (the same
declarations, in the wire form the registry and every runtime read):

```json
{
  "id": "context-pack",
  "abi_version": "wrun-1",
  "warmup_bars": 1,
  "params": [],
  "inputSources": {
    "close": { "source": "ohlcv", "field": "close" },
    "buy_volume": { "source": "trades", "field": "volume", "side": "BUY" },
    "iv_1m": { "source": "implied_volatility", "field": "implied_volatility", "tenor": "ONE_M" },
    "btc_mcap": { "source": "token_supply", "field": "marketcap", "token": "BTC" },
    "bar_time": { "source": "time", "field": "bar_open_sec" },
    "sell_liqs": { "source": "liquidations", "field": "liquidations", "side": "SELL", "missing": "zero" }
  },
  "inputs": [
    { "index": 0, "name": "close" },
    { "index": 1, "name": "buy_volume" },
    { "index": 2, "name": "iv_1m" },
    { "index": 3, "name": "btc_mcap" },
    { "index": 4, "name": "bar_time" },
    { "index": 5, "name": "sell_liqs", "description": "SELL-side liquidations, 0 on bars without any" }
  ],
  "outputs": [{ "index": 0, "name": "stress", "plot": "line", "panel": "lower" }]
}
```

### Alignment and the `missing` policy

The first input is the primary: it defines the grid (market and interval)
every other input aligns to. Equal-or-finer sources align by bar open, row
for row; a coarser source (an `interval` pin) contributes to a primary row
only as of its candle's close, so a forming 4h candle never leaks into the
1h rows under it. A scalar source with no observation on a primary bar
delivers, by policy, the latest eligible value carried forward (`missing:
"carry"`, the default), `NaN` (`"nan"`), or `0` (`"zero"`). On the primary
input `"nan"` and `"zero"` change the grid itself: the package computes on
a dense grid of bar opens at the primary's interval, which is how a sparse
series such as liquidations becomes a dense primary. Every rule is in
`functions/script-definition.md`.

## The `time` source

`time.bar_open_sec` (`{ "source": "time", "field": "bar_open_sec" }` in a
sheet) carries the primary bar's open timestamp (epoch seconds, UTC),
host-supplied off the primary grid, so a module can do session and
calendar math deterministically (`time-and-sessions.md`). Exactly one
field; every other knob is refused (there is no feed behind it); never the
primary input.

## Metric composition

`{ "source": "metric", "metric": "wrun/@scope/name/output" }` feeds another
installed package's output in as an input, with optional `params` for the
inner package. Rules in `functions/script-definition.md`; note composition
inputs are metadata-first only in code-first workspaces (the declaration
grammar refuses them by name), and a `metric` source pinned coarser than
the primary grid is refused by name.

## Permissions

`read:openmarket` covers every feed and celled source and is required
whenever any of them follows the selector; `metric` and `time` sources need
no permission. Venue-scoped reads (`read:binance`, `read:bybit`,
`read:hyperliquid`, `read:polymarket`) are a narrowing option only for
packages whose EVERY feed and celled source pins one of those venue
families.

## Celled source classes

A scalar source serves one number per bar. A celled source class serves a
whole BLOCK of rows per bar, which is what footprint-style indicators need:
a footprint is the same candle sliced by price, one `[low, high, buy, sell]`
row per price bucket, so "did buyers or sellers do the volume, and at which
prices" is answerable inside one bar instead of only as a per-bar total.
Celled inputs are declared as `input(name, <class>.cells, { max_cells })`
(`cellType: "array"` + `max_cells` in a sheet), which switches the derived
sheet to the second runtime contract (`abi_version: "wrun-2"`); the sheet
rules, and the exact-join alignment that never forward-fills a block, are
in `functions/script-definition.md`, and the module-side accessors are in
`functions/ta-library.md`:

| Class | Cell tuple | Serving | Knobs |
| --- | --- | --- | --- |
| `volume_profile` | `[low, high, buy, sell]` per price bucket | FETCHES live (`VOLUME_PROFILE_AGG`; the API's flat `[price, buy, sell]` triplets arrive with `low = high = price`) | optional `symbol` + `exchange` pin pair |
| `book` | `[price, size, side]` per level, side `+1` bid / `-1` ask, bids descending then asks ascending | FETCHES live (`BLOCK_BOOK_SNAPSHOT_AGG`) | `block_size` REQUIRED (the venue price-bucket width; `om block-sizes` lists them), optional `max_depth` (top N levels per side), optional pin pair |
| `tape` | `[offsetMs, price, size, side]` per print, side `+1` BUY / `-1` SELL | Live daemon buffer (`TRADE` websocket) | `min_size` REQUIRED; `history` defaults to `"0"`, other values refused; optional pin pair |
| `trade_volume_by_size` | declared for cross-host parity | REFUSED by name (`wrun_cells_unavailable`): no point lane on this data plane yet | none |

The raw `tape` class is live only. The REST API has no raw-trade data type
or minimum-size filter, so tape cannot backfill historical prints.
`TRADE_AGG` still serves per-period scalars through `trades` above.
kScript's `orderbook` depth functions (`sumBids`, `maxAskAmount`, ...) are
loops over the `book` block (`functions/orderbook-functions.md`).

Two named refusals bound the surface: an unserved class (or `cellType` on a
scalar source) refuses at fetch with `wrun_cells_unavailable` before
anything is fetched, and backtests / screens refuse whole celled packages
with `wrun_celled_metric_unsupported` (their replay and fan-out paths carry
no cell blocks yet). Alerts, `om metric get` / `om metric series`, and
chart previews are the supported consumers.

## Live tape

`tape` is a celled source for live individual trades on the daemon.
Declare a scalar primary input first, then a tape input:

```json
{
  "inputSources": {
    "close": { "source": "ohlcv", "field": "close" },
    "prints": { "source": "tape", "min_size": 1, "history": "0" }
  },
  "inputs": [
    { "index": 0, "name": "close" },
    { "index": 1, "name": "prints", "cellType": "array", "max_cells": 4096 }
  ]
}
```

Use `abi_version: "wrun-2"` or later. `min_size` is required, positive,
and measured in the raw trade's amount units. `history` defaults to `"0"`.
Every other history value refuses with `wrun_tape_history_unavailable`:
`history needs the raw trade lane; live prints only`.

Each print occupies four f64 values: `[offsetMs, price, size, side]`.
`offsetMs` starts at the primary bar's open; side is +1 BUY or -1 SELL.
Generated accessors reserve `max_cells * 4` f64 values. A bar exceeding
`max_cells` refuses the evaluation; it is never clipped.

The daemon buffer retains at most two hours or 200,000 prints, deduplicates
trade ids, and keeps sizes at or above the smallest active threshold.
Every input applies its own threshold when reading. Older bars and a buffer
that has not started return empty blocks. Historical REST backfill is
unavailable. `trades` still means scalar side-split aggregates.

The daemon opens the market's tape the first time a package with a tape
input is evaluated and keeps it streaming while evaluations keep touching
it (two primary bars plus fifteen idle minutes close it). Bars longer than
the two-hour retention see only the retained prints. A lane with no live stream, such as a
backtest replay or a one-shot read outside the daemon, refuses with
`wrun_tape_unavailable`. The celled contract (`docs/WRUN2_ABI.md`, section
"Live tape", in the openmarket repository) lists the lifecycle; the caps
are on [Limits](../reference/limits.md).

## The worked footprint example

The `vp-buy-share-codefirst` template is the footprint loop end to end: a
celled `volume_profile` input, the buy share of each bar's profile as a
numeric output, and a per-bar text renderer fed from a string slot. The
whole user file (code-first, so the sheet is derived):

```typescript
import { input, line, lower, ohlcv, output, render, string, volume_profile } from "./sdk/declare";
import { in_profile_capacity, in_profile_cells, in_profile_read } from "./gen/inputs";
import { emitRow, out_buy_share } from "./gen/outputs";
import { sb_clear, sb_f64, sb_text, str_summary_sb } from "./gen/strings";

input("close", ohlcv.close);
input("profile", volume_profile.cells, { max_cells: 512 });
output("buy_share", line, lower);
string("summary", { max_bytes: 64 });
render.text("flow", { y: "buy_share", text: "summary" });

const cells = new StaticArray<f64>(in_profile_capacity); let share: f64 = NaN;

export function init(): void {}
export function state(): i32 {
  const n = in_profile_cells(); if (n <= 0 || in_profile_read(i32(changetype<usize>(cells))) < 0) return 0;
  let buy = 0.0; let sell = 0.0;
  for (let i = 0; i + 3 < n; i += 4) { buy += cells[i + 2]; sell += cells[i + 3]; }
  share = buy + sell > 0.0 ? (100.0 * buy) / (buy + sell) : NaN; return isNaN(share) ? 0 : 1;
}
export function finalize(): void { out_buy_share(share); sb_clear(); sb_text("buy "); sb_f64(share, 1); sb_text("%"); str_summary_sb(); emitRow(); }
export function reset(): void { share = NaN; }
```

Scaffold it, install it, and read a live value on real volume-profile rows:

```bash
om wrun create @you/vp-flow ./vp-flow --template vp-buy-share-codefirst
om wrun install ./vp-flow --replace
om metric get --metric wrun/@you/vp-flow/buy_share --symbol BTCUSDT --exchange BINANCE_FUTURES
om metric series --metric wrun/@you/vp-flow/buy_share --symbol BTCUSDT --exchange BINANCE_FUTURES --bars 48
```

A bar whose profile splits 60/40 to the buy side computes `buy_share = 60`
and renders the text `buy 60.0%` at that bar. For a book-driven variant,
pick the venue's bucket width first:

```bash
om block-sizes --exchange BINANCE_FUTURES
```

## How many sources you can open

kScript budgeted ten weighted source slots per script. An Indicator has no
slot budget: every input is one series requirement, and the practical
cost is fetch time on the widest window (a coarse pin widens the fetch by
two source intervals). Two inputs with the same source, market, and knobs
are two declarations over one feed; a derived value (an average of an
input) costs nothing, since it is your arithmetic. The one hard ceiling
is on the other side: a module may write at most 256 output slots.

## Availability

The declaration grammar accepts any registered source and field; whether a
venue serves it for a market is a platform question, answered by name
before the module runs (`wrun_cells_unavailable` for an unserved celled
class, a chart message naming the source for a feed the browser lane does
not serve), never with silent empty data. `execution-model.md` lists what
each host serves.
