---
title: "Contract sides"
description: "Where the chart's underlying's US listed options (OPRA) traded against the quote, calls beside puts. Every contract of the underlying that traded in a bar…"
order: 98
section: "cookbook"
---

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

# Contract sides

Where the chart's underlying's US listed options (OPRA) traded against the quote, calls beside puts. Every contract of the underlying that traded in a bar arrives with its contracts split by the side of the quote, and the bars under the chart sum them over today's session or over the last bars you pick: **AA** above the ask, **A** at the ask, **M** the mid, **B** at the bid, **BB** below the bid, and **No side** for prints with no quote to judge. Premium splits three ways instead: at the ask and above, at the bid and below, and the rest (the mid and no side together). A print at the ask was most likely bought, one at the bid sold, and one outside the quote paid up for speed. In a pane of its own, each bar's bullish contracts minus its bearish contracts stand as one column.

The parts are a celled `option_contract_flow.cells` input ([Data sources](../core-concepts/data-sources.md#option_contract_flow)) read through its `present` mask ([The present mask and partial bars](../core-concepts/data-sources.md#the-present-mask-and-partial-bars)), a ring of each bar's sums, a frame feeding a `panel.bars` under the chart with a per-run title and badge ([Bars](../presentation/cards-frames-panels.md#bars)), a histogram coloured per bar by a data-only ladder output and a `render.bgcolor` wash over the bars the chart served in part ([Plotting](../presentation/plotting.md#lines-areas-columns-dots-marks), [tints](../presentation/plotting.md#text-labels-tables-strips-tints)), and the options kit's `optionsTradeDay` for today's session ([Options kit](../functions/options-kit.md#the-markets-own-date-and-the-chains-clock)). This is also the `contract-sides` template: the **Contract Sides** card under **Order flow** in the editor's starter list, and it compiles as written.

## The wrun indicator

```typescript sample=contract-sides
// Contract Sides: where the chart's underlying's US listed options (OPRA) traded against the quote, calls beside puts.
// Every contract the underlying lists that traded in a bar arrives with its contracts split by the side of the quote,
// and the bars under the chart sum them over today's session or the last bars you pick, by the midpoint rule: AA above
// the ask, A the rest of the ask side (above the mid, up to the ask), M the mid, B the rest of the bid side (below the
// mid, down to the bid), BB below the bid, and no side (a print with no quote to judge). Premium splits three ways
// instead: at or above the ask, at or below the bid, and the rest (the mid and no side together, the total premium from
// each contract's VWAP minus the two sides). A print at the ask or above was most likely bought, one at the bid or below
// sold, and one outside the quote paid up for speed. Under the chart, each bar's bullish minus bearish contracts (calls
// bought and puts sold, minus calls sold and puts bought). The chart may serve an older bar of a busy underlying in part
// (one marker instead of its contracts): such a bar is a floor, its column turns slate, a faint wash covers it and the
// bars' badge says so. It reads the chart's own underlying: a US stock chart or an option contract's chart.
section("Window");
param.choice("measure", ["contracts", "premium"], "contracts", { label: "Measure" });
param.choice("window", ["today", "last bars"], "today", { label: "Window", description: "Today: the bars of the New York trade date the newest bar is on; last bars: the bars setting" });
param.int("bars", 60, { min: 1, max: 500, label: "Bars", description: "The window under last bars" });
input("close", ohlcv.close); // the chart's own candles: the grid the flow lines up on
input("flow", option_contract_flow.cells, { max_cells: 16000, description: "Each contract of the underlying that traded in the bar, contracts and premium by side of the quote" }); // [root_code, expiry_ms, side_cp, strike, vol_ask, vol_bid, vol_mid, vol_none, prem_ask, prem_bid, share_delta_net, vwap, last_price, iv_last, vol_above_ask, vol_below_bid, present] per contract
output("lean", histogram, lower, { color_by: "lean_tone", colors: ["#e5484d", "#30a46c", "#64748b"], label: "Bullish minus bearish contracts", format: "si", description: "Calls at the ask or above plus puts at the bid or below, minus calls at the bid or below and puts at the ask or above, contracts" });
output("lean_tone", none, lower, { description: "The column's colour: 0 bearish, 1 bullish, 2 a bar served in part" });
output("above_ask", none, lower, { format: "si", description: "Contracts that printed above the ask in the bar, calls and puts" });
output("below_bid", none, lower, { format: "si", description: "Contracts that printed below the bid in the bar, calls and puts" });
output("call_ask_share", none, lower, { format: "%", decimals: 1, description: "Share of the window's call contracts at the ask or above, percent; live bar only" });
output("put_ask_share", none, lower, { format: "%", decimals: 1, description: "Share of the window's put contracts at the ask or above, percent; live bar only" });
output("partial", none, lower, { description: "1 on a bar the chart served in part: its contracts are a floor" });
render.bgcolor("served_in_part", { where: "partial", color: "#64748b14" }); // a faint wash over a bar served in part
const side_rows = frame("side_rows", { max_bytes: 2048 });
panel.bars({ name: "by_side", title: "Contracts by side", x: "category", place: "below", frame: side_rows, orientation: "vertical", chrome: "grid", labels: true, series: [{ name: "Calls", color: "#30a46c" }, { name: "Puts", color: "#e5484d" }], format: "si", hover_card: true, legend_style: "title", height_frac: 0.22 });

const FLOW = 17; // f64s per contract: OPTION_CONTRACT_FLOW_TUPLE
const SLOTS = 18; // per bar: calls AA, A, M, B, BB, none; puts the same; then calls' premium at the ask, at the bid, the rest; puts the same
const MAX_BARS = 500; // the bars setting's largest value
const ring = new StaticArray<f64>(MAX_BARS * SLOTS); // the last bars' sums, one slot per bar
const ringDay = new StaticArray<f64>(MAX_BARS); // each slot's New York trade date
const ringPartial = new StaticArray<f64>(MAX_BARS); // 1 when that bar was served in part
const barSums = new StaticArray<f64>(SLOTS); // this bar's sums
const sums = new StaticArray<f64>(SLOTS); // the window's sums
const BUCKETS: StaticArray<string> = ["AA", "A", "M", "B", "BB", "No side"];
const PREMIUM_BUCKETS: StaticArray<string> = ["Ask and above", "Bid and below", "Mid and no side"];

let measure = 0; // settings, read in onStart()
let today = true;
let bars = 60;
let head = 0; // the ring's next slot, and how many slots hold a bar
let filled = 0;

function onStart(): void {
  measure = i32(p_measure());
  today = p_window() == 0.0;
  bars = i32(p_bars());
}

// One contract's sums into barSums: its contracts by bucket (AA and BB are the part of the ask and bid sides that
// printed outside the quote) and its premium at the ask, at the bid and the rest, by call or put.
function add(cells: StaticArray<f64>, at: i32): void {
  const base = cells[at + 2] > 0.0 ? 0 : 6;
  const askSide = cells[at + 4];
  const bidSide = cells[at + 5];
  const aboveAsk = Math.min(cells[at + 14], askSide);
  const belowBid = Math.min(cells[at + 15], bidSide);
  barSums[base] += aboveAsk;
  barSums[base + 1] += askSide - aboveAsk;
  barSums[base + 2] += cells[at + 6];
  barSums[base + 3] += bidSide - belowBid;
  barSums[base + 4] += belowBid;
  barSums[base + 5] += cells[at + 7];
  const premiumBase = 12 + (cells[at + 2] > 0.0 ? 0 : 3);
  const premiumAsk = cells[at + 8];
  const premiumBid = cells[at + 9];
  barSums[premiumBase] += premiumAsk;
  barSums[premiumBase + 1] += premiumBid;
  if (hasBit(cells[at + 16], 0)) { // bit 0: the VWAP, so the total premium is known (standard 100-share contracts)
    const total = cells[at + 11] * (askSide + bidSide + cells[at + 6] + cells[at + 7]) * 100.0;
    if (total > premiumAsk + premiumBid) barSums[premiumBase + 2] += total - premiumAsk - premiumBid;
  }
}

function share(part: f64, whole: f64): f64 {
  return whole > 0.0 ? (part / whole) * 100.0 : NaN;
}

// The bars under the chart: one category per side, calls beside puts, over the window.
function writeBars(windowBars: i32, partialBars: i32): void {
  fb_clear();
  fb_text("{\"title\":\"");
  fb_text(measure == 0 ? "Contracts by side, " : "Premium by side, ");
  if (today) fb_text("today");
  else {
    fb_text("last ");
    fb_int(windowBars);
    fb_text(windowBars == 1 ? " bar" : " bars");
  }
  fb_text("\"");
  if (partialBars > 0) {
    fb_text(",\"badge\":{\"text\":\"");
    fb_int(partialBars);
    fb_text(partialBars == 1 ? " bar in part\",\"color\":\"#64748b\"}" : " bars in part\",\"color\":\"#64748b\"}");
  }
  fb_text(",\"rows\":[");
  if (measure == 0) {
    for (let b = 0; b < 6; b += 1) {
      if (b > 0) fb_text(",");
      fb_text("[");
      fb_str(BUCKETS[b]);
      fb_text(",");
      fb_f64(sums[b], 0);
      fb_text(",");
      fb_f64(sums[6 + b], 0);
      fb_text("]");
    }
  } else {
    for (let b = 0; b < 3; b += 1) {
      if (b > 0) fb_text(",");
      fb_text("[");
      fb_str(PREMIUM_BUCKETS[b]);
      fb_text(",");
      fb_f64(sums[12 + b], 0);
      fb_text(",");
      fb_f64(sums[15 + b], 0);
      fb_text("]");
    }
  }
  fb_text("]}");
  writeFrameBuffer(side_rows);
}

// onBar() runs once per bar: the bar's contracts summed by side into its ring slot; the column and the bar's readings on
// every bar; the window's sums, the two shares and the bars on the live bar.
function onBar(): void {
  const barMs = bar.time() * 1000.0;
  const n = in_flow_cells();
  for (let s = 0; s < SLOTS; s += 1) barSums[s] = 0.0;
  let partial = false;
  if (n > 0) {
    const cells = in_flow_view();
    for (let i = 0; i + FLOW - 1 < n; i += FLOW) {
      if (isPartial(cells[i + 16])) partial = true;
      if (!(cells[i + 3] > 0.0)) continue; // no strike: the chart's marker for a bar served in part, never a contract
      add(cells, i);
    }
  }
  const slot = head * SLOTS;
  for (let s = 0; s < SLOTS; s += 1) ring[slot + s] = barSums[s];
  ringDay[head] = optionsTradeDay(barMs, 1);
  ringPartial[head] = partial ? 1.0 : 0.0;
  head = (head + 1) % MAX_BARS;
  if (filled < MAX_BARS) filled += 1;
  if (n < 0) { // no block on this bar: before the history the chart serves
    out_lean(NaN);
    out_lean_tone(NaN);
    out_above_ask(NaN);
    out_below_bid(NaN);
    out_partial(NaN);
  } else {
    const bullish = barSums[0] + barSums[1] + barSums[9] + barSums[10]; // calls at the ask or above, puts at the bid or below
    const bearish = barSums[3] + barSums[4] + barSums[6] + barSums[7]; // calls at the bid or below, puts at the ask or above
    out_lean(bullish + bearish > 0.0 ? bullish - bearish : NaN); // a bar with no sided contract draws no column
    out_lean_tone(partial ? 2.0 : bullish >= bearish ? 1.0 : 0.0);
    out_above_ask(barSums[0] + barSums[6]);
    out_below_bid(barSums[4] + barSums[10]);
    out_partial(partial ? 1.0 : 0.0);
  }
  if (!bar.isLast()) {
    out_call_ask_share(NaN);
    out_put_ask_share(NaN);
    return;
  }
  // The window: today's bars, or the last `bars`, newest first from the ring.
  for (let s = 0; s < SLOTS; s += 1) sums[s] = 0.0;
  const day = optionsTradeDay(barMs, 1);
  let windowBars = 0;
  let partialBars = 0;
  for (let k = 0; k < filled; k += 1) {
    const at = (head - 1 - k + MAX_BARS) % MAX_BARS;
    if (today ? ringDay[at] != day : k >= bars) break;
    for (let s = 0; s < SLOTS; s += 1) sums[s] += ring[at * SLOTS + s];
    windowBars += 1;
    if (ringPartial[at] > 0.0) partialBars += 1;
  }
  const calls = sums[0] + sums[1] + sums[2] + sums[3] + sums[4] + sums[5];
  const puts = sums[6] + sums[7] + sums[8] + sums[9] + sums[10] + sums[11];
  out_call_ask_share(share(sums[0] + sums[1], calls));
  out_put_ask_share(share(sums[6] + sums[7], puts));
  writeBars(windowBars, partialBars);
}
```

## How it works

**Each contract's flow, by side.** The input hands each bar one tuple per contract that traded in it (`max_cells: 16000`, the whole chain): its contracts at the ask, at the bid, at the mid and with no side, its premium at the ask and at the bid, its VWAP, and its contracts above the ask and below the bid. The data plane judges each print against the national best bid and offer just before it, by the midpoint: above the mid it is at the ask, below it at the bid, exactly on it at the mid, and with no valid quote it has no side.

**Six buckets of contracts.** A print above the ask is an ask-side print, and one below the bid a bid-side print, so the buckets split those two sides: **AA** is the contracts above the ask, **A** the rest of the ask side, **B** the rest of the bid side, **BB** the contracts below the bid; **M** and **No side** are the mid and the unquoted prints. Each bucket is summed for the calls and for the puts.

**Three buckets of premium.** The data carries a contract's premium at the ask and at the bid, so premium splits three ways: **Ask and above**, **Bid and below**, and **Mid and no side**, which is the contract's total premium (its VWAP times its contracts times 100, a standard contract's multiplier) minus the two sides, counted when the contract carries its VWAP (bit 0).

**The window.** **Window** sums today's bars (the New York trade date the newest bar is on, up to the last 500 bars) or the last **Bars** bars (60, up to 500). **Measure** picks contracts (six buckets) or premium (three). The bars stand under the chart on the live bar, calls green beside puts red in each bucket, titled by the measure and the window (`Contracts by side, today`, `Premium by side, last 60 bars`).

**The column.** `lean` is each bar's bullish contracts (calls at the ask or above plus puts at the bid or below) minus its bearish contracts (calls at the bid or below plus puts at the ask or above), a histogram in its own pane, green at or above zero and red below (`lean_tone` picks the colour); a bar with no contract on a side of the quote draws no column.

**The readings.** Data-only outputs: `above_ask` and `below_bid` (the bar's contracts outside the quote, calls and puts), `partial` (1 on a bar the chart served in part), and on the live bar `call_ask_share` and `put_ask_share`, the share of the window's call contracts and of its put contracts at the ask or above, in percent.

## Where it runs

US stock and ETF charts, through the listed options (OPRA) of the chart's own underlying. The OPRA sources are upcoming: the kit declares them ahead of the chart lanes that will serve them, so this indicator builds today but no surface serves it yet ([Data sources](../core-concepts/data-sources.md#us-listed-options-opra)). Once those lanes ship, the chart in the browser serves them under the US options access rule ([Limitations](../reference/limitations.md)): OpenMarket's official Contract Sides package reads them (it is not on the registry yet), and a copy you run from the editor or publish yourself stops with "Paused on US options", as a draft or a community indicator does under that rule. An alert and a Protected indicator on OpenMarket's servers refuse `option_contract_flow` by name. On an option contract's chart the input reads the contract's underlying, so the bars are the same as on the stock's chart. A bar with more contracts than 16,000 refuses the run, never truncated. The chart routes the reads to the tier the account holds, 15 minutes delayed unless it holds real-time US options, and the legend names that tier.

## When data is missing

- **No block.** A bar before the history the chart serves carries no block: the column and the readings read `NaN`, and the bar adds nothing to the window.
- **An empty block.** A bar where no contract traded adds zeros to the window and draws no column.
- **A bar served in part.** Bit 14 of `present` (`isPartial`) marks a bar the chart served in part: on a busy underlying the chart may send an older bar as one zero tuple whose only set bit is 14 instead of its contracts, which this indicator never counts as a contract. On such a bar `partial` reads 1, its column (if it has one) turns slate, a faint slate wash covers it, and its contracts are a floor. While the window holds such bars, the bars' badge counts them (`3 bars in part`).

## Customize it

- **The session's verdict.** Keep Window at today and Measure at premium: the dollars bought against the dollars sold since the open.
- **A rolling view.** Set Window to last bars and Bars to 12 on a 5-minute chart: the last hour's sides.
- **Urgency alone.** Read `above_ask` and `below_bid` in the Console: the contracts that crossed the spread to trade.

## Run it

1. In the editor's Explorer, press the **Templates** icon ("Browse starter templates") and pick **Contract Sides** under **Order flow**.
2. Press **Run** on a US stock chart such as SPY: once the chart serves US option flow the bars stand under the chart with the column in its own pane; until then the legend's chip names the source the chart does not serve yet.
3. At the editor's Console prompt, type `call_ask_share` and `put_ask_share` to read the window's shares bought at the ask.

## Concepts used

- [Data sources](../core-concepts/data-sources.md#option_contract_flow) for the `option_contract_flow` celled class and its tuple, and [The present mask and partial bars](../core-concepts/data-sources.md#the-present-mask-and-partial-bars) for `hasBit`, `isPartial` and bit 14
- [Bars](../presentation/cards-frames-panels.md#bars) for `panel.bars` over a frame with two series, a per-run title and a badge
- [Plotting](../presentation/plotting.md#lines-areas-columns-dots-marks) for a histogram coloured by a ladder output, and [tints](../presentation/plotting.md#text-labels-tables-strips-tints) for `render.bgcolor`
- [Options kit](../functions/options-kit.md#the-markets-own-date-and-the-chains-clock) for `optionsTradeDay`
