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…

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:

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) sourceIndicator
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_balanceNot 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.

SourceFieldsRequired knobsNotes
ohlcvopen, high, low, close, volumenoneCandles on the selector's (or pinned) market. Close prices are ohlcv + close; there is no "market"/"price" source.
tradesopen, high, low, close, volumeside (BUY or SELL)Side-split per-period trade aggregates.
fundingrate_open, rate_high, rate_low, rate_close, predicted_open, predicted_high, predicted_low, predicted_closenonePerp funding rate, realized and predicted. Units differ by lane (the chart serves rate_close as a percent), so keep thresholds as params.
oiopen, high, low, closenoneOpen interest.
liquidationsliquidationsnone (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_volatilityimplied_volatilitytenorOptions IV at a tenor (ONE_D, THREE_D, ONE_W, ONE_M, TWO_M, THREE_M, SIX_M, ONE_Y).
skewskewtenorOptions skew at the same tenor vocabulary.
token_supplymarketcap, first_marketcap, marketcap_dominance_percent, circulating_supply, total_supply, max_supply, total_value_locked, fully_diluted_valuation, cg_marketcap_rank, total_volume, usd_pricetokenSupply and cap series keyed by token name.
oddsopen, high, low, close (default), volumenonePolymarket 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):

{
  "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:

ClassCell tupleServingKnobs
volume_profile[low, high, buy, sell] per price bucketFETCHES 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 ascendingFETCHES 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 SELLLive daemon buffer (TRADE websocket)min_size REQUIRED; history defaults to "0", other values refused; optional pin pair
trade_volume_by_sizedeclared for cross-host parityREFUSED by name (wrun_cells_unavailable): no point lane on this data plane yetnone

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:

{
  "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.

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):

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:

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:

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.