Options volume
The option contracts traded on the chart's underlying in every bar, as stacked columns under the chart: calls above zero and puts below, each column split by where its prints traded against the quote.
By side, the default, stacks the calls bought at the ask in deep green, the calls sold at the bid in pale green and the calls at the mid or with no quote in slate, and the puts the same way in red under zero. Bullish and bearish stacks the calls bought at the ask and the puts sold at the bid above zero, the puts bought at the ask and the calls sold at the bid below, and splits the prints with no side evenly into a slate band on each side. A card at the top right reads the session: the call and put contracts, the put/call ratio and the bullish share of the sided contracts.
The parts are eight scalar options_flow inputs, the contracts by side for calls and puts (Data sources), ten bar outputs sharing one stack, so positives pile up above zero and negatives below in declaration order (Plotting), and a render.hud card in the terminal look (HUD cards). This is also the options-volume template: the Options Volume card under Order flow in the editor's starter list, and it compiles as written.
The wrun indicator
// Options Volume: the option contracts traded on the chart's underlying in every bar, calls stacked above zero and
// puts below, each column split by where its prints traded against the quote. By side (the default): calls bought
// at the ask in deep green, sold at the bid in pale green and at the mid or with no quote in slate; puts the same in
// red under zero. Bullish and bearish: calls bought at the ask and puts sold at the bid stack above zero as bullish,
// puts bought at the ask and calls sold at the bid below zero as bearish, and the prints at the mid or with no quote
// split evenly into a slate band on each side, since they carry no side. A card at the top right reads the session
// (each trade date starts again): the call and put contracts, the put/call ratio and the bullish share of the sided
// contracts. The rows come from options_flow, one per bar: 1-minute rows reach back 7 days and 5-minute rows 30
// days, so older bars carry none; a bar without a row draws no column, and the card counts the regular-session bars
// that had none, the sums being a floor while that count is above 0.
param.choice("split", ["side", "sentiment"], "side", { label: "Split", description: "side: each column split into at the ask, at the bid and at the mid or no quote; sentiment: bullish above zero, bearish below, the mid and no-quote prints split evenly between them" });
input("close", ohlcv.close); // the chart's own candles: the bar grid
input("trade_date", time.trade_date); // the bar's trade date: the card's sums start again on each
input("session", time.session); // 1 in the regular session: the bars a missing row is counted on
input("call_ask", options_flow.call_contracts_ask, { missing: "nan", description: "Call contracts bought at the ask in the bar, the chart's underlying" }); // NaN on a bar the class serves no row for
input("call_bid", options_flow.call_contracts_bid, { missing: "nan", description: "Call contracts sold at the bid" });
input("call_mid", options_flow.call_contracts_mid, { missing: "nan", description: "Call contracts printed at the mid" });
input("call_none", options_flow.call_contracts_none, { missing: "nan", description: "Call contracts printed with no quote to side them" });
input("put_ask", options_flow.put_contracts_ask, { missing: "nan", description: "Put contracts bought at the ask" });
input("put_bid", options_flow.put_contracts_bid, { missing: "nan", description: "Put contracts sold at the bid" });
input("put_mid", options_flow.put_contracts_mid, { missing: "nan", description: "Put contracts printed at the mid" });
input("put_none", options_flow.put_contracts_none, { missing: "nan", description: "Put contracts printed with no quote" });
// The columns: one stack, the calls (or the bullish side) above zero and the puts (or the bearish side) below, in
// declaration order. Each split writes its own columns; the other split's read NaN and draw nothing.
output("calls_ask", bar, lower, { color: "#16a34a", width: 0.8, stack: "contracts", label: "Calls at the ask", format: "si", description: "Call contracts bought at the ask (split: side)" });
output("calls_bid", bar, lower, { color: "#86efac", width: 0.8, stack: "contracts", label: "Calls at the bid", format: "si", description: "Call contracts sold at the bid (split: side)" });
output("calls_other", bar, lower, { color: "#64748b", width: 0.8, stack: "contracts", label: "Calls, no side", format: "si", description: "Call contracts at the mid or with no quote (split: side)" });
output("puts_ask", bar, lower, { color: "#dc2626", width: 0.8, stack: "contracts", label: "Puts at the ask", format: "si", description: "Put contracts bought at the ask, drawn under zero (split: side)" });
output("puts_bid", bar, lower, { color: "#fca5a5", width: 0.8, stack: "contracts", label: "Puts at the bid", format: "si", description: "Put contracts sold at the bid, under zero (split: side)" });
output("puts_other", bar, lower, { color: "#475569", width: 0.8, stack: "contracts", label: "Puts, no side", format: "si", description: "Put contracts at the mid or with no quote, under zero (split: side)" });
output("bullish", bar, lower, { color: "#22c55e", width: 0.8, stack: "contracts", label: "Bullish", format: "si", description: "Calls bought at the ask plus puts sold at the bid (split: sentiment)" });
output("neutral_up", bar, lower, { color: "#94a3b8", width: 0.8, stack: "contracts", label: "Neutral", format: "si", description: "Half the contracts at the mid or with no quote, above zero (split: sentiment)" });
output("neutral_down", bar, lower, { color: "#94a3b8", width: 0.8, stack: "contracts", label: "Neutral, below", legend: false, format: "si", description: "The other half, under zero (split: sentiment)" });
output("bearish", bar, lower, { color: "#ef4444", width: 0.8, stack: "contracts", label: "Bearish", format: "si", description: "Puts bought at the ask plus calls sold at the bid, under zero (split: sentiment)" });
// The readings, data-only: the card reads them and the Console can too.
output("session_calls", none, lower, { description: "Call contracts over the session" });
output("session_puts", none, lower, { description: "Put contracts over the session" });
output("put_call", none, lower, { description: "The session's put contracts over its call contracts" });
output("bull_share", none, lower, { description: "Bullish contracts over bullish plus bearish, percent: the sided prints only" });
output("missing_bars", none, lower, { description: "Regular-session bars of this session that carried no flow row: the sums are a floor while it is above 0" });
output("tone", none, lower, { description: "The card's colour: 0 puts outweigh calls, 1 no reading or even, 2 calls outweigh puts" });
string("read", { max_bytes: 48 }); // the headline
// The card, in the terminal look: which side traded more leads (red puts, slate no reading, green calls).
render.hud("volume_card", {
position: "top_right",
safe_area: true,
look: "terminal",
title: "Options volume, this session",
columns: 2,
tiles: [
tile.pill("Session", "read", { headline: true, color_by: "tone", colors: ["#ef4444", "#94a3b8", "#22c55e"] }),
tile.value("Calls", "session_calls", { format: "si" }),
tile.value("Puts", "session_puts", { format: "si" }),
tile.value("Put/call", "put_call", { format: "0.00" }),
tile.value("Bullish share", "bull_share", { format: "%" }),
tile.rows([["Bars with no flow row", "missing_bars", "int"]]),
],
});
let bySide = true; // setting, read in onStart()
let day: f64 = NaN; // the trade date the card's sums run over
let calls = 0.0; // the session's sums
let puts = 0.0;
let bull = 0.0;
let bear = 0.0;
let rows = 0; // bars of this session that carried a flow row
let missing = 0; // regular-session bars of this session without one
let runRows = 0; // flow rows over the whole run
function onStart(): void {
bySide = p_split() == 0.0;
}
function writeRead(): void {
sb_clear();
if (runRows == 0) sb_text("No options flow on this chart yet");
else if (rows == 0) sb_text("No options flow yet this session");
else if (calls > puts) sb_text("More calls than puts");
else if (puts > calls) sb_text("More puts than calls");
else sb_text("Calls and puts even");
str_read_sb();
}
// onBar() runs once per bar: a new trade date starts the card's sums again; the bar's row draws its column in the
// split the setting picks and adds to the sums, or counts as missing on a regular-session bar.
function onBar(): void {
const date = in_trade_date();
if (!isNaN(date) && date != day) {
day = date;
calls = 0.0;
puts = 0.0;
bull = 0.0;
bear = 0.0;
rows = 0;
missing = 0;
}
const callAsk = in_call_ask();
const callBid = in_call_bid();
const callOther = in_call_mid() + in_call_none();
const putAsk = in_put_ask();
const putBid = in_put_bid();
const putOther = in_put_mid() + in_put_none();
const served = !isNaN(callAsk) && !isNaN(callBid) && !isNaN(callOther) && !isNaN(putAsk) && !isNaN(putBid) && !isNaN(putOther);
if (served) {
calls += callAsk + callBid + callOther;
puts += putAsk + putBid + putOther;
bull += callAsk + putBid;
bear += putAsk + callBid;
rows += 1;
runRows += 1;
} else if (in_session() == 1.0) missing += 1;
const side = served && bySide;
const sentiment = served && !bySide;
out_calls_ask(side ? callAsk : NaN);
out_calls_bid(side ? callBid : NaN);
out_calls_other(side ? callOther : NaN);
out_puts_ask(side ? -putAsk : NaN);
out_puts_bid(side ? -putBid : NaN);
out_puts_other(side ? -putOther : NaN);
const neutral = callOther + putOther;
out_bullish(sentiment ? callAsk + putBid : NaN);
out_neutral_up(sentiment ? neutral * 0.5 : NaN);
out_neutral_down(sentiment ? -neutral * 0.5 : NaN);
out_bearish(sentiment ? -(putAsk + callBid) : NaN);
const reading = rows > 0;
out_session_calls(reading ? calls : NaN);
out_session_puts(reading ? puts : NaN);
out_put_call(reading && calls > 0.0 ? puts / calls : NaN);
out_bull_share(reading && bull + bear > 0.0 ? (100.0 * bull) / (bull + bear) : NaN);
out_missing_bars(f64(missing));
out_tone(reading ? (calls > puts ? 2.0 : puts > calls ? 0.0 : 1.0) : 1.0);
if (bar.isLast()) writeRead();
}How it works
The four sides. Each options_flow row splits its contracts by the quote at the print: at the ask, at the bid, at the mid, and with no quote to side it, for calls and for puts. The package reads the eight fields and adds the mid and the no-quote contracts into one slate segment, since neither carries a side.
Two splits, one stack. All ten columns share the stack contracts, so the chart piles each bar's positive columns above zero and its negative ones below, in the order they are declared. By side writes the six side columns (the puts negated) and leaves the four sentiment columns NaN; Bullish and bearish does the opposite, so a column never draws in the split you did not pick. Each stack adds up to the bar's own totals: the calls' three segments to call_contracts_total and the puts' to put_contracts_total.
The session's numbers. The card sums each session from its first bar (time.trade_date starts it again): the calls, the puts, their ratio (puts over calls) and the bullish share, bullish over bullish plus bearish contracts, the sided prints only. Its headline says which side traded more and takes that side's colour.
Where it runs
US stock and ETF charts, and the option contract charts of those underlyings, through OpenMarket's official package once the chart serves options_flow. The class is upcoming: the kit declares it ahead of the chart lane, and a copy you run from the editor follows the US options rule on Limitations. One-minute rows reach back 7 days and five-minute rows 30 days; older bars draw no column.
When data is missing
A bar the class serves no row for draws no column at all, never a zero. The card's headline reads "No options flow on this chart yet" while no bar of the run carried a row and "No options flow yet this session" while the newest session has none. Bars with no flow row counts the session's regular-session bars without one: above 0, the session's numbers are a floor.
Customize it
- Premium instead of contracts. Read the
_prem_fields (call_prem_ask, ...) in place of the_contracts_ones and format the columns asusd. - One expiry bucket. Add
dte: "0dte"ordte: "week"to the eight inputs to stack only those contracts; the knob is one read for all eight. - Out of the money. Add
moneyness: "otm"to the inputs to count the out-of-the-money contracts alone.
Run it
- In the editor's Explorer, press the Templates icon ("Browse starter templates") and pick Options Volume under Order flow.
- Press Run on a US stock chart: the official package draws the columns under the chart and the card at the top right.
- At the editor's Console prompt, type
put_callandbull_shareto read the session on the live bar, orlast 20 calls_askfor the calls bought at the ask on the last 20 bars.
Concepts used
- Data sources for the
options_flowclass and its side fields - Plotting for
baroutputs and thestackword - HUD cards and Blocks and tiles for the card
- Limitations for the US options rule and the upcoming OPRA sources