---
title: Data Sources
description: The market feeds kScript can subscribe to, their column members, and how to load them with source() and ohlcv().
verified:
  engine: 3.0.67
  probes:
    - scripts/probes/data-sources/ohlcv_direct.ks
    - scripts/probes/data-sources/group_market_flows.ks
    - scripts/probes/data-sources/group_derivatives_volatility.ks
    - scripts/probes/data-sources/group_etf_options.ks
    - scripts/probes/data-sources/group_protocol_positions.ks
    - scripts/probes/data-sources/source_budget_boundary.ks
---

<div class="flex gap-3 mb-6">
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 text-blue-600 text-sm font-medium">
    Core Concept
  </span>
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 text-sm font-medium">
    6 min read
  </span>
</div>

## What this is

A kScript script reads market data through **sources**. A source is one feed (price candles, funding, order book, options volume, ETF flows) for one instrument on one venue. You subscribe to it with `source(...)` or, for plain price candles, the `ohlcv(...)` shorthand. Each source returns a `timeseries` whose **members** are the columns of that feed: `.close`, `.buy`, `.value`, and so on.

This page is the reference for every source type the platform serves, the members each one exposes, and the two rules that trip people up: how you pass the symbol and exchange, and how many sources one script may open.

## Loading a source

Two calls do the job. Use `ohlcv(...)` for price candles, and `source("<type>", ...)` for everything else:

```javascript
//@version=2
define(title="Close and funding", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries funding = source("funding_rate", symbol=currentSymbol, exchange=currentExchange)

plotLine(value=trade.close, colors=["#2563eb"], label=["Close"], desc=["chart close price"])
plotLine(value=funding.value, colors=["#7c3aed"], label=["Funding"], desc=["funding rate"])
```

The first positional argument to `source()` is the source **type** (a string from the table below). The rest are keyword arguments that identify the instrument. Which keywords a source needs depends on the type: most price-like feeds take `symbol` and `exchange`, the Deribit and options feeds take `coin`, and ETF feeds take `symbol` only. The table lists the required keys per source.
### Symbol and exchange must be env identifiers, not script variables

This is the most common mistake. The arguments to `source()` are resolved at subscription time, before the bar loop runs, so they cannot reference a variable you computed in the script. Pass one of:

- a **literal**: `source("ohlcv", symbol="BTCUSDT", exchange="BINANCE")`
- an **env identifier**: `currentSymbol`, `currentCoin`, `currentExchange` (the chart's current context)
- an **input**: a value from `input(...)`
- an **inline env expression** built from the above

```javascript
// Works: env identifiers
timeseries a = source("ohlcv", symbol=currentSymbol, exchange=currentExchange)

// Fails: a script variable is not allowed here
// var wantedSymbol = "ETHUSDT"
// timeseries b = source("ohlcv", symbol=wantedSymbol, exchange=currentExchange)
```
Reaching for a script variable raises a build error that names the offending identifier and tells you to use a literal, an env identifier, an input, or an inline env expression instead. If it slips past the build, the run reports the identifier as undefined.

## Source reference

The platform serves **20 source types**. Each row lists the keyword arguments `source()` requires and the members you can read off the returned timeseries.

| Source | Required `source()` args | Members |
| --- | --- | --- |
| `ohlcv` | `symbol`, `exchange` | `time`, `open`, `high`, `low`, `close`, `volume` |
| `open_interest` | `symbol`, `exchange` | `time`, `open`, `high`, `low`, `close` |
| `buy_sell_volume` | `symbol`, `exchange`, `currency?` | `time`, `buy`, `sell` |
| `trade_volume_by_size` | `symbol`, `exchange` | `time`, `cells` (array-celled) |
| `funding_rate` | `symbol`, `exchange` | `time`, `value` |
| `liquidations` | `symbol`, `exchange` | `time`, `buy`, `sell` |
| `orderbook` | `symbol`, `exchange` | `time`, `bids`, `asks` (array-celled) |
| `cme_oi` | `coin` | `time`, `open`, `high`, `low`, `close` |
| `deribit_implied_volatility` | `coin` | `time`, `one_week`, `one_month`, `three_months` |
| `deribit_volatility_index` | `coin` | `time`, `open`, `high`, `low`, `close` |
| `skew` | `coin`, `delta` | `time`, `one_week`, `onemonth`, `three_months` |
| `etf_premium_rate` | `symbol` | `time`, `value` |
| `etf_holding` | `symbol` | `time`, `value` |
| `options_volume` | `coin`, `exchange` | `time`, `puts`, `calls` |
| `options_open_interest` | `coin`, `exchange` | `time`, `puts`, `calls` |
| `etf_flow` | `symbol` | `time`, `value` |
| `ethena_positions` | `coin` | `time`, `value` |
| `long_short_ratio` | `symbol`, `exchange` | `total_account`, `top_trader_account`, `top_trader_position` |
| `binance_treasury_balance` | `asset` | `time`, `value` |

### `buy_sell_volume` in quote notional

`buy_sell_volume(symbol, exchange, currency="USD")` returns buy/sell volume in USD notional instead of base-asset quantity. Omit `currency` (or pass anything else) for the base-asset default. USD-normalized series refresh their live edge on the periodic tail poll rather than the per-tick merge, so the newest bar updates on a short delay.

```javascript
//@version=2
define(title="USD Notional CVD", position="offchart", axis=true);

timeseries ohlcvData = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries bsv = buy_sell_volume(symbol=currentSymbol, exchange=currentExchange, currency="USD");

static cvd = 0
cvd = cvd + bsv.buy[0] - bsv.sell[0]
plotLine(value=cvd, colors=["#42a5f5"], width=2, label=["CVD (USD)"], desc=["Cumulative volume delta in quote notional"]);
```

## Tape by order size (array-celled)

`trade_volume_by_size` exposes the tape below the bar, pre-bucketed by USD order size. It is **array-celled**: each bar's row carries a list of cells you read with `.cells`, and it charges **weight 2** against the per-script source budget.

**`trade_volume_by_size(symbol, exchange)`** delivers one cell per populated bucket:

```
[bucketId, buyVolUsd, sellVolUsd, buyCount, sellCount]
```

Bucket ids are fixed USD notional bands: 1 = $0-1K, 2 = $1K-10K, 3 = $10K-100K, 4 = $100K-500K, 5 = $500K-1M, 6 = $1M-10M, 7 = $10M+. This is how you get real whale flow (filter `c[0] >= 5`) and per-bar trade counts without touching the raw tape.

Working with cells:

```javascript
//@version=2
define(title="HTF Whale Flow", position="offchart", axis=true);

timeseries ohlcvData = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries tv = trade_volume_by_size(symbol=currentSymbol, exchange=currentExchange);
timeseries h4 = htf(tv, "4h");

var whaleNet = h4.cells.filter((c) => c[0] >= 5).map((c) => c[1] - c[2]).reduce((a, b) => a + b, 0);
plotLine(value=whaleNet, colors=["#8d6e63"], width=1, label=["4h Whale Net"], desc=["Net 500K+ USD flow in the last completed 4h bucket"]);
```

`.cells` returns the current bar's cell list (`[]` on a quiet bar, never an error), and the usual `.map`/`.filter`/`.reduce` lambdas do the math. `htf()` concatenates cells across the higher-timeframe bucket, which makes it the multi-timeframe path for tape data; `requestBars()` and `ltf()` reject array-celled sources.

**Data depth differs by layer, by design.** `trade_volume_by_size` reaches back about a week at 1m resolution and months at 1h/1d. `buy_sell_volume` carries years of bar-exact history. So cumulative delta over long ranges belongs to `buy_sell_volume`, and whale flow over recent months belongs to the bucket source. See the [Whale vs Retail CVD cookbook](/kscript/cookbook/tape-reader) for a full worked example.


Two members are easy to get wrong:

- **`long_short_ratio` has no `time` member.** It is the one source here that does not carry a timestamp column; read only the three account/position members.
- **`skew` spells it `onemonth`,** not `one_month`. Its other two tenors use underscores (`one_week`, `three_months`), so the inconsistency catches people. The Deribit implied-volatility source uses `one_month` with the underscore.

Reading a member that doesn't exist on a source won't crash the compiler, but it produces nothing to plot: the series is all `na` and the line never draws. If a plot is blank, check the member name against this table first.

## Direct OHLCV

`ohlcv(...)` is the dedicated shorthand for the price-candle source. It returns the same shape as `source("ohlcv", ...)`: a timeseries with `open`, `high`, `low`, `close`, `volume`. Use it for the chart's own price, or name another symbol to pull a second instrument's candles.

```javascript title="Direct OHLCV" lines wrap
//@version=2
define(title="Direct OHLCV", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
plotLine(value=trade.close, colors=["#2563eb"], label=["Close"], desc=["direct ohlcv close"])
```

## Reading members: worked examples

The four examples below open the full source catalog grouped by theme. Each reads real members off the returned timeseries and plots them, so you can see exactly which column lives where.

### Market flows

Price, open interest, buy/sell volume, funding, and liquidations. Notice the idiomatic combinations: `buy - sell` gives net taker flow, and the same shape gives net liquidations.

```javascript title="Market flow sources" lines wrap
//@version=2
define(title="Market Flow Sources", position="offchart", axis=true)

timeseries trade = source("ohlcv", symbol=currentSymbol, exchange=currentExchange)
var tradeMembers = trade.open + trade.high + trade.low + trade.close + trade.volume
plotLine(value=tradeMembers, colors=["#2563eb"], label=["ohlcv"], desc=["ohlcv open high low close volume"])

timeseries openInterest = source("open_interest", symbol=currentSymbol, exchange=currentExchange)
var openInterestMembers = openInterest.open + openInterest.high + openInterest.low + openInterest.close
plotLine(value=openInterestMembers, colors=["#059669"], label=["open_interest"], desc=["open_interest open high low close"])

timeseries buySellVolume = source("buy_sell_volume", symbol=currentSymbol, exchange=currentExchange)
var buySellMembers = buySellVolume.buy - buySellVolume.sell
plotLine(value=buySellMembers, colors=["#dc2626"], label=["buy_sell_volume"], desc=["buy_sell_volume buy sell"])

timeseries funding = source("funding_rate", symbol=currentSymbol, exchange=currentExchange)
plotLine(value=funding.value, colors=["#7c3aed"], label=["funding_rate"], desc=["funding_rate value"])

timeseries liq = source("liquidations", symbol=currentSymbol, exchange=currentExchange)
var liquidationMembers = liq.buy - liq.sell
plotLine(value=liquidationMembers, colors=["#ea580c"], label=["liquidations"], desc=["liquidations buy sell"])
```

### Derivatives and volatility

The order book, CME open interest, and the Deribit volatility feeds. `orderbook` is special: its `bids` and `asks` members are **array-celled**, meaning each bar holds a list of price levels rather than a single number, so you render them through `plotTable` instead of `plotLine`. The volatility sources are ordinary OHLC- or tenor-shaped series.

```javascript title="Derivatives and volatility sources" lines wrap
//@version=2
define(title="Derivatives Volatility Sources", position="offchart", axis=true)

timeseries book = source("orderbook", symbol=currentSymbol, exchange=currentExchange)
var bidCells = book.bids
var askCells = book.asks
plotTable(data=[["side", "cells"], ["bids", bidCells], ["asks", askCells]], position="top_right")

timeseries cme = source("cme_oi", coin=currentCoin)
var cmeMembers = cme.open + cme.high + cme.low + cme.close
plotLine(value=cmeMembers, colors=["#4f46e5"], label=["cme_oi"], desc=["cme_oi open high low close"])

timeseries impliedVol = source("deribit_implied_volatility", coin=currentCoin)
var impliedVolMembers = impliedVol.one_week + impliedVol.one_month + impliedVol.three_months
plotLine(value=impliedVolMembers, colors=["#0f766e"], label=["deribit_implied_volatility"], desc=["deribit implied volatility tenors"])

timeseries dvol = source("deribit_volatility_index", coin=currentCoin)
var dvolMembers = dvol.open + dvol.high + dvol.low + dvol.close
plotLine(value=dvolMembers, colors=["#9333ea"], label=["deribit_volatility_index"], desc=["deribit volatility index open high low close"])

timeseries skewData = source("skew", coin=currentCoin, delta=25)
var skewMembers = skewData.one_week + skewData.onemonth + skewData.three_months
plotLine(value=skewMembers, colors=["#be123c"], label=["skew"], desc=["skew one_week onemonth three_months"])
```

### ETF and options

ETF premium, holdings, and flow are single-`value` feeds keyed by `symbol`. The options feeds split into `puts` and `calls`, keyed by `coin` and `exchange`. Note `etf_holding` also accepts an optional `currency`.

```javascript title="ETF and options sources" lines wrap
//@version=2
define(title="ETF Options Sources", position="offchart", axis=true)

timeseries premium = source("etf_premium_rate", symbol="BTC")
plotLine(value=premium.value, colors=["#65a30d"], label=["etf_premium_rate"], desc=["etf_premium_rate value"])

timeseries holding = source("etf_holding", symbol="BTC", currency="USD")
plotLine(value=holding.value, colors=["#0d9488"], label=["etf_holding"], desc=["etf_holding value"])

timeseries optionVolume = source("options_volume", coin=currentCoin, exchange=currentExchange)
var optionVolumeMembers = optionVolume.puts + optionVolume.calls
plotLine(value=optionVolumeMembers, colors=["#a16207"], label=["options_volume"], desc=["options_volume puts calls"])

timeseries optionInterest = source("options_open_interest", coin=currentCoin, exchange=currentExchange)
var optionInterestMembers = optionInterest.puts + optionInterest.calls
plotLine(value=optionInterestMembers, colors=["#c2410c"], label=["options_open_interest"], desc=["options_open_interest puts calls"])

timeseries etfFlow = source("etf_flow", symbol="BTC")
plotLine(value=etfFlow.value, colors=["#1d4ed8"], label=["etf_flow"], desc=["etf_flow value"])
```

### Protocol and positioning

Ethena positions, the long/short ratio, and Binance treasury balance. `long_short_ratio` is the source with no `time` member, so read only its three account/position columns.

```javascript title="Protocol and positioning sources" lines wrap
//@version=2
define(title="Protocol Position Sources", position="offchart", axis=true)

timeseries ethena = source("ethena_positions", coin=currentCoin)
plotLine(value=ethena.value, colors=["#047857"], label=["ethena_positions"], desc=["ethena_positions value"])

timeseries longShort = source("long_short_ratio", symbol=currentSymbol, exchange=currentExchange)
var longShortMembers = longShort.total_account + longShort.top_trader_account + longShort.top_trader_position
plotLine(value=longShortMembers, colors=["#9d174d"], label=["long_short_ratio"], desc=["long_short_ratio account and position members"])

timeseries treasury = source("binance_treasury_balance", asset="BTC")
plotLine(value=treasury.value, colors=["#374151"], label=["binance_treasury_balance"], desc=["binance_treasury_balance value"])
```

## Array-celled sources

`orderbook` above is one example of an array-celled source: each bar carries a list of cells, not a scalar. The other is [`volume_profile`](../functions/volume-profile.md), whose every bar is a row of price-level buckets shaped `[priceLow, priceHigh, buyVol, sellVol]`, with the bucket count changing bar to bar. Because there is no fixed column, `volume_profile` has no scalar members like `.buy`. You pull values out of it with the dedicated [volume-profile accessors](../functions/volume-profile.md) (`vpBuy`, `vpDelta`, `vpPoc`, and friends), and you can walk the raw buckets yourself with array indexing.

## How many sources you can open

Each distinct source is a live data subscription, so kScript budgets them. The budget is **10 weighted source slots** per script (`MAX_SOURCES_PER_SCRIPT`): a native stream like `ohlcv` costs one slot, heavier source types can be configured to cost more, and the chart's own price spine can cost zero. Ten single-slot sources fill the budget exactly.

Two things do not count against the budget:

- **Identical calls dedupe.** Subscribing to the same `(type, symbol, exchange)` twice is one source, so you can reference it freely.
- **Derived series are free.** `htf(...)`, indicators, and other transforms of a source you already opened fetch nothing new.

Treat the 10 weighted slots as the contract and design multi-venue aggregations within it. For those patterns, see [Multi-Source & Aggregation](multi-source.md).
