---
title: "Data sources"
description: "The market data a wrun indicator can read on the chart: the sources the chart serves, the fields and knobs each one takes, what each value means, and the…"
order: 10
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 data a wrun indicator can read on the chart: the sources the chart
serves, the fields and knobs each one takes, what each value means, and the
celled sources that hand the module a whole block of rows per bar. A
source is one feed (candles, funding, a book snapshot, a footprint)
declared as an `input`, or read straight off the chart's own candle
through `bar`; the module itself has no network, and every number
it sees arrives through one of these, fetched by the chart over the bars it
has loaded.

![The same candles twice. Pine reads the bars and their volume. wrun also reads the order book's resting orders, the buyers and sellers at every price, the call and put walls from the options chain, and the long and short liquidations under the bars that forced them](/wrun/images/diagrams/sees-chart.svg)

1. **Pine** reads the candles and their volume.
2. **wrun** reads the same candles and what moved them: the resting orders
   in the order book and the buyers and sellers at every price
   ([celled sources](#celled-sources)), the call and put walls from the
   [options chain](#options-chain), and long and short liquidations
   ([feed sources](#feed-sources)).

## Loading a source

One declaration per field you read beyond the chart's own candle. The
source is a namespace word, the field is a member, and the options object
carries the knobs:

```text
input("close", ohlcv.close);                                        // the chart's own candles
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" }); // another market's candles
input("daily", ohlcv.close, { interval: "1d" });                    // a coarser interval, as of its close
input("liqs", liquidations.liquidations, { side: "SELL", missing: "zero" });
input("us10y", economic.value, { publisher: "FRED", series: "DGS10" }); // a series named by its own knobs
input("bar_t", time.bar_open_sec);                                  // the bar's open, epoch seconds
input("profile", volume_profile.cells, { max_cells: 8192 });        // a celled source
input("d", candles.cells, { interval: "1d", bars: 400 });           // closed daily candles as a stream
```

The editor derives the sheet from these declarations as it compiles: each
one becomes an `inputSources` entry keyed by input name
(`{ "source": "trades", "field": "volume", "side": "BUY" }`). Every value is
read in `onBar()` through `in_<name>()`, a celled block through
`in_<name>_cells()` and `in_<name>_view()` ([Celled sources](#celled-sources)).

### The bar grid and the bar's own fields

The chart's own candle needs no declaration: `bar.open()`, `bar.high()`,
`bar.low()`, `bar.close()`, `bar.volume()` and `bar.time()` read it inside
`onBar()`, and reading a field adds its input to the sheet, on the chart's
own market and interval, with no options:

| Member | Input it adds | Source field |
| --- | --- | --- |
| `bar.open()` | `open` | `ohlcv.open` |
| `bar.high()` | `high` | `ohlcv.high` |
| `bar.low()` | `low` | `ohlcv.low` |
| `bar.close()` | `close` | `ohlcv.close` |
| `bar.volume()` | `volume` | `ohlcv.volume` |
| `bar.time()` | `bar_t` | `time.bar_open_sec` (the bar's open, epoch seconds UTC) |

Input 0 of the sheet is the bar grid: the market and interval every row
is a candle of, which every other input aligns to. The rules:

- **A file with no `input` line runs on the chart's own candles.** It gets
  `close` as input 0 whether or not it reads `bar.close()`, then the other
  fields it reads (`bar.time()` alone gives `close`, `bar_t`).
- **The first `input` line is the grid.** Once a file has an input line,
  the first one is input 0, never a bar field. A file whose first input
  line names another candle field (a lone `input("high", ohlcv.high);`)
  keeps that line, so the grid stays on `high` instead of moving to the
  close; `bar.high()` reads through it, and the other `bar` fields work
  either way. A typed scalar feed may come
  first (`input("fund", funding.rate_close)` then `bar.close()` gives
  `fund`, `close`); a celled or `time` input first is refused when the
  sheet is checked (`celled source class 'volume_profile' cannot be the
  primary input (index 0): the primary must be a fetched scalar series that
  defines the request grid`; `a time source cannot be the primary input
  (index 0); the primary defines the request grid, declare a feed or metric
  source first`; for `intrabar`, `wrun_intrabar_primary: input 'm1' reads
  intrabar cells at index 0; the primary input must be a fetched scalar
  series that defines the request grid the finer bars are sliced by
  (declare ohlcv first, intrabar as a secondary input)`; for `candles`,
  `wrun_candles_primary: input 'd' reads candles at index 0; the primary
  input must be a fetched scalar series that defines the request grid the
  candles are delivered on (declare ohlcv first, candles as a secondary
  input)`), so a file with a celled input keeps a scalar line in front of
  it (`input("close", ohlcv.close);`).
- **Declared inputs keep their order and indexes.** The fields the file
  reads through `bar` that no declared input serves are appended after
  them, in the fixed order `open`, `high`, `low`, `close`, `volume`,
  `bar_t`.
- **A declared input serves its field.** When an input is exactly that
  feed (a scalar input with that source and field and no option other than
  `description`), `bar.close()` reads through it, under its own name and
  index: `input("price", ohlcv.close)` serves `bar.close()`, and
  `in_price()` and `bar.close()` are then the same value. A `symbol`,
  `exchange`, `interval`, `views`, `missing` or any other option, a
  `param.source` pick or a derived view makes it another feed, and the
  field is added as an input of its own.
- **The bar's names are reserved.** An input named `open`, `high`, `low`,
  `close`, `volume` or `bar_t` with another feed, in a file that reads that
  field through `bar`, is refused on the line of the first read:
  `bar.close() reads the chart's own ohlcv.close through an input named
  'close', and input 'close' is already declared with another feed; rename
  the declared input (open, high, low, close, volume and bar_t are the
  bar's own names)`. The same name is fine when the file never reads that
  field through `bar`.

### Symbol and exchange are literals

Every option is a literal, read from the text without running it.
An input without a pin follows the chart's market. A pinned
input names `symbol` **and** `exchange` together, written in the chart's
own ids (`BTCUSDT` on `BINANCE_FUTURES`): symbols are venue-native, so the
editor refuses half a pin, which would name a market that does not exist.
Leaving the pin off is how an input follows the chart. A pin may name a
stock or ETF (`POLYGON`), a forex pair, gold or silver (`FX_OTC`:
`EUR/USD`, `XAU/USD`, `XAG/USD`) beside the crypto venues
([Stocks, forex and gold](multi-source.md#stocks-forex-and-gold)); a
secondary input pinned to the CME Group
family (`CME`, `CBOT`, `NYMEX`, `COMEX`, `GLOBEX`) is refused by name,
because that market data cannot be read from another market's script
(the first input is the package's own market, and whether a host serves
a CME market is the host's call). Which inputs may pin a
market or an interval is on [Multi-source](multi-source.md) and
[Multi-timeframe](multi-timeframe.md); the venue ids are on [Symbol
format](../reference/symbol-format.md).

## Feed sources

A feed source serves one number per bar. Every one needs its `field`, and
each reads the chart's own market or its coin: only a secondary `ohlcv`
input may name another market, an `odds` input names its own Polymarket
market, and a few series are named by a knob of their own (a fund, a
publisher and a series id, a token). The tables group them the way a
chart does: the market itself, the options of its coin, and the series
from beyond the venue.

### The chart's market

| Source | Fields | Knobs | What the chart serves |
| --- | --- | --- | --- |
| `ohlcv` | `open`, `high`, `low`, `close`, `volume` | none | The chart's own candles, history plus the live bar. Close prices are `ohlcv` + `close`; there is no "market" or "price" source. A secondary input may pin another market or a coarser interval. |
| `trades` | `volume` | `side`: `BUY` or `SELL` (required); `currency`: `USD` or `Coin` (optional) | One side's traded volume per bar, in the base asset (coins), history plus live. `currency: "USD"` reads it in dollars instead (each trade's notional), `"Coin"` in coins; two inputs differing only in `currency` are two lanes, so a package with a units setting declares both and picks one. Any other field is refused by name. |
| `oi` | `open`, `high`, `low`, `close` | none | Open interest in USD, refreshed by polling. May pin a coarser interval. |
| `liquidations` | `liquidations` | `side` (optional): `BUY` or `SELL` | Liquidation volume in USD, history plus live. With `side` the value is that side's; without it, the bar's total over both sides. Rows exist only where liquidations happened, so declare `missing: "zero"` when a quiet bar should read 0. |
| `funding` | `rate_close` | none | The funding rate in percent, normalized to a one-hour interval, history plus live. The other funding fields are refused by name. May pin a coarser interval. |
| `long_short_ratio` | `total_account`, `top_trader_account`, `top_trader_position` | none | The market's long/short ratios, plain ratios of longs over shorts: every account, the venue's top traders counted by account, and the top traders counted by position size. Refreshed by polling. A value at or below 0 reads `NaN`, and the chart must be 5 minutes or coarser. |
| `odds` | `open`, `high`, `low`, `close` (default), `volume` | `symbol`: the market's condition id (required); `outcome`: `YES` (default) or `NO` | A Polymarket market's YES probability, 0 to 1, refreshed by polling. Details below. |
| `time` | `bar_open_sec`, `trade_date`, `session` | none | The bar's open time in epoch seconds, its exchange trade date, and its session phase (below). |

### Options, by the chart's coin

| Source | Fields | Knobs | What the chart serves |
| --- | --- | --- | --- |
| `implied_volatility` | `implied_volatility` | `tenor`: `ONE_W`, `ONE_M`, `TWO_M`, `THREE_M` or `SIX_M` (required) | Deribit's implied volatility for the chart's coin at that tenor, refreshed by polling. `ONE_D`, `THREE_D` and `ONE_Y` are refused by name: the data carries none of them. |
| `skew` | `skew` | `tenor`, as above (required); `delta`: `5`, `15`, `25` (the default) or `35` | Deribit's skew for the chart's coin at that tenor and delta, refreshed by polling. |
| `volatility_index` | `open`, `high`, `low`, `close` | none | Deribit's volatility index (DVOL) for the chart's coin, in index points, refreshed by polling. BTC and ETH. |
| `options_oi` | `puts`, `calls` | `venue`: `deribit` (the default) or `binance` | Put and call open interest of the chart's coin on that venue, in the venue's own units (contracts on Deribit, the base coin on Binance), refreshed by polling. BTC and ETH; another coin is refused by name. |
| `options_volume` | `puts`, `calls` | `venue`, as above | Put and call volume of the chart's coin on that venue, in contracts, refreshed by polling. BTC and ETH; another coin is refused by name. |

These are per-bar summaries. The chain itself, contract by contract, is
the celled `options_chain` source ([Options chain](#options-chain)).

### Beyond the chart's venue

| Source | Fields | Knobs | What the chart serves |
| --- | --- | --- | --- |
| `etf_flow` | `flow_usd` | `fund`: an ETF ticker (`IBIT`, `FBTC`, `ETHA`, ...) or `all` (required) | Daily net spot-ETF flow in USD, on a chart of BTC, ETH or SOL; another coin, or a ticker the coin does not list, is refused by name. |
| `etf_holdings` | `holdings` | `fund`: an ETF ticker or `all` (required) | The fund's holdings, in coins of its underlying; `all` sums every fund listed for the chart's coin. A coin without listed funds, or a ticker the coin does not list, is refused by name. |
| `etf_premium` | `premium_rate` | `fund`: one ETF ticker (required) | The fund's premium to its net asset value, in percent, daily. `all` is refused by name: a rate does not sum across funds. |
| `ethena_positions` | `collateral` | none | Ethena's collateral held against the chart's coin, in USD, summed over the venues that hold it. BTC and ETH; another coin is refused by name. |
| `bitfinex_funding` | `funding_size`, `credit_size`, `active_credit_size`, `margin_rate` | none | Bitfinex margin funding for the chart's coin: the total funding provided, the funding used in positions and the active credits, each in the funding currency, and the margin rate as an APR in percent. |
| `treasury_balance` | `balance` | `asset` (optional): an upper-case ticker, the chart's coin when absent | Binance's own balance of that coin (its liability minus its customer liability), in coins, published monthly. |
| `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`: the token's display name (required) | A token's supply and market-cap series, daily. The token is named the way the series spells it (`Bitcoin`, `Ethereum`, `Solana`), never by its ticker; a name the chart does not list is refused by name, and so is a one-minute chart. |
| `economic` | `value` | `publisher` and `series` (both required) | One economic series in the publisher's own unit, daily or slower: `{ publisher: "FRED", series: "DGS10" }` is the US 10-year Treasury yield in percent. A publisher the chart does not know is refused by name. |

Every feed in the last two tables is refreshed by polling, and none takes
a market or an interval pin: each follows the chart's coin or the series
its knobs name.

A few behaviors to plan for:

- **Funding exists on perpetuals only.** On a spot, FX or prediction
  market, a `funding` input that declares `missing: "nan"` or `"zero"`
  reads that fill on every bar; on the default `carry` policy the run stops
  with a message naming the funding rate.
- **ETF flows are daily.** On an intraday chart the day's value lands on
  the first bar of its day and the later bars of that day follow the
  `missing` policy (`carry` repeats it across the day, `nan` and `zero`
  leave a one-bar spike); on a daily chart it joins row for row; on a
  coarser chart each bar sums its days. A day's flow is known after the US
  session, so on an intraday chart it is a same-day display, never a
  signal that was available at the day's open.
- **Daily levels land the same way, and never sum.** `etf_premium`,
  `economic` and `treasury_balance` are daily or slower: a chart bar reads
  the newest value at or before its day. On an intraday chart the day's
  value lands on the first bar of its day and the later bars follow the
  `missing` policy; on a daily chart it joins row for row; on a coarser
  chart a bar reads its last day. A slower series (a monthly balance, a
  weekly print) carries between its prints under `carry`.
- **Polymarket odds.** Pin the market by its condition id (`0x` followed by
  64 hex characters) in `symbol`. The exchange is implied (declaring one is
  refused), and the chart has no market picker, so a `binding` is refused.
  `outcome: "NO"` reads 1 minus the close. An `odds` input may be the first
  input (the rows are still the chart's candles), and it needs a chart of
  one minute or coarser. The prediction-market starters in the template
  picker ship a placeholder market: **Run** refuses them until you paste a
  real condition id into the input's `symbol`.
- **Required and optional sources.** A source the chart must have (volume
  profile, implied volatility, skew, the options chain, ETF flows, funding
  on the default policy, the candles of a pinned or `intrabar` input) that
  answers empty stops the run with a toast naming it. Trades, open interest,
  liquidations and the book may legitimately be empty for a stretch, so
  they read their `missing` fill (the book an empty block) instead. Every
  other feed of the options and beyond-the-venue tables, and
  `long_short_ratio`, follows the funding rule: an empty answer stops the
  run with a message naming the feed, unless every input that reads it
  declares `missing: "nan"` or `"zero"`, in which case those inputs read
  that fill. A volume profile declared `missing: "empty"` is optional: on
  a market that serves none (a stock, an index), or a stretch with no
  profile, every bar reads an empty block (`in_<name>_cells()` is `0`) and
  the run goes on, so the parts of the indicator that read it blank while
  the rest draws as usual. A bar whose profile has more buckets than the
  input's `max_cells` reads the same empty block on an optional profile
  (on a required one it stops the run: a block is never truncated).
  `"empty"` is the one word a celled input takes, and only a volume
  profile takes it.

A code-first file that exercises the per-source requirements together (a
required `side`, a required `tenor`, an optional `side` with a `missing`
policy) and puts the units side by side:

```typescript sample=context-pack
input("close", ohlcv.close);
input("buy_volume", trades.volume, { side: "BUY" });
input("iv_1m", implied_volatility.implied_volatility, { tenor: "ONE_M" });
input("sell_liqs", liquidations.liquidations, { side: "SELL", missing: "zero", description: "SELL-side liquidations, 0 on bars without any" });
output("stress", line, lower, { description: "SELL liquidations per dollar of buy volume, scaled by one-month IV" });

function onBar(): void {
  // trades volume is in coins; liquidations are already in USD.
  const buyUsd = in_buy_volume() * bar.close();
  const iv = in_iv_1m();
  if (isNaN(buyUsd) || buyUsd <= 0.0 || isNaN(iv)) return;
  out_stress((in_sell_liqs() / buyUsd) * iv);
}
```

The option summaries read the same way. This one puts five of them side
by side on a BTC or ETH perpetual chart of 5 minutes or coarser: put/call
ratios by open interest (Deribit, the default venue) and by volume
(Binance), the volatility index, a one-week skew at 5 delta, and the top
traders' long/short ratio:

```typescript sample=options-positioning
input("close", ohlcv.close);
input("put_oi", options_oi.puts); // venue absent: Deribit, in contracts
input("call_oi", options_oi.calls);
input("put_volume", options_volume.puts, { venue: "binance" });
input("call_volume", options_volume.calls, { venue: "binance" });
input("dvol", volatility_index.close);
input("wing_skew", skew.skew, { tenor: "ONE_W", delta: 5 });
input("long_short", long_short_ratio.top_trader_position, { missing: "nan", description: "Top traders' long/short ratio by position, NaN on a bar without a print" });
output("put_call_oi", line, lower, { description: "Puts over calls by open interest, Deribit" });
output("put_call_volume", line, lower, { description: "Puts over calls by volume, Binance" });
output("dvol", line, lower, { description: "Deribit volatility index, index points" });
output("wing_skew", line, lower, { description: "One-week skew at 5 delta" });
output("long_short", line, lower, { description: "Top traders' long/short ratio by position" });

function onBar(): void {
  if (isNaN(bar.close())) return;
  // A ratio needs a positive denominator: NaN before the first print, never a division by zero.
  const callOi = in_call_oi();
  const callVolume = in_call_volume();
  out_put_call_oi(callOi > 0.0 ? in_put_oi() / callOi : NaN);
  out_put_call_volume(callVolume > 0.0 ? in_put_volume() / callVolume : NaN);
  out_dvol(in_dvol());
  out_wing_skew(in_wing_skew());
  out_long_short(in_long_short());
}
```

And the series from beyond the venue, on a BTC chart. Holdings and the
treasury balance arrive in coins, so the chart's close prices them; the
ETF premium, the yield and the token's market cap arrive in their own
units and are plotted as they are:

```typescript sample=beyond-the-venue
input("close", ohlcv.close);
input("etf_coins", etf_holdings.holdings, { fund: "all" }); // coins held by every listed fund
input("marketcap", token_supply.marketcap, { token: "Bitcoin" }); // USD; the token by its display name
input("premium", etf_premium.premium_rate, { fund: "IBIT" }); // percent, daily
input("us10y", economic.value, { publisher: "FRED", series: "DGS10" }); // the publisher's unit: percent
input("treasury", treasury_balance.balance, { asset: "BTC" }); // coins; monthly
input("ethena", ethena_positions.collateral);
input("provided", bitfinex_funding.funding_size); // funding currency
input("lent", bitfinex_funding.credit_size); // the part in use
output("etf_share", line, lower, { unit: "%", description: "Spot-ETF holdings as a share of the market cap" });
output("premium", line, lower, { unit: "%", description: "IBIT premium to net asset value" });
output("us10y", line, lower, { unit: "%", description: "US 10-year Treasury yield" });
output("treasury_usd", line, lower, { description: "Binance's own BTC balance, in USD" });
output("ethena", line, lower, { description: "Ethena collateral in the chart's coin" });
output("utilization", line, lower, { unit: "%", description: "Bitfinex funding in use, as a share of funding provided" });

function onBar(): void {
  const close = bar.close();
  if (isNaN(close)) return;
  // Holdings and the treasury balance are coins: the close turns them into USD.
  const cap = in_marketcap();
  out_etf_share(cap > 0.0 ? (100.0 * in_etf_coins() * close) / cap : NaN);
  out_premium(in_premium());
  out_us10y(in_us10y());
  out_treasury_usd(in_treasury() * close);
  out_ethena(in_ethena());
  const provided = in_provided();
  out_utilization(provided > 0.0 ? (100.0 * in_lent()) / provided : NaN);
}
```

### Alignment and the `missing` policy

The rows are the chart's own candles: the first input follows the chart's
market and interval, and every other input is aligned onto those rows.
Sources at the chart's interval join by bar open, row for row; a coarser
`interval` pin contributes to a row only as of that row's close, so a
forming 4h candle never leaks into the 1h rows under it
([Multi-timeframe](multi-timeframe.md)). A scalar source with no
observation on a bar delivers, by policy, the latest value carried forward
(`missing: "carry"`, the default; `NaN` before the first observation),
`NaN` (`"nan"`), or `0` (`"zero"`). Celled blocks never carry: a bar with
no observation gets an empty block.

## The `time` source

`time.bar_open_sec` carries the bar's open timestamp (epoch seconds, UTC),
taken from the chart's own candle, so a module can do session and calendar
math deterministically ([Time and sessions](time-and-sessions.md));
`bar.time()` reads it with no declaration. Two
more fields carry the bar's market facts, so an indicator never has to
know the venue's calendar:

- `time.trade_date`: the bar's exchange trade date, as epoch seconds at
  00:00 UTC of that date. On CME futures the trade date starts at the
  17:00 Chicago open, so an evening bar belongs to the next date; on US
  stocks it is the New York calendar date; on crypto, forex, HIP-3 and
  Polymarket markets it is the UTC date.
- `time.session`: `1` regular, `2` pre-market, `3` after-hours, `0`
  closed. US stocks follow the New York pre-market, regular (09:30 to
  16:00) and after-hours sessions; CME futures read `1` inside the
  contract's regular trading hours (all session long for a contract
  without them), `2` before and `3` after them within the same trade date,
  and `0` while the market is closed; a market without sessions reads `1`
  on every bar.

A bar of one day or longer (a 1D, 3D, 1W or 1M chart) is a whole trading
day or more, so it takes the date of its own stamp and `session` `1` on
every market: the chart stamps such bars at 00:00 UTC of their trade date.
Both fields are facts of the market, the bar and its interval alone:
switching the chart between regular and extended hours never changes
them. Every field is
filled by the chart (there is no feed behind it), every other knob is
refused, and a `time` input is never the first input.

## Celled sources

A feed source serves one number per bar. A celled source 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, <source>.cells, { max_cells })`,
which moves the derived sheet to at least the second contract
(`abi_version: "wrun-2"`); the module-side accessors are in
[TA library](../functions/ta-library.md):

| Source | One tuple per | What the chart serves | Knobs |
| --- | --- | --- | --- |
| `volume_profile` | price bucket: `[low, high, buy, sell]` | the chart's own volume profile for each bar, with real bucket bounds, in ascending price order; history plus live | `max_cells`; `ticks_per_bar` (1 to 500 of the market's buckets merged into one, or `"@<param>"` naming a `param.int` whose value it takes); `currency` (`"USD"` for dollar volumes, `"Coin"` by default); `missing: "empty"` (the profile optional: below) |
| `book` | level: `[price, size, side]`, side `+1` bid, `-1` ask | the chart's own order book for each bar, up to 500 levels a side: bids first, then asks from the highest price down; history plus live | `max_cells` (1000 holds both sides); `block_size` (the declaration requires one, but the chart reads its own order-book feed and does not use it or `max_depth`) |
| `intrabar` | closed finer bar: `[offset_ms, open, high, low, close, volume]`, `offset_ms` the finer open minus the bar's open | the finer candles inside each bar, of the chart's market or a pinned one | `interval` (required), `max_cells`, `symbol` + `exchange` |
| `candles` | closed candle of the stream's interval: `[offset_ms, open, high, low, close, volume]`, `offset_ms` the candle's open minus the bar's open | a stream of closed candles: the backlog on the first bar, then each candle on the bar it closes with, of the chart's market or a pinned one | `interval` (required, any word), `bars` (1 to 5000), `max_cells` (the editor fills it when left out), `symbol` + `exchange` |
| `trade_volume_by_size` | USD trade-size bucket: `[bucket, buy_usd, sell_usd, buy_count, sell_count]` | the chart market's traded volume split by the size of each trade (each fill bucketed by its own dollar value), one tuple per bucket that traded in the bar, in ascending bucket order | `max_cells` (7 holds a full bar) |
| `options_chain` | listed contract: `[strike, expiry_ms, side, oi, gamma, delta, mark_iv, underlying, multiplier, vega]` | the newest option chain, on the newest bar only | `venue`, `expiries`, `max_cells` |

Every celled source reads the chart's own market (a market pin is
refused) except `intrabar` and `candles`, which may name another market
with `symbol` and `exchange` together. A block over `max_cells` refuses
the run instead of being truncated, except on a volume profile declared
`missing: "empty"`, where that bar reads an empty block. Order-book
depth figures (the summed bid size, the largest ask, ...) are loops over
the `book` block
([Order flow](../functions/order-flow-kit.md#depth-window-scans-by-hand)); the
`intrabar` and `candles` rules are on
[Multi-timeframe](multi-timeframe.md).

### Reading a block

A celled input gets three readers and two constants, and the build owns
one buffer per celled input, allocated at module start (`max_cells` x the
tuple width, so 8192 volume profile tuples are 262,144 bytes):

- `in_<name>_cells(): i32`: the f64 values in this bar's block; `-1` when
  the bar carries none, `0` for an empty block.
- `in_<name>_view(): StaticArray<f64>`: the build's own buffer, no copy.
  Only the first `in_<name>_cells()` values belong to this bar: on an
  absent or empty bar the buffer still holds the previous block, so always
  bound the loop by the count.
- `in_<name>_read(ptr: i32): i32`: copies the block to memory at `ptr` and
  returns the bytes written (count x 8), `-1` absent, `0` empty. It is for
  code that owns a buffer of its own, which then holds the block twice;
  prefer the view.
- `in_<name>_max_cells` and `in_<name>_capacity` (`max_cells` x tuple
  width): constants.

Read the block where you write, bounded by the count:

```typescript
function onBar(): void {
  const n = in_profile_cells(); if (n <= 0) return;
  const cells = in_profile_view();
  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]; }
  if (buy + sell > 0.0) out_buy_share((100.0 * buy) / (buy + sell));
}
```

### Volume by trade size

`trade_volume_by_size` answers "who traded this bar": the same volume
split by how large each trade was. A trade is one fill, bucketed by its
own dollar value, so an order that fills in pieces lands in smaller
buckets: read the large buckets as a floor for what large traders did.
`bucket` runs from 1 to 7 by the USD value of one trade:

| `bucket` | Trade size, USD |
| --- | --- |
| 1 | under 1K |
| 2 | 1K to 10K |
| 3 | 10K to 100K |
| 4 | 100K to 500K |
| 5 | 500K to 1M |
| 6 | 1M to 10M |
| 7 | 10M and up |

`buy_usd` and `sell_usd` are USD notional, `buy_count` and `sell_count`
are trade counts. A bucket that did not trade has no tuple, and a bar
with no trades is an empty block. The share of a bar's volume that
arrived in trades of 100K and up:

```typescript sample=large-order-share
input("close", ohlcv.close);
input("sizes", trade_volume_by_size.cells, { max_cells: 7 }); // seven buckets hold a full bar
output("large_share", line, lower, { unit: "%", description: "Share of the bar's USD volume in trades of 100K and up" });

function onBar(): void {
  const n = in_sizes_cells();
  if (n <= 0) return;
  const cells = in_sizes_view();
  let total = 0.0;
  let large = 0.0;
  // One [bucket, buy_usd, sell_usd, buy_count, sell_count] row per bucket that traded.
  for (let i = 0; i + 4 < n; i += 5) {
    const usd = cells[i + 1] + cells[i + 2];
    total += usd;
    if (cells[i] >= 4.0) large += usd; // bucket 4 starts at 100K
  }
  out_large_share(total > 0.0 ? (100.0 * large) / total : NaN);
}
```

### What the chart does not serve

- **Live individual trades** (the `tape` source): the declaration
  compiles, and **Run** refuses it by name. Per-bar buy and sell volume is
  `trades`; the buy/sell split by price is `volume_profile`; the split by
  trade size is `trade_volume_by_size`.
- **Another indicator's outputs, or a saved series**: the editor has no
  declaration for either; an indicator reads market data only.
- **CME open interest**: no source serves it. Open interest on the venues
  the chart serves is `oi`.

There is no `cvd` source: cumulative volume delta is buy minus sell
`trades` volume, accumulated in the module.

## Options chain

`options_chain` is the celled source behind a gamma map: the chart
market's option chain, one tuple per listed contract, so exposure by
strike is a loop inside the indicator instead of a server aggregate.
Declare the chart's candles first, then the chain input:

```typescript
input("close", ohlcv.close);
input("chain", options_chain.cells, { max_cells: 2000 });
```

Each contract occupies ten f64 values: `[strike, expiry_ms, side, oi,
gamma, delta, mark_iv, underlying, multiplier, vega]`. `expiry_ms` is the
expiry in epoch milliseconds; `side` is `+1` for a call and `-1` for a put;
`oi` is the open interest in the venue's amount units; `gamma` and `delta`
are the venue's served greeks; `mark_iv` is the mark implied volatility;
`underlying` is the chain's underlying price; `multiplier` is the USD
value of one price unit per OI unit (`1` where the OI already carries it,
the CME point value on a CME chain); `vega` is the venue's vega, the price
change per one IV point (`0` where the venue serves none). Gamma exposure
per contract is `gamma * oi * multiplier * underlying * underlying * 0.01`,
summed by strike with the sign of `side`; vega exposure is
`vega * oi * multiplier`, summed the same way.

The chart serves the LATEST chain, not a history of chains, so the block is
present on the newest bar only: every older bar is an empty block
(`in_chain_cells()` reads `0`), and the indicator reads the chain when
`bar.isLast()` is true. The chart refreshes it from the same
30-second snapshot its options panes use. `venue: "auto"` (the default)
reads the chart's own market when it is an options venue (a CME futures
chart reads the CME chain of its root), else the coin's Deribit chain.
Every other word pins one venue's chain: `"deribit"`, `"cme"`,
`"binance"`, `"okx"`, `"bybit"`, `"bullish"` or `"derive"`. A pinned
venue is refused by name on a chart it cannot serve: a coin that venue
does not list, or a venue the chart does not offer you. `expiries` is
`"all"` (the default) or
`"nearest:N"`, the N nearest live expiries. `max_cells` counts contracts:
a chain over the cap refuses the run by name, never truncated, so size it
for the venue (a full BTC chain is about 1,500 contracts; 2000 leaves
headroom). A market pin, an unknown venue word and an expiries selector
outside that grammar are refused by name.

## 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 text renderer fed from a string slot on the
newest bar. The whole file (code-first, so the sheet is derived):

```typescript sample=vp-buy-share-codefirst
input("close", ohlcv.close); // the first input is the bar grid: the chart's own candles
input("profile", volume_profile.cells, { max_cells: 8192 });
output("buy_share", line, lower, { label: "Buy share", format: "%", decimals: 1 });
string("summary", { max_bytes: 64 });
render.text("flow", { y: "buy_share", text: "summary" });

function onBar(): void {
  const n = in_profile_cells(); if (n <= 0) return; // no profile on this bar: nothing is drawn
  const cells = in_profile_view(); // this bar's cells, four numbers each: low, high, buy, sell
  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]; }
  if (buy + sell <= 0.0) return;
  const share = (100.0 * buy) / (buy + sell);
  out_buy_share(share); if (!bar.isLast()) return; // the text rides the newest bar only, so the pane stays readable
  sb_clear(); sb_text("buy "); sb_f64(share, 1); sb_text("%"); str_summary_sb();
}
```

To run it, open the editor, click the **Templates** icon ("Browse starter
templates") in the Explorer, pick **VP Buy Share** under **Order flow**,
and press **Run**. A bar whose profile splits 60/40 to the buy side
computes `buy_share = 60`; only the newest bar also gets its text, such
as `buy 60.0%`, so the pane stays readable. A book-driven variant loops
over `book.cells` the same way.

## How many sources you can open

On the chart a
wrun indicator you add counts against your plan's per-chart indicator limit by
the number of distinct sources it reads, where a source is the source name
plus its pinned market: close, high and volume of the chart's own candles
count once, and a pinned `ohlcv` input on another market counts again.
Inside the indicator a derived value (an average of an input) costs
nothing, since it is your arithmetic, and two inputs with the same source,
market and knobs are two declarations over one feed. The practical cost of
each extra source is fetch time over the loaded window (a coarse interval
pin widens its fetch by two of its candles, and `bars` by as many as it
asks). The one hard ceiling inside
the module is on the other side: it may write at most 256 output slots.

## Availability

The editor accepts any declared source and field the kit knows; whether
the chart can serve it on the market in front of you is answered when you
press **Run**, by name, never with silent empty data. A source the chart
cannot serve there shows a **Could not load** chip on the indicator's
legend with the reason and a **Retry** button, and the editor's Console
shows the same sentence. On a one-second chart, sources the chart does not
serve at one second are declined. Where each part of an indicator runs,
and what an alert can evaluate, is on
[Execution model](execution-model.md).
