# Market stats table

> Summarise price, volume, flow, open interest, funding and liquidations for the chart's market in a live table window.

When you trade several coins, you want the essentials of each one in a single glance: how far price has moved today, whether buyers or sellers are in control, whether open interest is building, where funding stands and who has been liquidated. This script collects those numbers and shows them in a table window over the chart, with colour-coded values.

The table has three columns: the measure, its value, and a short reading such as "buyers" or "building". Every number is formatted for reading, not for precision.

```flowscope title="market-stats-table.fs"
script "Market stats"

input (
  lookback = input.int(24, title: "Window (bars)", min: 2, max: 500)
  hotFunding = input.float(30.0, title: "Funding hot above (% p.a.)", min: 1.0, max: 200.0)
  perDay = input.int(3, title: "Funding periods per day", min: 1, max: 24)
)

data (
  chart = subscribe(data.ohlcv)
  daily = subscribe(data.ohlcv, timeframe: 1D)
  oi = subscribe(data.oi)
  stats = subscribe(data.stat)
  book = subscribe(data.book)
)

window dash = window(title: "Market stats") {
  table: table.new(columns: [table.col(title: "Measure", width: 1.4), table.col(title: "Value", align: align.right), table.col(title: "Reading")]),
}

state (
  buys = rolling<float>(lookback)
  sells = rolling<float>(lookback)
  longLiqs = rolling<float>(lookback)
  shortLiqs = rolling<float>(lookback)
)

// "+1.23%" for a value, "n/a" for none.
fn signed(x: float?, suffix: string) -> string {
  if x == null {
    return "n/a"
  }
  return str.format("{0}{1:.2}{2}", x >= 0.0 ? "+" : "", x, suffix)
}

fn tone(x: float?) -> color {
  return (x ?? 0.0) >= 0.0 ? color.green : color.red
}

on chart.close {
  buys.push(chart.buyVolume)
  sells.push(chart.sellVolume)
  longLiqs.push(stats.sellLiq)
  shortLiqs.push(stats.buyLiq)
}

on chart.update {
  if chart.isLast {
    let c = chart.close
    let dayOpen = daily.forming.open
    let dayPct: float? = c != null && dayOpen != null && dayOpen != 0.0 ? (c - dayOpen) / dayOpen * 100.0 : null

    let b = buys.sum()
    let s = sells.sum()
    let buyShare = b + s > 0.0 ? b / (b + s) * 100.0 : 50.0

    let oiNow = oi.close
    let oiThen = oi.close[lookback]
    let oiPct: float? = oiNow != null && oiThen != null && oiThen != 0.0 ? (oiNow - oiThen) / oiThen * 100.0 : null

    let rate = stats.forming.fundingRate
    let annual: float? = rate != null ? rate * float(perDay) * 365.0 * 100.0 : null
    let hot = math.abs(annual ?? 0.0) >= hotFunding

    let ll = longLiqs.sum()
    let sl = shortLiqs.sum()
    let pressure = book.imbalance(0.5)

    let t = dash.table
    t.row("price", str.format("{0} on {1}", context.base, context.exchange), str.format("{0}", c ?? 0.0), table.cell(signed(dayPct, "% today"), fg: tone(dayPct)))
    t.row("flow", str.format("Buy share, {0} bars", lookback), str.format("{0:.1}%", buyShare), table.cell(buyShare >= 50.0 ? "buyers" : "sellers", fg: buyShare >= 50.0 ? color.green : color.red))
    t.row("oi", str.format("OI change, {0} bars", lookback), table.cell(signed(oiPct, "%"), fg: tone(oiPct)), (oiPct ?? 0.0) >= 0.0 ? "building" : "unwinding")
    t.row("funding", "Funding", table.cell(signed(annual, "% p.a."), fg: tone(annual)), table.cell(hot ? "crowded" : "normal", bg: hot ? color.withAlpha(color.amber, 60) : color.transparent))
    t.row("liqs", str.format("Liquidations, {0} bars", lookback), str.format("L {0:,.0} / S {1:,.0}", ll, sl), table.cell(ll >= sl ? "longs flushed" : "shorts squeezed", fg: ll >= sl ? color.red : color.green))
    t.row("book", "Book within 0.5%", table.cell(signed(pressure == null ? null : (pressure ?? 0.0) * 100.0, "%"), fg: tone(pressure)), (pressure ?? 0.0) >= 0.0 ? "bids heavier" : "asks heavier")
  }
}
```

## How it works

### A window with a table

A `window` declares a floating window with named panels. Here it holds a single table made with `table.new` and three columns from `table.col`. The panel is addressed as `dash.table`, and the handler keeps a short local alias `t`.

`row(key, cells...)` writes a whole row, one cell per column. A row with the same key is replaced, so each update rewrites the six rows in place. A cell is a plain value or `table.cell(value, fg:, bg:)` for colours.

### Collect on close, render at the live edge

Rolling sums of buy and sell volume and of liquidations are collected in `on chart.close`, one value per confirmed bar, in `rolling<float>` collections sized by the `lookback` input. The table itself is only useful for the present, so the update handler renders it only when `chart.isLast` is true. During history nothing is rendered, and at the live edge the table refreshes with every update.

### Reading other subscriptions

All other sources are read from the chart's update handler. Each one gives its last confirmed period, except where the script explicitly asks for the developing one: `daily.forming.open` is today's open, while `daily.open` would be yesterday's, and `stats.forming.fundingRate` the current rate. `oi.close[lookback]` reaches back `lookback` periods on the OI subscription. See [data and update events](/docs/scripting/guides/data-and-events) for the full rules.

`book.imbalance(0.5)` compares the resting value on both sides within 0.5 % of the mid price, from the book the chart has recorded. Before the recording reaches back far enough it is `null`, and the row reads "n/a".

### Formatting helpers

Two small functions keep the cells consistent. `signed` adds a plus sign to positive numbers and returns `"n/a"` for missing values, so the table never shows a raw `null`. `tone` picks a colour by sign. `{0:,.0}` in a format string writes a number with thousands separators and no decimals.

## Adapting it

- **Profile rows.** Add `profile = subscribe(data.volume)` and show the point of control of the last `lookback` bars from `profile.profile(lookback).poc()`.
- **Large-print share.** Sum the notional of prints above a floor from a trades subscription, as in [large prints](/docs/scripting/examples/large-prints), and show its share of the volume.
- **Heat colours.** Replace `tone` with `color.gradient` so the colour intensity follows the size of the move.

## Related

- [MTF dashboard](/docs/scripting/examples/mtf-dashboard) uses the same table panel for several timeframes.
- [Viewport HUD](/docs/scripting/examples/viewport-hud) shows a smaller summary directly on the chart.
- [Custom axes and charts](/docs/scripting/guides/custom-axes-and-charts#tables), [`surfaces` reference](/docs/scripting/reference/catalog/surfaces)
