---
title: "Estimated dealer positioning"
description: "Where option dealers in the chart's underlying are likely positioned, and what their hedging does next, every ten minutes, as an estimate. Dealers' positions…"
order: 124
section: "cookbook"
---

<!-- source: docs/indicators/cookbook/dealer-positioning.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Estimated dealer positioning

Where option dealers in the chart's underlying are likely positioned, and what their hedging does next, every ten minutes, as an estimate. Dealers' positions are not observed. The book is the open-interest model of the 10-minute options statistics (dealers long the calls and short the puts: net gamma in USD per 1% move, charm in dollar delta per day, vanna in dollar delta per volatility point, the walls, the flip and the magnet). Today's flow assumes dealers took the other side of every print at the ask or at the bid: the share delta bought at the ask minus the share delta sold at the bid, summed since the session's first bar in view, from the option flow on the chart's underlying.

From those the card reads the gamma regime and the hedge it implies (long gamma sells a rally and buys a drop, short gamma chases both), the hedge per 1% move, the charm drift to the 16:00 New York close, the hedge a one-point IV drop calls for through vanna, the dealer delta today's flow left behind, and net gamma against 10, 20 and 30 minutes ago with its percentile among the window's samples. A pane under the chart draws net gamma as a step line, green when dealers are long gamma and red when short, a second pane the dealer delta from today's flow, and the walls, the flip and the magnet ride the price.

The parts are scalar `option_stats_10m` inputs and `options_flow` inputs on the chart's underlying ([Data sources](../core-concepts/data-sources.md#us-listed-options-by-the-charts-underlying)), the options kit's `DayHistory` keyed by each sample's 10-minute slot ([Options kit](../functions/options-kit.md#a-history-of-daily-statistics)), two named panes ([Panes](../presentation/plotting.md#panes)), and a `render.hud` card. This is also the `dealer-positioning` template: the **Estimated Dealer Positioning** card under **HUDs** in the editor's starter list, and it compiles as written.

## The wrun indicator

```typescript sample=dealer-positioning
// Estimated Dealer Positioning: where option dealers in the chart's underlying are likely positioned and what their
// hedging does next, every ten minutes, as an estimate. Dealers' positions are not observed: the book is the
// open-interest model of the 10-minute options statistics (option_stats_10m: dealers long the calls and short the puts,
// net gamma in USD per 1% move, charm in dollar delta per day, vanna in dollar delta per volatility point, the walls,
// the flip and the magnet), and today's flow assumes dealers took the other side of every print at the ask or at the
// bid (options_flow on the chart's underlying: the share delta bought at the ask minus the share delta sold at the bid,
// summed since the session's first bar in view). From those the card reads: the gamma regime and the hedge it implies
// (long gamma sells a rally and buys a drop, short gamma chases both), the hedge per 1% move, the charm drift to the
// 16:00 New York close, the hedge a one-point IV drop calls for through vanna, the dealer delta today's flow left
// behind, and net gamma against 10, 20 and 30 minutes ago and its percentile among the window's samples. A pane under
// the chart draws net gamma as a step line, green when dealers are long gamma and red when short, and a second pane the
// dealer delta from today's flow; the walls, the flip and the magnet ride the price. The history is short today (the
// 10-minute statistics start when the data plane switches the job on): the percentile needs Minimum history (40
// samples, about a session), and until then the card says how many there are. Charts of 10 minutes or finer on the
// underlyings the data plane samples every ten minutes; any other underlying is refused by name.

param.int("lookback", 400, { min: 40, max: 2000, label: "Window, samples", description: "10-minute samples the percentile reads: 400 is about ten sessions" });
param.int("min_samples", 40, { min: 10, max: 2000, label: "Minimum history, samples", description: "Samples before the percentile shows" });
param.bool("show_levels", true, { label: "Levels", description: "Draw the call wall, the put wall, the gamma flip and the magnet on price" });
input("close", ohlcv.close); // the chart's own close: the grid
input("gex", option_stats_10m.net_gex, { missing: "nan", description: "Net gamma exposure, USD per 1% move, every ten minutes (the 10-minute options statistics)" }); // lands on the bar holding the sample, NaN elsewhere
input("charm", option_stats_10m.net_charm, { missing: "nan", description: "Net charm, dollar delta per calendar day" });
input("vanna", option_stats_10m.net_vanna, { missing: "nan", description: "Net vanna, dollar delta per volatility point" });
input("call_wall", option_stats_10m.call_wall, { missing: "nan", description: "The call wall, a price" });
input("put_wall", option_stats_10m.put_wall, { missing: "nan", description: "The put wall, a price" });
input("flip", option_stats_10m.gamma_flip, { missing: "nan", description: "The gamma flip, a price" });
input("magnet", option_stats_10m.gamma_magnet, { missing: "nan", description: "The gamma magnet, a price" });
input("spot", option_stats_10m.underlying_price, { missing: "nan", description: "The underlying's price at the sample" });
input("delta_ask", options_flow.share_delta_ask, { missing: "zero", description: "Share delta of the bar's prints at the ask: customers buying" }); // the chart's underlying (scope ticker); a quiet bar reads 0
input("delta_bid", options_flow.share_delta_bid, { missing: "zero", description: "Share delta of the bar's prints at the bid: customers selling" });
pane("gex", { title: "Dealer gamma, USD per 1% move", height_frac: 0.18, format: "usd" });
pane("flow", { title: "Dealer delta from today's flow, USD", height_frac: 0.14, format: "usd" });
output("call_wall_line", line, overlay, { color: "#22c55e", width: 1, step: true, line_style: "dashed", label: "Call wall", format: "price", description: "The call wall, held until the next sample" });
output("put_wall_line", line, overlay, { color: "#ef4444", width: 1, step: true, line_style: "dashed", label: "Put wall", format: "price", description: "The put wall" });
output("flip_line", line, overlay, { color: "#f59e0b", width: 1, step: true, label: "Gamma flip", format: "price", description: "The gamma flip" });
output("magnet_line", line, overlay, { color: "#a78bfa", width: 1, step: true, line_style: "dotted", label: "Magnet", format: "price", description: "The gamma magnet" });
output("net_gex", line, lower, { pane: "gex", width: 2, step: true, color_by: "gamma_code", colors: ["#ef4444", "#22c55e"], label: "Net gamma", format: "usd", description: "Net dealer gamma on the open-interest model, USD per 1% move, every ten minutes" });
output("flow_delta", area, lower, { pane: "flow", color: "#38bdf8", fill_color: "#38bdf8", fill_opacity: 0.25, label: "Flow delta", format: "usd", description: "Dealer delta from today's flow: minus the customers' net share delta (at the ask minus at the bid) since the session's first bar in view, times the price, USD" });
output("gamma_code", none, lower, { description: "0 dealers short gamma, 1 long gamma (net at or above zero)" });
output("hedge_1pct", none, lower, { format: "usd", description: "What dealers trade for a 1% rally: minus net gamma, USD (negative sells, positive buys)" });
output("charm_to_close", none, lower, { format: "usd", description: "What charm has dealers trade from now to the 16:00 New York close, USD (negative sells)" });
output("vanna_iv_down", none, lower, { format: "usd", description: "What a one-point IV drop has dealers trade through vanna, USD (negative sells)" });
output("chg_10", none, lower, { format: "usd", description: "Net gamma against the sample 10 minutes before" });
output("chg_20", none, lower, { format: "usd", description: "Against 20 minutes before" });
output("chg_30", none, lower, { format: "usd", description: "Against 30 minutes before" });
output("gex_pct", none, lower, { description: "Net gamma's percentile among the window's earlier samples, 0 to 100" });
output("samples", none, lower, { description: "10-minute samples the window holds" });
output("status", none, lower, { description: "0 not enough history for the percentile, 1 it reads, 2 a chart coarser than 10 minutes" });
string("read", { max_bytes: 64 }); // the headline: the regime and what the hedge does
string("flow_text", { max_bytes: 64 }); // "Flow left dealers short $1.20M delta since 13:30"
string("history_text", { max_bytes: 48 });
string("title", { max_bytes: 64 });

render.hud("dealer_card", {
  position: "bottom_right",
  look: "terminal",
  title: "{{title}}",
  columns: 2,
  tiles: [
    tile.pill("Dealers, estimated", "read", { headline: true, color_by: "gamma_code", colors: ["#ef4444", "#22c55e"] }),
    tile.pill("Today's flow", "flow_text"),
    tile.pill("Against its history", "history_text", { color_by: "status", colors: ["#94a3b8", "#e2e8f0", "#94a3b8"] }),
    tile.rows([
      ["1) Net gamma", "net_gex", "usd"],
      ["2) Hedge for a 1% rally", "hedge_1pct", "usd"],
      ["3) Charm to the close", "charm_to_close", "usd"],
      ["4) IV down a point", "vanna_iv_down", "usd"],
      ["5) Flow delta", "flow_delta", "usd"],
      ["6) Gamma, 10 min ago", "chg_10", "usd"],
      ["7) Gamma, 20 min ago", "chg_20", "usd"],
      ["8) Gamma, 30 min ago", "chg_30", "usd"],
      ["9) Percentile", "gex_pct", "0"],
    ]),
  ],
});

const DAY = 86400.0;
const SAMPLE_SEC = 600.0;
const CLOSE_MINUTE = 960.0; // 16:00 on New York's clock
const CAPACITY = 2000;
const samples = new DayHistory(CAPACITY); // net gamma per 10-minute sample, keyed by the sample's own 10-minute slot
const newYork = new Clock("America/New_York"); // the close the charm drift runs to
const utc = new Clock("UTC");

let lookback = 400; // settings, read in onStart()
let need = 40;
let showLevels = true;
let prevT: f64 = NaN; // the bar spacing
let barSec: f64 = NaN;
let lastT: f64 = NaN; // the newest sample, and what it carried, held
let gex: f64 = NaN;
let charm: f64 = NaN;
let vanna: f64 = NaN;
let callWall: f64 = NaN;
let putWall: f64 = NaN;
let flip: f64 = NaN;
let magnet: f64 = NaN;
let spot: f64 = NaN;
let pctNow: f64 = NaN;
let flowDay: f64 = NaN; // the session the flow sums, from its first bar in view
let flowFirstT: f64 = NaN;
let customerShares = 0.0;

function onStart(): void {
  lookback = i32(p_lookback());
  need = i32(p_min_samples());
  if (need > lookback) need = lookback;
  showLevels = p_show_levels() > 0.5;
}

/** Net gamma's change from the sample minutes before the newest; NaN when the window holds none there. */
function changeFrom(minutes: f64): f64 {
  if (samples.count() < 2) return NaN;
  const ago = samples.agoOf(samples.day(0) - minutes / 10.0);
  return ago < 0 ? NaN : samples.value(0) - samples.value(ago);
}

/** Minutes from the bar to the 16:00 New York close; 0 after it. */
function minutesToClose(t: f64): f64 {
  newYork.update(t);
  const now = f64(newYork.hour() * 60 + newYork.minute());
  return now < CLOSE_MINUTE ? CLOSE_MINUTE - now : 0.0;
}

function sbUsd(v: f64): void { // "$1.20M"
  sb_text("$");
  const a = Math.abs(v);
  if (a >= 1.0e9) {
    sb_f64(a / 1.0e9, 2);
    sb_text("B");
  } else if (a >= 1.0e6) {
    sb_f64(a / 1.0e6, 2);
    sb_text("M");
  } else if (a >= 1.0e3) {
    sb_f64(a / 1.0e3, 2);
    sb_text("K");
  } else sb_f64(a, 0);
}

function sbClock(t: f64): void { // "13:30"
  utc.update(t);
  if (utc.hour() < 10) sb_text("0");
  sb_int(utc.hour());
  sb_text(":");
  if (utc.minute() < 10) sb_text("0");
  sb_int(utc.minute());
}

function writeWords(coarse: bool): void {
  sb_clear();
  if (coarse) sb_text("Needs a 10-minute or finer chart");
  else if (isNaN(gex)) sb_text("Waiting for the 10-minute options statistics");
  else {
    sb_text(gex >= 0.0 ? "Long gamma: sell rallies, buy dips, " : "Short gamma: chase moves, ");
    sbUsd(gex);
    sb_text(" a 1%");
  }
  str_read_sb();
  sb_clear();
  if (coarse || isNaN(flowFirstT)) sb_text("No flow yet");
  else {
    const dealerUsd = -customerShares * (spot > 0.0 ? spot : bar.close());
    sb_text(dealerUsd >= 0.0 ? "Dealers long " : "Dealers short ");
    sbUsd(dealerUsd);
    sb_text(" delta since ");
    sbClock(flowFirstT);
    sb_text(" UTC");
  }
  str_flow_text_sb();
  sb_clear();
  const held = samples.span(lookback);
  if (coarse || samples.count() == 0) sb_text("No history");
  else if (held < need) {
    sb_text("Not enough history yet: ");
    sb_int(held);
    sb_text(" of ");
    sb_int(need);
    sb_text(" samples");
  } else {
    sb_text("Percentile ");
    sb_f64(pctNow, 0);
    sb_text(" of ");
    sb_int(held);
    sb_text(" samples");
  }
  str_history_text_sb();
  sb_clear();
  sb_text("Estimated dealer positioning");
  if (!coarse && !isNaN(lastT)) {
    sb_text(", ");
    sbClock(lastT);
    sb_text(" UTC");
  }
  str_title_sb();
}

// onBar() runs once per bar: the spacing; the bar's flow into the session's sum (a new UTC day starts a new sum); the
// 10-minute sample when it lands (its readings kept, net gamma into the window); every bar writes the held readings.
function onBar(): void {
  const t = bar.time();
  if (!isNaN(prevT) && t > prevT && (isNaN(barSec) || t - prevT < barSec)) barSec = t - prevT;
  prevT = t;
  const coarse = !isNaN(barSec) && barSec > SAMPLE_SEC + 1.0;
  const day = Math.floor(t / DAY);
  if (day != flowDay) {
    flowDay = day;
    flowFirstT = NaN;
    customerShares = 0.0;
  }
  const ask = in_delta_ask();
  const bid = in_delta_bid();
  if (!isNaN(ask) && !isNaN(bid)) {
    if (isNaN(flowFirstT)) flowFirstT = t;
    customerShares += ask - bid;
  }
  const g = in_gex();
  if (!coarse && !isNaN(g)) {
    lastT = t;
    gex = g;
    charm = in_charm();
    vanna = in_vanna();
    callWall = in_call_wall();
    putWall = in_put_wall();
    flip = in_flip();
    magnet = in_magnet();
    spot = in_spot();
    samples.push(Math.floor(t / SAMPLE_SEC), g); // each sample's 10-minute slot: a gap of slots is a gap of time
    pctNow = samples.span(lookback) >= need ? samples.percentile(lookback) : NaN;
  }
  const levels = showLevels && !coarse;
  out_call_wall_line(levels ? callWall : NaN);
  out_put_wall_line(levels ? putWall : NaN);
  out_flip_line(levels ? flip : NaN);
  out_magnet_line(levels ? magnet : NaN);
  out_net_gex(coarse ? NaN : gex);
  out_gamma_code(coarse || isNaN(gex) ? NaN : gex >= 0.0 ? 1.0 : 0.0);
  const price = spot > 0.0 ? spot : bar.close();
  out_flow_delta(coarse || isNaN(flowFirstT) ? NaN : -customerShares * price);
  out_hedge_1pct(coarse ? NaN : -gex);
  out_charm_to_close(coarse || isNaN(charm) ? NaN : (-charm * minutesToClose(t)) / 1440.0);
  out_vanna_iv_down(coarse ? NaN : vanna);
  out_chg_10(coarse ? NaN : changeFrom(10.0));
  out_chg_20(coarse ? NaN : changeFrom(20.0));
  out_chg_30(coarse ? NaN : changeFrom(30.0));
  out_gex_pct(coarse ? NaN : pctNow);
  out_samples(coarse ? NaN : f64(samples.span(lookback)));
  out_status(coarse ? 2.0 : samples.span(lookback) >= need ? 1.0 : 0.0);
  if (bar.isLast()) writeWords(coarse);
}
```

## How it works

**The hedges, signed as trades.** Negative sells, positive buys. A 1% rally moves dealer delta by net gamma, so dealers trade minus net gamma. Charm moves dealer delta by net charm a day, so over the minutes left to the close dealers trade minus net charm times that fraction of a day. A one-point IV drop moves dealer delta by minus net vanna, so dealers trade plus net vanna.

**The flow.** Each bar's share delta at the ask minus at the bid is what customers bought; the sum since the session's first bar in view is their net, and dealers hold the opposite, priced at the newest sample. A bar with no prints reads 0 (`missing: "zero"`); a new UTC day starts a new sum.

**The lookbacks.** Each sample joins a `DayHistory` under its 10-minute slot, so "10 minutes ago" is the slot before, and a gap in the samples is a gap, never a neighbour.

## Where it runs

US stock charts of 10 minutes or finer, through OpenMarket's official package, on the underlyings the data plane samples every ten minutes; any other underlying is refused by name (`wrun_not_in_10m_universe`). Both classes are declared ahead of the lanes that serve them, so the template builds today and the chart shows the lane's refusal until the data plane serves them. The statistics job samples the delayed chain, so the newest 10-minute sample trails the market by about 15 minutes.

## When data is missing

The 10-minute statistics start the day their job is switched on. The readings show from the first sample; the percentile needs **Minimum history** (40 samples, about a session), and until then the pill reads "Not enough history yet: 12 of 40 samples". The flow sums from the session's first bar in view, which the pill names. Before the first sample the card reads "Waiting for the 10-minute options statistics"; a chart coarser than 10 minutes reads "Needs a 10-minute or finer chart".

## Customize it

- **Another window.** `lookback` sets the samples the percentile reads (400 is about ten sessions).
- **A clean price pane.** Turn **Levels** off.

## Run it

1. In the editor's Explorer, press the **Templates** icon and pick **Estimated Dealer Positioning** under **HUDs**.
2. Press **Run** on SPY's 5-minute chart.
3. At the editor's Console prompt, type `net_gex`, `hedge_1pct` and `flow_delta`.

## Concepts used

- [Data sources](../core-concepts/data-sources.md#us-listed-options-by-the-charts-underlying) for `option_stats_10m` and `options_flow`
- [Options kit](../functions/options-kit.md#a-history-of-daily-statistics) for `DayHistory`
- [Panes](../presentation/plotting.md#panes) for the two panes
- [HUD cards](../presentation/hud-and-hover-cards.md#hud-cards) for the card
