# Funding extremes

> Turn funding into a z-score with extreme bands, tint the chart when positioning is crowded and get alerted when funding stretches.

Funding tells you which side of the perpetual market is paying to stay in its position. Its absolute level is hard to judge: 0.01 % per period is normal on one coin and stretched on another, and what counts as "high" drifts with the market. A z-score fixes that by measuring each reading against its own recent history.

This script plots the funding z-score as columns coloured by severity and draws two pairs of bands. When funding reaches the severe band, the price pane gets a light tint, and alerts tell you when longs or shorts become crowded. The legend shows the current rate annualised.

```flowscope title="funding-extremes.fs"
script "Funding extremes"

input (
  zLen = input.int(270, title: "Z-score length (bars)", min: 20, max: 2000)
  hot = input.float(2.0, title: "Extreme band (z)", min: 0.5, max: 5.0)
  severe = input.float(3.0, title: "Severe band (z)", min: 1.0, max: 8.0)
  perDay = input.int(3, title: "Funding periods per day", min: 1, max: 24, description: "3 for 8-hour funding, 24 for hourly")
)

data stats = subscribe(data.stat)

type Crowding {
  side: string
  rate: float
  z: float
}

pane zPane = pane(title: "Funding z-score", height: 0.25)

plot (
  zCols = plot.histogram(title: "Z-score", on: zPane)
  hotUp = plot.line(title: "Extreme +", color: color.withAlpha(color.red, 120), style: linestyle.dashed, on: zPane)
  hotDown = plot.line(title: "Extreme -", color: color.withAlpha(color.green, 120), style: linestyle.dashed, on: zPane)
  severeUp = plot.line(title: "Severe +", color: color.red, on: zPane)
  severeDown = plot.line(title: "Severe -", color: color.green, on: zPane)
  crowdTint = plot.bg(title: "Severe funding")
)

alert (
  longsCrowded = alert(title: "Funding: longs crowded", onClose: true, payload: Crowding)
  shortsCrowded = alert(title: "Funding: shorts crowded", onClose: true, payload: Crowding)
)

on stats.close {
  let r = stats.fundingRate
  let mean = ta.sma(r, zLen)
  let sd = ta.stdev(r, zLen)
  let z: float? = r != null && mean != null && sd != null && sd > 0.0 ? (r - mean) / sd : null
  let upCross = ta.crossover(z, hot)
  let downCross = ta.crossunder(z, 0.0 - hot)

  hotUp.plot(hot)
  hotDown.plot(0.0 - hot)
  severeUp.plot(severe)
  severeDown.plot(0.0 - severe)

  let shade = match z {
    null => color.gray
    >= severe => color.red
    >= hot => color.withAlpha(color.red, 150)
    <= 0.0 - severe => color.green
    <= 0.0 - hot => color.withAlpha(color.green, 150)
    _ => color.withAlpha(color.gray, 160)
  }
  zCols.plot(z, color: shade)

  if math.abs(z ?? 0.0) >= severe {
    crowdTint.plot(color: color.withAlpha((z ?? 0.0) > 0.0 ? color.red : color.green, 30))
  }

  if r != null {
    let annual = r * float(perDay) * 365.0 * 100.0
    script.setTitle(str.format("Funding extremes  {0:.1}% p.a.  z {1:.2}", annual, z ?? 0.0))
    if z != null && upCross {
      longsCrowded.trigger(Crowding { side: "longs", rate: r, z: z })
    }
    if z != null && downCross {
      shortsCrowded.trigger(Crowding { side: "shorts", rate: r, z: z })
    }
  }
}
```

## How it works

### Funding on a chart

`data.stat` carries `fundingRate`: on past bars the last settled rate, on the newest bar the current one. The script needs no candles, so it subscribes only to statistics. Spot markets have no funding; there every value is `null` and nothing is drawn.

### The z-score

The z-score measures how many standard deviations the current rate is from its mean over `zLen` bars. Because the window is in bars, its span depends on the chart timeframe: 270 bars on a 1h chart are a little over eleven days. The `float?` annotation types the `null` branch for the warm-up bars.

All `ta.*` calls, including the two crossings, run at the top of the handler on every bar. Calling a `ta` function inside an `if` would skip bars and break its history.

### Colour by severity

The `match` expression maps the z-score to a colour. Relational arms are tested in order, so the severe arms come before the extreme arms; the bands are inputs, which arms may compare with. A `null` arm handles the warm-up period, and `_` covers everything in between.

### Annualised rate

Venues settle funding at different intervals: every eight hours on most, every hour on some. The rate is per period, so `perDay` turns it into a yearly percentage for the legend. Set it to the venue's schedule.

### Alerts with a payload

Each alert carries a `Crowding` record with the side, the raw rate and the z-score. `ta.crossover(z, hot)` is true only on the bar where the z-score enters the band, so the alert fires once per excursion instead of on every bar that stays above it. `onClose: true` makes it fire on confirmed bars only.

## Variations

- **Funding versus price.** Combine with [OI regimes](/docs/scripting/examples/oi-regimes): severe positive funding during a "new longs" regime is a crowded trend, during "short covering" it is late chasing.
- **Absolute bands instead of z.** Plot the annualised rate directly and set bands at fixed yields such as 30 % and 60 % per year.
- **Liquidation follow-through.** `stats.sellLiq` on the same subscription shows whether long liquidations spike in the bars after a severe positive reading.

## Related

- [Liquidation magnets](/docs/scripting/examples/liquidation-magnets)
- [Pattern matching](/docs/scripting/guides/pattern-matching), [inputs and visuals](/docs/scripting/guides/inputs-and-visuals)
- [`data` reference](/docs/scripting/reference/catalog/data), [`alert` reference](/docs/scripting/reference/catalog/alert)
