# Viewport HUD

> Pin a compact market card to a corner of the price pane that stays in place while you pan and zoom.

Some information belongs on the chart but not on a price level: today's change, current volatility, how far price is from VWAP, where funding stands. Labels attached to a bar scroll away as soon as you pan. A heads-up display (HUD) does not: it is anchored to the viewport, so it stays in the same corner whatever part of the chart you look at.

This script builds a small card with the market, price and daily change, ATR as a percentage of price, distance from the daily VWAP, funding, open-interest change and the active trading session. You choose the corner in the settings.

```flowscope title="viewport-hud.fs"
script "Market HUD"

input (
  where = input.select("top right", title: "Corner", options: ["top left", "top right", "bottom left", "bottom right"])
  oiBars = input.int(24, title: "OI change over (bars)", min: 1, max: 500)
  perDay = input.int(3, title: "Funding periods per day", min: 1, max: 24)
  showSession = input.bool(true, title: "Show session")
)

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

setup (
  corner = match where {
    "top left" => anchor.topLeft
    "bottom left" => anchor.bottomLeft
    "bottom right" => anchor.bottomRight
    _ => anchor.topRight
  }
  asia = time.session("00:00", "08:00", "Asia")
  london = time.session("07:00", "16:00", "London")
  newYork = time.session("13:00", "22:00", "New York")
)

state (
  card = entities.labelPool(max: 1, anchor: corner)
  previousTime: time? = null
)

fn pct(x: float?, digits: int) -> string {
  if x == null {
    return "n/a"
  }
  let sign = x >= 0.0 ? "+" : ""
  return digits == 1 ? str.format("{0}{1:.1}%", sign, x) : str.format("{0}{1:.2}%", sign, x)
}

on chart.update {
  // Indicators run on every update so their history stays complete.
  let t = chart.time ?? time.fromUnixSeconds(0)
  let atr = chart.atr(14)
  let vwap = chart.vwap(reset: time.isNewPeriod(t, previousTime, 1D))
  if chart.isNew {
    previousTime = t
  }

  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 atrPct: float? = c != null && atr != null && c != 0.0 ? atr / c * 100.0 : null
    let vwapPct: float? = c != null && vwap != null && vwap != 0.0 ? (c - vwap) / vwap * 100.0 : null
    let oiNow = oi.close
    let oiThen = oi.close[oiBars]
    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 session = newYork.contains(t) ? "New York" : london.contains(t) ? "London" : asia.contains(t) ? "Asia" : "Off hours"

    let lines = string[](8)
    lines.push(str.format("{0}  {1}  {2}", context.base, context.exchange, context.timeframe))
    lines.push(str.format("Price {0}   {1} today", c ?? 0.0, pct(dayPct, 2)))
    lines.push(str.format("ATR 14  {0:.2}% of price", atrPct ?? 0.0))
    lines.push(str.format("VWAP  {0} away", pct(vwapPct, 2)))
    lines.push(str.format("Funding  {0} p.a.", pct(annual, 1)))
    lines.push(str.format("OI {0} bars  {1}", oiBars, pct(oiPct, 2)))
    if showSession {
      lines.push(str.format("Session  {0}", session))
    }
    let text = lines.reduce("", |acc, line| acc == "" ? line : str.concat(acc, "\n", line))
    card.get("market").set(12.0, 12.0, text, font: font.mono)
  }
}
```

## How it works

### Anchored to the viewport

A drawing pool with an `anchor:` measures its coordinates in pixels from that point of the pane instead of in time and price. `card.get("market").set(12.0, 12.0, text)` places the label 12 pixels inward from the chosen corner, and the anchor also decides which corner of the label sits there. Panning and zooming do not move it. Setting the same key again replaces the text, which is how the card updates live.

### A corner chosen in settings

The anchor constants cannot be offered directly in a dropdown, so the input is a plain text selection. A `match` expression in `setup` converts it into an anchor once, before the pool is made. The `_` arm makes the expression exhaustive and gives a sensible default.

### Indicators outside the live-edge check

The card is only drawn when `chart.isLast` is true, at the live edge. The two indicators are computed before that check, on every update during history and live. If `chart.atr` and `chart.vwap` were only called at the live edge, they would have no history and return `null`. This rule applies to every function that keeps history: call it unconditionally, use the result conditionally.

`chart.vwap(reset:)` restarts when `reset` is true, here on the first bar of each UTC day. The previous bar's time is stored once per bar, when `chart.isNew` says a bar has just opened.

### Building the text

`string[](8)` creates a local array with room for eight lines, and `push` adds each line. `reduce` joins them with line breaks into one multi-line label, set in the monospace font so the columns line up. The `pct` helper formats a signed percentage and returns `"n/a"` when the value is missing, so the card never shows a raw `null`.

`daily.forming.open` is today's open, because the daily subscription's forming bar is the day in progress. `oi.close[oiBars]` reads open interest `oiBars` confirmed periods back. Sessions are UTC windows from `time.session`.

## Adapting it

- **Two cards.** Make a second pool with another anchor, for example an order-flow card with the last bar's liquidations from `stats.sellLiq` and `stats.buyLiq`, or the book imbalance from `data.book`.
- **Warning lines.** Add a line only when something needs attention, such as funding above a threshold or price more than two ATRs from VWAP.
- **Strategy status.** In a strategy script, show `strategy.positionSize`, `strategy.openProfit` and `strategy.netProfit` on the card. See [position and trades](/docs/scripting/strategies/position-and-trades).

## Related

- [Market stats table](/docs/scripting/examples/market-stats-table) shows the same kind of data in a separate window.
- [Inputs and visuals](/docs/scripting/guides/inputs-and-visuals)
- [`entities` reference](/docs/scripting/reference/catalog/entities), [`ta` reference](/docs/scripting/reference/catalog/ta)
