---
title: "Option prints"
description: "The US listed option prints (OPRA) of the chart's underlying, filtered and listed newest first in a table under the chart, each one also a mark on price at the…"
order: 95
section: "cookbook"
---

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

# Option prints

The US listed option prints (OPRA) of the chart's underlying, filtered and listed newest first in a table under the chart, each one also a mark on price at the underlying's price when it printed. The filters are settings: the smallest premium, calls or puts, the side of the quote, the days to expiry and out of the money only. A row reads the time in New York, the contract (`SPY 450C 21NOV`), its days to expiry, the side of the quote (AA above the ask, A at the ask, M the mid, B at the bid, BB below the bid, a dash with no quote), the contracts, the price, the premium, the underlying's price, the IV, the delta and the lean: bullish for a call bought or a put sold, bearish for a call sold or a put bought, neutral at the mid. Each row names its contract, so the chart can open that contract's chart from the row once the row click ships.

The parts are a celled `option_prints.cells` input ([Data sources](../core-concepts/data-sources.md#option_prints)) 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 the newest kept prints, a frame feeding a `panel.table` under the chart whose rows name their contracts in `markets` with `fb_contract` ([Table](../presentation/cards-frames-panels.md#table), [Rows that open a market](../presentation/cards-frames-panels.md#rows-that-open-a-market)), a `plot.marks` section on price ([Marks](../presentation/marks-spans-rays.md#marks)), a `render.bgcolor` wash over the bars the chart served in part ([Plotting](../presentation/plotting.md#text-labels-tables-strips-tints)) and the options kit's `optionsTradeDay` for the days to expiry ([Options kit](../functions/options-kit.md#the-markets-own-date-and-the-chains-clock)). This is also the `option-prints` template: the **Option Prints** card under **Order flow** in the editor's starter list, and it compiles as written.

## The wrun indicator

```typescript sample=option-prints
// Option Prints: the US listed option prints (OPRA) of the chart's underlying, filtered, newest first, in a table under
// the chart, each one also a mark on price at the underlying's price when it printed. The filters are settings: the
// smallest premium, calls or puts, the side of the quote, the days to expiry and out of the money only. A row reads the
// time (New York), the contract, its days to expiry, the side (AA above the ask, A at the ask, M the mid, B at the bid,
// BB below the bid, a dash with no quote), the contracts, the price, the premium, the underlying's price, the IV, the
// delta and the lean: bullish for a call bought or a put sold, bearish for a call sold or a put bought, neutral at the
// mid. Each row names its contract, so the chart can open it. The chart serves three days of prints of 10,000 and up;
// a busy bar keeps its largest 4,000 prints and is served in part: its prints are exact but some are missing, so its
// counts are a floor (partial reads 1 and the table's caption says so). It reads the chart's own underlying: on an
// option contract's chart the table fills and the marks stay off, since the price axis is the contract's own.
section("Filters");
param.number("min_premium", 25000, { min: 10000, max: 1000000000, step: 5000, label: "Smallest premium, USD", description: "A print below this premium is left out; the chart serves prints from 10,000" });
param.choice("types", ["calls and puts", "calls", "puts"], "calls and puts", { label: "Calls or puts" });
param.choice("sides", ["every side", "at or above the ask", "at or below the bid", "outside the quote"], "every side", { label: "Side of the quote", description: "Outside the quote keeps the prints above the ask or below the bid" });
param.int("max_dte", 0, { min: 0, max: 1000, label: "Days to expiry, at most", description: "0 keeps every expiry" });
param.bool("otm_only", false, { label: "Out of the money only", description: "Keeps calls struck above the underlying and puts struck below it, a strike within 0.5% counting as out; a print without its underlying is left out" });
section("Display");
param.int("rows", 30, { min: 5, max: 100, label: "Table rows", description: "The newest prints the table lists" });
param.number("mark_unit", 50000, { min: 1000, max: 1000000000, step: 1000, label: "Mark unit, USD", description: "A print of this premium or less draws the smallest mark; the area grows with the premium" });
input("close", ohlcv.close); // the chart's own close: where a print without its underlying is marked, and the check that the chart is the underlying's
input("prints", option_prints.cells, { max_cells: 4000, min_premium: 10000, history: "3d", overflow: "largest", description: "The underlying's option prints of 10,000 and up: three days of history, live on a real-time chart; a busy bar keeps its largest 4,000 and says so" }); // [offset_ms, print_key, root_code, expiry_ms, side_cp, strike, price, size, premium, aggressor, outside, underlying, iv, delta, venue, present] per print
// The readings, data-only: NaN on a bar the chart served no block for.
output("kept", none, overlay, { description: "Prints in the bar the filters kept" });
output("bull_premium", none, overlay, { format: "usd", description: "Premium of the kept prints that lean bullish, USD" });
output("bear_premium", none, overlay, { format: "usd", description: "Premium of the kept prints that lean bearish, USD" });
output("partial", none, overlay, { description: "1 on a bar the chart served in part: its counts and premiums are a floor" });
plot.marks({ name: "print_marks", slots: 16, size: { law: "sqrt", unit: "@mark_unit" }, opacity_pct: 35, x: 1, text: { mode: 0 }, colors: ["#e5484d", "#30a46c", "#94a3b8"], tooltip: "{{qty}} premium at {{price}}, {{fills}} contracts" }); // up to 16 of the bar's kept prints, largest first: side 0 bearish red, 1 bullish green, 2 slate
render.bgcolor("served_in_part", { where: "partial", color: "#64748b14" }); // a faint wash over a bar served in part
const print_rows = frame("print_rows", { max_bytes: 49152 }); // the table, rewritten on the live bar
panel.table({
  name: "print_table",
  title: "Option prints",
  x: "category",
  place: "below",
  frame: print_rows,
  height_frac: 0.3,
  font_size: 10,
  cell_padding: 3,
  valign: "middle",
  header_text_color: "theme.muted",
  grid_color: "theme.grid",
  grid_width: 1,
  grid_lines: "rows",
  border_width: 0,
  series: [
    { name: "Time" },
    { name: "Contract" },
    { name: "DTE", align: "right", format: "int" },
    { name: "Side", align: "center" },
    { name: "Size", align: "right", format: "int" },
    { name: "Price", align: "right", format: "0.00" },
    { name: "Premium", align: "right", format: "usd" },
    { name: "Spot", align: "right", format: "0.00" },
    { name: "IV", align: "right", format: "0.0", unit: "%" },
    { name: "Delta", align: "right", format: "0.00" },
    { name: "Lean" },
  ],
});

const PRINT = 16; // f64s per print: OPTION_PRINTS_TUPLE
const ROW = 15; // f64s a kept row holds: the time, the contract, price, size, premium, aggressor, outside, underlying, iv, delta, present, the bar's open
const MAX_ROWS = 100; // the rows setting's largest value
const MAX_MARKS = 16; // the marks section's slots
const NEAR_SPOT = 0.2; // a print's underlying within 20% of the close: the chart is the underlying's
const MONTHS: StaticArray<string> = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
// A symbol code's characters (0-9 are 1 to 10, A-Z 11 to 36, the dot 37), written one by one so no row allocates.
const CODE_CHARS: StaticArray<string> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "."];
const rootDigits = new StaticArray<i32>(10);
const kept = new StaticArray<f64>(MAX_ROWS * ROW); // the newest kept prints, a ring
const largest = new StaticArray<i32>(MAX_MARKS); // the bar's largest kept prints, as offsets into the block, biggest first
const newYork = new Clock("America/New_York"); // a print's time of day
const dateClock = new Clock("UTC"); // an expiry's trade date, read from its day number

let minPremium = 25000.0; // settings, read in onStart()
let types = 0;
let sides = 0;
let maxDte = 0.0;
let otmOnly = false;
let rows = 30;
let head = 0; // the ring's next slot, and how many slots hold a row
let filled = 0;
let lastPartialMs: f64 = NaN; // the open of the newest bar served in part

function onStart(): void {
  minPremium = p_min_premium();
  types = i32(p_types());
  sides = i32(p_sides());
  maxDte = p_max_dte();
  otmOnly = pb_otm_only();
  rows = i32(p_rows());
}

// +1 bullish, -1 bearish, 0 neutral, from the contract's side (+1 call, -1 put) and the print's side of the quote
// (aggressor +1 the ask, -1 the bid, 0 the mid, 2 no quote).
function leanOf(sideCp: f64, aggressor: f64): i32 {
  if (aggressor == 1.0) return sideCp > 0.0 ? 1 : -1;
  if (aggressor == -1.0) return sideCp > 0.0 ? -1 : 1;
  return 0;
}

// Whether the print at offset `at` passes the filters. A tuple whose size or premium is 0 is the chart's marker for a
// bar served in part, never a print.
function passes(cells: StaticArray<f64>, at: i32, barMs: f64): bool {
  const premium = cells[at + 8];
  if (!(cells[at + 7] > 0.0) || !(premium >= minPremium)) return false;
  const sideCp = cells[at + 4];
  if ((types == 1 && sideCp < 0.0) || (types == 2 && sideCp > 0.0)) return false;
  const aggressor = cells[at + 9];
  const outside = hasBit(cells[at + 15], 3) ? cells[at + 10] : 0.0; // bit 3: the quote was valid
  if (sides == 1 && aggressor != 1.0) return false;
  if (sides == 2 && aggressor != -1.0) return false;
  if (sides == 3 && outside == 0.0) return false;
  if (maxDte > 0.0 && optionsTradeDay(cells[at + 3], 1) - optionsTradeDay(barMs + cells[at], 1) > maxDte) return false;
  if (otmOnly) {
    if (!hasBit(cells[at + 15], 2)) return false; // bit 2: the print carries its underlying
    const strike = cells[at + 5];
    const spot = cells[at + 11];
    const outOfMoney = sideCp > 0.0 ? strike >= spot * 0.995 : strike <= spot * 1.005;
    if (!outOfMoney) return false;
  }
  return true;
}

// The print at offset `at` into the ring, its time made absolute.
function keep(cells: StaticArray<f64>, at: i32, barMs: f64): void {
  const base = head * ROW;
  kept[base] = barMs + cells[at];
  for (let f = 0; f < 4; f += 1) kept[base + 1 + f] = cells[at + 2 + f]; // root_code, expiry_ms, side_cp, strike
  for (let f = 0; f < 8; f += 1) kept[base + 5 + f] = cells[at + 6 + f]; // price, size, premium, aggressor, outside, underlying, iv, delta
  kept[base + 13] = cells[at + 15];
  kept[base + 14] = barMs;
  head = (head + 1) % rows;
  if (filled < rows) filled += 1;
}

// Slides the print at offset `at` into the list of the bar's largest, biggest first; returns the list's new length.
function offer(cells: StaticArray<f64>, at: i32, count: i32): i32 {
  const premium = cells[at + 8];
  if (count == MAX_MARKS && premium <= cells[largest[count - 1] + 8]) return count;
  let i = count < MAX_MARKS ? count : count - 1;
  for (; i > 0 && cells[largest[i - 1] + 8] < premium; i -= 1) largest[i] = largest[i - 1];
  largest[i] = at;
  return count < MAX_MARKS ? count + 1 : count;
}

// ── The table ──
// The characters of a symbol code into rootDigits, last first, and their count; 0 for anything no symbol encodes to.
function rootLength(code: f64): i32 {
  if (!(code >= 1.0) || code > 6278211847988223.0 || code != Math.floor(code)) return 0;
  let rest = u64(code);
  let count = 0;
  while (rest > 0) {
    const value = i32(rest % 38);
    if (value == 0 || count == 10) return 0;
    rootDigits[count] = value;
    count += 1;
    rest = rest / 38;
  }
  return count;
}
function fbRoot(code: f64): void { // the option root as listed, or ? when the code names none
  const count = rootLength(code);
  if (count == 0) fb_text("?");
  for (let i = count - 1; i >= 0; i -= 1) fb_text(CODE_CHARS[rootDigits[i] - 1]);
}
function fbPad(value: i32): void { // two digits, zero first
  if (value < 10) fb_text("0");
  fb_int(value);
}
function fbStrike(strike: f64): void { // 450, 452.5, 1.25: the strike as listed
  fb_f64(strike, strike == Math.floor(strike) ? 0 : strike * 10.0 == Math.floor(strike * 10.0) ? 1 : 2);
}
function fbContract(base: i32): void { // "SPY 450C 21NOV": the root, the strike, call or put, the expiry's date
  fb_text("\"");
  fbRoot(kept[base + 1]);
  fb_text(" ");
  fbStrike(kept[base + 4]);
  fb_text(kept[base + 3] > 0.0 ? "C " : "P ");
  dateClock.update(optionsTradeDay(kept[base + 2], 1) * 86400.0);
  fbPad(dateClock.dayOfMonth());
  fb_text(MONTHS[dateClock.month() - 1]);
  fb_text("\"");
}
function fbTime(ms: f64, todayKey: i32): void { // 15:59:58 today, 16OCT 15:59 on an earlier day, New York time
  newYork.update(Math.floor(ms / 1000.0));
  const today = newYork.dayKey() == todayKey;
  fb_text("\"");
  if (!today) {
    fbPad(newYork.dayOfMonth());
    fb_text(MONTHS[newYork.month() - 1]);
    fb_text(" ");
  }
  fbPad(newYork.hour());
  fb_text(":");
  fbPad(newYork.minute());
  if (today) {
    fb_text(":");
    fbPad(newYork.second());
  }
  fb_text("\"");
}
function fbSide(aggressor: f64, outside: f64, lean: i32): void { // AA, A, M, B, BB or a dash, inked by the lean
  fb_text("{\"text\":\"");
  if (aggressor == 1.0) fb_text(outside > 0.0 ? "AA" : "A");
  else if (aggressor == -1.0) fb_text(outside < 0.0 ? "BB" : "B");
  else if (aggressor == 0.0) fb_text("M");
  else fb_text("-");
  fb_text(lean > 0 ? "\",\"text_color\":\"theme.up\"}" : lean < 0 ? "\",\"text_color\":\"theme.down\"}" : "\",\"text_color\":\"theme.muted\"}");
}
// The contract a row names so the chart can open it: a root of 1 to 6 characters, an expiry, a call or a put and a
// strike below 100,000; any other row names no market.
function fbMarket(base: i32): void {
  const count = rootLength(kept[base + 1]);
  const strike = kept[base + 4];
  if (count < 1 || count > 6 || !(kept[base + 2] > 0.0) || !(strike > 0.0) || strike >= 100000.0) fb_text("null");
  else fb_contract(kept[base + 1], kept[base + 2], kept[base + 3] > 0.0 ? 1.0 : -1.0, strike);
}
function slotOf(k: i32): i32 { // the ring slot of the k-th newest row
  return ((head - 1 - k + rows * 2) % rows) * ROW;
}
function writeTable(nowMs: f64): void {
  newYork.update(Math.floor(nowMs / 1000.0));
  const todayKey = newYork.dayKey();
  const oldestBarMs = filled > 0 ? kept[slotOf(filled - 1) + 14] : NaN;
  fb_clear();
  fb_text("{\"title\":\"Option prints, ");
  fb_int(filled);
  fb_text(filled == 1 ? " row" : " rows");
  fb_text("\",\"caption\":\"");
  if (filled == 0) fb_text("No print passes the filters yet");
  else if (lastPartialMs >= oldestBarMs) fb_text("Some bars here were served in part: largest prints only");
  else fb_text("Newest first, New York time");
  fb_text("\",\"rows\":[");
  for (let k = 0; k < filled; k += 1) {
    const base = slotOf(k);
    const sideCp = kept[base + 3];
    const aggressor = kept[base + 8];
    const present = kept[base + 13];
    const lean = leanOf(sideCp, aggressor);
    if (k > 0) fb_text(",");
    fb_text("[");
    fbTime(kept[base], todayKey);
    fb_text(",");
    fbContract(base);
    fb_text(",");
    fb_f64(optionsTradeDay(kept[base + 2], 1) - optionsTradeDay(kept[base], 1), 0);
    fb_text(",");
    fbSide(aggressor, hasBit(present, 3) ? kept[base + 9] : 0.0, lean);
    fb_text(",");
    fb_f64(kept[base + 6], 0);
    fb_text(",");
    fb_num(kept[base + 5]);
    fb_text(",");
    fb_f64(kept[base + 7], 0);
    fb_text(",");
    if (hasBit(present, 2)) fb_num(kept[base + 10]); // bit 2: the underlying
    else fb_text("\"\"");
    fb_text(",");
    if (hasBit(present, 0)) fb_num(kept[base + 11]); // bit 0: the IV, percent
    else fb_text("\"\"");
    fb_text(",");
    if (hasBit(present, 1)) fb_num(kept[base + 12]); // bit 1: the delta
    else fb_text("\"\"");
    fb_text(lean > 0 ? ",{\"text\":\"Bullish\",\"text_color\":\"theme.up\"}]" : lean < 0 ? ",{\"text\":\"Bearish\",\"text_color\":\"theme.down\"}]" : ",{\"text\":\"Neutral\",\"text_color\":\"theme.muted\"}]");
  }
  fb_text("],\"markets\":[");
  for (let k = 0; k < filled; k += 1) {
    if (k > 0) fb_text(",");
    fbMarket(slotOf(k));
  }
  fb_text("]}");
  writeFrameBuffer(print_rows);
}

// onBar() runs once per bar: the bar's prints through the filters, each kept one into the ring, the bar's largest into
// the marks; the outputs on every bar, the table on the live bar.
function onBar(): void {
  const close = bar.close();
  const barMs = bar.time() * 1000.0;
  const n = in_prints_cells();
  if (n < 0) { // no block on this bar: before the history the chart serves
    out_kept(NaN);
    out_bull_premium(NaN);
    out_bear_premium(NaN);
    out_partial(NaN);
  } else {
    const cells = in_prints_view();
    let count = 0;
    let marks = 0;
    let bull = 0.0;
    let bear = 0.0;
    let partial = false;
    for (let i = 0; i + PRINT - 1 < n; i += PRINT) {
      if (isPartial(cells[i + 15])) partial = true;
      if (!passes(cells, i, barMs)) continue;
      count += 1;
      const lean = leanOf(cells[i + 4], cells[i + 9]);
      if (lean > 0) bull += cells[i + 8];
      else if (lean < 0) bear += cells[i + 8];
      keep(cells, i, barMs);
      marks = offer(cells, i, marks);
    }
    if (partial) lastPartialMs = barMs;
    out_kept(f64(count));
    out_bull_premium(bull);
    out_bear_premium(bear);
    out_partial(partial ? 1.0 : 0.0);
    for (let k = 0; k < marks; k += 1) {
      const at = largest[k];
      const priced = hasBit(cells[at + 15], 2) && cells[at + 11] > 0.0;
      const price = priced ? cells[at + 11] : close;
      if (!(close > 0.0) || !(price > 0.0) || Math.abs(price / close - 1.0) > NEAR_SPOT) continue; // not the underlying's chart
      const lean = leanOf(cells[at + 4], cells[at + 9]);
      const size = cells[at + 7];
      const offset = cells[at];
      out_print_marks_mark(k, price, cells[at + 8], lean > 0 ? 1 : lean < 0 ? 0 : 2, size >= 65535.0 ? 65535 : i32(size), !(offset > 0.0) ? 0 : offset >= 1073741824.0 ? 1073741824 : i32(offset));
    }
  }
  if (bar.isLast()) writeTable(barMs);
}
```

## How it works

**The chart serves the prints.** The input asks for every print of 10,000 and up (`min_premium: 10000`), three days of history (`history: "3d"`) and, on a real-time chart, the live prints as they print. A busy bar keeps its largest 4,000 prints by premium (`overflow: "largest"`, `max_cells: 4000`) instead of refusing the run, and marks itself partial. Each print is sixteen numbers: its time inside the bar, its key, the contract (`root_code`, `expiry_ms`, `side_cp`, `strike`), the price, the size, the premium, the side of the quote (`aggressor`, `outside`), the underlying, the IV, the delta, the venue and the `present` mask.

**The filters are settings.** **Smallest premium** (25,000 by default, 10,000 at least) drops the smaller prints. **Calls or puts** keeps one side. **Side of the quote** keeps the prints at or above the ask, at or below the bid, or outside the quote (above the ask or below the bid, read only where the quote was valid, bit 3). **Days to expiry, at most** keeps the expiries within that many days of the print's New York trade date (0 keeps every expiry). **Out of the money only** keeps calls struck at or above 99.5% of the print's underlying and puts struck at or below 100.5% of it (a strike within 0.5% counts as out), and leaves out a print that carries no underlying (bit 2 clear).

**The side and the lean.** 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 (`A`), below it at the bid (`B`), exactly on it at the mid (`M`), and with no valid quote it has no side (a dash). `AA` is an ask-side print that printed above the ask, `BB` a bid-side print below the bid. The lean reads the side against the contract: a call at the ask or a put at the bid leans bullish, a call at the bid or a put at the ask bearish, the mid and no side neutral. The side cell and the lean cell take the chart's up colour, its down colour or the muted ink.

**The table.** The newest kept prints sit in a ring as large as **Table rows** (30, up to 100), filled bar by bar through the history and the live bar, and the table is rewritten on the live bar, newest first. The time is New York time: hours, minutes and seconds on the newest bar's trade date, then the day, the month, hours and minutes before it (`16OCT 15:59`). Price, Spot, IV and Delta print through their columns' formats; Spot, IV and Delta stay empty for a print that does not carry them (bits 2, 0 and 1). The title counts the rows (`Option prints, 30 rows`) and the caption reads "Newest first, New York time".

**Rows that name their contract.** The frame's `markets` list holds one entry per row, in the rows' order: the row's contract written with `fb_contract(root_code, expiry_ms, side_cp, strike)` straight from the print. A row whose root does not decode to 1 to 6 characters, or whose strike is not above 0 and below 100,000, names no market (`null`), and its contract cell reads `?` where the root would be. The chart's click that opens a row's contract in a new chart tab is upcoming.

**The marks.** Up to 16 of each bar's kept prints, largest premium first, are marks on price at the print's own time inside the bar (`x: 1`), at the underlying's price when the print carries it and at the bar's close when it does not: bearish red, bullish green, neutral slate, the area growing with the premium from the **Mark unit** (50,000), which draws the smallest mark. The hover reads the premium, the price and the contracts. A print whose underlying sits more than 20% from the bar's close draws no mark, since the chart is not the underlying's.

**The readings.** Four data-only outputs on every bar: `kept` (the prints the filters kept), `bull_premium` and `bear_premium` (their premium by lean, USD) and `partial` (1 on a bar the chart served in part). Each reads `NaN` on a bar the chart served no block for.

## 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 Option Prints 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_prints` by name. On an option contract's chart the input reads the contract's underlying, so the table fills; the marks stay off, since that chart's price axis is the contract's own price. 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 four readings read `NaN` and the bar draws nothing.
- **An empty block.** A bar with no print of 10,000 and up reads 0 kept prints and no premium, and draws no mark.
- **A bar served in part.** Bit 14 of `present` (`isPartial`) marks a bar the chart cut down to its largest 4,000 prints, or one past the history the data plane keeps; a bar served in part that holds nothing arrives as one zero tuple whose only set bit is 14, which this indicator never counts as a print. On such a bar `partial` reads 1, a faint slate wash covers it, and its counts and premiums are a floor: the prints it lists are exact, but some are missing. The caption reads "Some bars here were served in part: largest prints only" while such a bar falls inside the rows the table lists.
- **Nothing passes the filters.** The table lists no row and its caption reads "No print passes the filters yet".

## Customize it

- **Only the big ones.** Raise Smallest premium to 250,000 and Mark unit with it: the table and the marks keep the trades that move the most money.
- **Short-dated flow.** Set Days to expiry, at most to 7 and turn Out of the money only on: the table reads the near-term bets.
- **Urgency.** Set Side of the quote to outside the quote: the prints that paid above the ask or sold below the bid, the ones that chose speed over price.

## Run it

1. In the editor's Explorer, press the **Templates** icon ("Browse starter templates") and pick **Option Prints** under **Order flow**.
2. Press **Run** on a US stock chart such as SPY: once the chart serves US option prints the table stands under the chart and the marks sit on the candles at the prints' times; until then the legend's chip names the source the chart does not serve yet.
3. At the editor's Console prompt, type `kept` and `bull_premium` to read the newest bar's counts.

## Concepts used

- [Data sources](../core-concepts/data-sources.md#option_prints) for the `option_prints` celled class, its tuple and its knobs, and [The present mask and partial bars](../core-concepts/data-sources.md#the-present-mask-and-partial-bars) for `hasBit`, `isPartial` and bit 14
- [Table](../presentation/cards-frames-panels.md#table) and [Rows that open a market](../presentation/cards-frames-panels.md#rows-that-open-a-market) for `panel.table` over a frame, styled cells and `markets`
- [Marks](../presentation/marks-spans-rays.md#marks) for `plot.marks`, its size law and `x: 1`
- [Plotting](../presentation/plotting.md#text-labels-tables-strips-tints) for `render.bgcolor` gated by a data-only output
- [Options kit](../functions/options-kit.md#the-markets-own-date-and-the-chains-clock) for `optionsTradeDay`
