# Data and update events

> Subscribe to market data, react to open, update, close and trade events, and read earlier values.

A subscription names a market-data series the script reads. For a chart indicator,
start with the chart's candles:

```flowscope
script "Chart data"

data chart = subscribe(data.ohlcv)
```

`chart` is a name you choose, not a built-in global. It exposes candle fields such
as `open`, `high`, `low`, `close` and `volume`. These can be missing, so their types
are nullable (`float?`).

## Sources

| Source | Record | What it holds |
|---|---|---|
| `data.ohlcv` | `OhlcvSub` | Candles: `open high low close volume buyVolume sellVolume buyCount sellCount trades`. |
| `data.vd` | `VdSub` | Volume delta per bar as `open high low close`. |
| `data.cvd` | `CvdSub` | Cumulative volume delta as `open high low close`. |
| `data.oi` | `OiSub` | Open interest as `open high low close`. |
| `data.stat` | `StatSub` | Market statistics: `sellLiq buyLiq markPrice fundingRate`. |
| `data.trades` | `TradesSub` | Individual trades, delivered to an `on t.trade(tr)` handler. |
| `data.book` | `BookSub` | Order-book snapshots, read from another subscription's handler. |
| `data.volume` | `VolumeSub` | Volume-profile snapshots, read from another subscription's handler. |

### What a chart provides

On a chart, subscriptions serve the chart's own market:

- **Candles, delta, CVD and open interest** at the chart's interval or a whole
  multiple of it (a 15m chart serves `timeframe: 1h`, not `5m`).
- **Statistics** from the market's liquidations and funding: `sellLiq` and `buyLiq`
  are the value of liquidation orders in the bar (a sell liquidates a long),
  `fundingRate` the last settled rate (the current rate on the newest bar).
  `markPrice` is not available. Spot markets have no statistics.
- **Trades** from the trades the chart holds in memory.
- **Order books** from the book the chart has recorded (what its liquidity heatmap
  shows): a snapshot is the book resting at the end of a period. Before the
  recording starts, reads return `null`.
- **Volume profiles** from the footprints of the chart's bars: a snapshot sums the
  volume traded in a period by footprint row. While a script reads a profile, the
  chart loads footprints for the bars on screen; bars without one read `null`.

Volume delta needs a volume split: the data hub's candles carry one, being built
from recorded trades, and so does the live tape.

### Other exchanges and symbols

Candles, delta and CVD can come from other markets than the chart's. `exchange:`
takes one exchange id or several joined with `markets.agg(...)`; their bought and
sold volume are added up, and the prices are those of the first exchange that
traded in the bar. Without `symbol:` each exchange's market of the chart's coin is
read (`BTCUSDT` on Binance, `BTC-USDT-SWAP` on OKX, `BTC` on Hyperliquid), so one
script works on any chart:

```flowscope
script "Spot CVD"

setup spot = markets.agg("binance", "bybit", "okx", "coinbase", "kraken")

data (
  chart = subscribe(data.ohlcv)
  spotCvd = subscribe(data.cvd, exchange: spot)
)

pane cvdPane = pane(title: "Spot CVD", height: 0.25)
plot line = plot.line(title: "Spot CVD", on: cvdPane)

on chart.update {
  line.plot(spotCvd.close)
}
```

The bars are the chart's bars: a bar no exchange of the subscription traded in
reads `null`. The other markets load after the script starts, so the first values
appear a moment later. Open interest, statistics, trades, order books and volume
profiles of other markets are not available: they read `null`.
`markets.exchangeNames("spot")` and `markets.exchangeNames("futures")` list the
exchange ids.

## Respond to updates

Put calculations in a handler of the subscription that drives them:

```flowscope
script "Candle range"

data chart = subscribe(data.ohlcv)

plot range = plot.histogram(title: "Candle range")

on chart.update {
  let candleRange = (chart.high ?? 0.0) - (chart.low ?? 0.0)
  range.plot(candleRange)
}
```

## Several handlers

Each subscription reacts to its own events, so different rates stay in separate,
focused blocks. This script saves levels when an hourly candle closes and plots
them as the chart advances:

```flowscope
script "Previous hour high and low"

data (
  chart = subscribe(data.ohlcv)
  h1 = subscribe(data.ohlcv, timeframe: 1h)
)

state (
  prevHigh: float? = null
  prevLow: float? = null
)

plot (
  highPlot = plot.line(title: "Previous hour high", color: color.red, style: linestyle.step)
  lowPlot = plot.line(title: "Previous hour low", color: color.green, style: linestyle.step)
)

on h1.close {
  prevHigh = h1.high
  prevLow = h1.low
}

on chart.update {
  highPlot.plot(prevHigh)
  lowPlot.plot(prevLow)
}
```

- `on h1.close` runs when an hourly candle is confirmed and saves values that do
  not change afterwards.
- `on chart.update` runs as the chart advances and draws the saved levels.

`state` connects the handlers: what one writes, later calls of the other read.

Period subscriptions support `open`, `update` and `close` handlers. A trades
subscription uses a `trade` handler for each print. There is at most one handler per
subscription and event.

## Statistics: liquidations and funding

```flowscope
script "Liquidations"

data (
  chart = subscribe(data.ohlcv)
  stats = subscribe(data.stat)
)

pane liqPane = pane(title: "Liquidations (USD)", height: 0.25)

plot (
  longs = plot.histogram(title: "Longs liquidated", color: color.red, on: liqPane)
  shorts = plot.histogram(title: "Shorts liquidated", color: color.green, on: liqPane)
)

on stats.close {
  longs.plot(stats.sellLiq)
  shorts.plot(stats.buyLiq == null ? null : 0.0 - (stats.buyLiq ?? 0.0))
}
```

Funding as a line in basis points:

```flowscope
script "Funding (bps)"

data stats = subscribe(data.stat)

pane fundingPane = pane(title: "Funding", height: 0.2)

plot funding = plot.line(title: "Funding (bps)", color: color.amber, on: fundingPane)

on stats.close {
  let rate = stats.fundingRate
  funding.plot(rate == null ? null : (rate ?? 0.0) * 10000.0)
}
```

## Trades

A trades subscription runs its `trade` handler once for each print, oldest first.
`minSize:` keeps only prints of at least that size. Each `tr` is a `Trade` with
`time`, `price`, `size` and `isBuy`.

```flowscope
script "Large buys"

input minSize = input.float(5.0, title: "Smallest trade (base units)", min: 0.0)

data (
  chart = subscribe(data.ohlcv)
  tape = subscribe(data.trades, minSize: minSize)
)

state bought = 0.0

pane buysPane = pane(title: "Large buy volume", height: 0.2)
plot buys = plot.histogram(title: "Large buys", color: color.green, on: buysPane)

on chart.open {
  bought = 0.0
}

on tape.trade(tr) {
  if tr.isBuy {
    bought += tr.size
  }
}

on chart.update {
  buys.plot(bought)
}
```

On a chart, trades reach back as far as the chart keeps them in memory, so older
bars have no trades.

## Order books and volume profiles

Book and profile subscriptions have no handlers of their own: read them from another
subscription's handler. Each read gets the snapshot of the last period completed at
that moment, so in `on chart.close` it describes the bar that just closed; `barsAgo:`
reaches further back.

```flowscope
script "Book imbalance and bar POC"

data (
  chart = subscribe(data.ohlcv)
  book = subscribe(data.book)
  profile = subscribe(data.volume)
)

pane imbalancePane = pane(title: "Book imbalance within 1%", height: 0.2)

plot (
  imbalance = plot.histogram(title: "Imbalance", on: imbalancePane)
  poc = plot.marker(title: "Bar POC", shape: shape.diamond, color: color.amber, size: 4.0)
)

on chart.close {
  // (bids - asks) / (bids + asks) by value, from -1 to 1.
  let ratio = book.imbalance(1.0)
  imbalance.plot(ratio, color: (ratio ?? 0.0) >= 0.0 ? color.green : color.red)
  let level = profile.poc()
  if level != null {
    poc.plot(level.price)
  }
}
```

`profile.profile(n)` merges the last `n` periods into one profile with the same
queries (`poc`, `valueArea`, `buckets`, `summary`).

## Read earlier values

Brackets read history on period fields:

```flowscope
script "Previous close"

data chart = subscribe(data.ohlcv)

plot previous = plot.line(title: "Previous close")

on chart.update {
  previous.plot(chart.close[1])
}
```

Earlier values can be `null`, especially near the start of the chart. Keep that
absence where it matters instead of inventing a value.

For live candles and close-confirmed signals, continue with
[repainting and confirmed values](/docs/scripting/guides/repainting).
