Net flow
Who is paying up in the options of the chart's underlying, summed through the session.
The net call premium is the call premium bought at the ask minus the call premium sold at the bid; the net put premium is the same on puts. Both run from the session's first bar as two lines under the chart, calls in green and puts in red: call buying lifts the green line and put buying lifts the red one, while call selling and put selling push them under zero. A card at the top right reads the session in one line (bullish flow leads, bearish flow leads), then both sums, their difference and the net contracts. The Whole market setting draws the same two lines over every US listed underlying instead, and Cap each print counts each print's premium up to 2,000,000, so one block cannot swamp a session.
The parts are scalar options_flow inputs read with missing: "nan", the chart's underlying by default and the market scope behind a setting gate (Data sources), the bar's trade date and session from the time source (Data sources), and a render.hud card in the terminal look over data-only outputs and two text slots (HUD cards). This is also the net-flow template: the Net Flow card under Order flow in the editor's starter list, and it compiles as written.
The wrun indicator
// Net Flow: who is paying up in the options of the chart's underlying, summed through the session. The net call
// premium is the call premium bought at the ask minus the call premium sold at the bid; the net put premium is the
// same on puts. Both run from the session's first bar (each trade date starts again) as two lines under the chart,
// calls in green and puts in red: call buying lifts the green line and put buying the red one, call and put selling
// push them under zero. Prints at the mid or with no quote count in neither line. The Whole market setting draws
// the same two lines over every US listed underlying instead (a second read, fetched only while it is on), and Cap
// each print counts each print's premium up to 2,000,000, so one block cannot swamp a session. A card at the top
// right reads the session: which side the flow leans to (bullish: calls bought and puts sold; bearish: puts bought
// and calls sold), the two sums, their difference and the net contracts, bought at the ask minus sold at the bid,
// calls minus puts. 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 adds nothing, and the card counts the regular-session bars
// that had none: while that count is above 0 the sums are a floor.
section("Flow");
param.bool("market", false, { label: "Whole market", description: "Draw every US listed underlying's net premium instead of the chart's own (a second read, fetched only while this is on)" });
param.bool("capped", false, { label: "Cap each print", description: "Count each print's premium up to 2,000,000, so one block cannot swamp the session" });
input("close", ohlcv.close); // the chart's own candles: the bar grid
input("trade_date", time.trade_date); // the bar's trade date, the New York date on a US stock: the sums start again on each
input("session", time.session); // 1 in the regular session: the bars a missing row is counted on
input("call_net", options_flow.net_call_prem, { missing: "nan", description: "Call premium bought at the ask minus sold at the bid in the bar, the chart's underlying, USD" }); // NaN on a bar the class serves no row for
input("put_net", options_flow.net_put_prem, { missing: "nan", description: "Put premium bought at the ask minus sold at the bid in the bar, USD" });
input("call_net_capped", options_flow.net_call_prem_capped, { missing: "nan", description: "The net call premium with each print's premium capped at 2,000,000" });
input("put_net_capped", options_flow.net_put_prem_capped, { missing: "nan", description: "The net put premium with each print's premium capped at 2,000,000" });
input("call_ask", options_flow.call_contracts_ask, { missing: "nan", description: "Call contracts bought at the ask in the bar" });
input("call_bid", options_flow.call_contracts_bid, { missing: "nan", description: "Call contracts sold at the bid in the bar" });
input("put_ask", options_flow.put_contracts_ask, { missing: "nan", description: "Put contracts bought at the ask in the bar" });
input("put_bid", options_flow.put_contracts_bid, { missing: "nan", description: "Put contracts sold at the bid in the bar" });
// The whole market's rows, the same fields under scope "market": fetched only while Whole market is on.
input("mkt_call_net", options_flow.net_call_prem, { scope: "market", when: "market", missing: "nan", description: "Net call premium over every US listed underlying, USD" });
input("mkt_put_net", options_flow.net_put_prem, { scope: "market", when: "market", missing: "nan", description: "Net put premium over every US listed underlying, USD" });
input("mkt_call_net_capped", options_flow.net_call_prem_capped, { scope: "market", when: "market", missing: "nan", description: "The market's net call premium, each print capped at 2,000,000" });
input("mkt_put_net_capped", options_flow.net_put_prem_capped, { scope: "market", when: "market", missing: "nan", description: "The market's net put premium, each print capped at 2,000,000" });
input("mkt_call_ask", options_flow.call_contracts_ask, { scope: "market", when: "market", missing: "nan", description: "The market's call contracts bought at the ask" });
input("mkt_call_bid", options_flow.call_contracts_bid, { scope: "market", when: "market", missing: "nan", description: "The market's call contracts sold at the bid" });
input("mkt_put_ask", options_flow.put_contracts_ask, { scope: "market", when: "market", missing: "nan", description: "The market's put contracts bought at the ask" });
input("mkt_put_bid", options_flow.put_contracts_bid, { scope: "market", when: "market", missing: "nan", description: "The market's put contracts sold at the bid" });
output("ncp", line, lower, { color: "#22c55e", width: 2, label: "Net call premium", format: "usd", description: "Net call premium summed since the session's first bar, USD; NaN until the session's first flow row" });
output("npp", line, lower, { color: "#ef4444", width: 2, label: "Net put premium", format: "usd", description: "Net put premium summed since the session's first bar, USD" });
output("zero", line, lower, { color: "#334155", width: 1, legend: false, description: "The pane's zero line" }); // keeps zero inside the pane's scale
// The readings, data-only: the card reads them and the Console can too.
output("net_premium", none, lower, { description: "Net call premium minus net put premium over the session, USD: above zero the flow leans bullish" });
output("net_contracts", none, lower, { description: "Contracts bought at the ask minus sold at the bid over the session, calls minus puts" });
output("bar_call_net", none, lower, { description: "This bar's net call premium, USD; NaN on a bar without a flow row" });
output("bar_put_net", none, lower, { description: "This bar's net put premium, USD; NaN on a bar without a flow row" });
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 bearish, 1 no reading or even, 2 bullish" });
string("read", { max_bytes: 48 }); // the headline: the session's lean, or why there is no reading
string("title", { max_bytes: 48 }); // the card's title: the scope and the cap
// The card, in the terminal look: the session's lean leads as the headline (red bearish, slate no reading, green
// bullish), then the two sums, their difference, the net contracts and the bars with no flow row.
render.hud("flow_card", {
position: "top_right",
safe_area: true,
look: "terminal",
title: "{{title}}",
columns: 2,
tiles: [
tile.pill("Session", "read", { headline: true, color_by: "tone", colors: ["#ef4444", "#94a3b8", "#22c55e"] }),
tile.value("Net call premium", "ncp", { format: "usd" }),
tile.value("Net put premium", "npp", { format: "usd" }),
tile.value("Calls minus puts", "net_premium", { format: "usd" }),
tile.value("Net contracts", "net_contracts", { format: "si" }),
tile.rows([["Bars with no flow row", "missing_bars", "int"]]),
],
});
let market = false; // settings, read in onStart()
let capped = false;
let day: f64 = NaN; // the trade date the sums run over
let ncp = 0.0; // the session's sums
let npp = 0.0;
let netContracts = 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 {
market = pb_market();
capped = pb_capped();
}
// The card's words, on the live bar.
function writeCard(net: f64): 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 (net > 0.0) sb_text("Bullish flow leads");
else if (net < 0.0) sb_text("Bearish flow leads");
else sb_text("Calls and puts even");
str_read_sb();
sb_clear();
sb_text(market ? "Net flow, whole market" : "Net flow, this underlying");
if (capped) sb_text(", capped");
str_title_sb();
}
// onBar() runs once per bar: a new trade date starts the sums again; this bar's row, from the scope the settings
// pick, adds to them, or counts as missing on a regular-session bar; then the lines, the readings and, on the live
// bar, the card's words.
function onBar(): void {
const date = in_trade_date();
if (!isNaN(date) && date != day) {
day = date;
ncp = 0.0;
npp = 0.0;
netContracts = 0.0;
rows = 0;
missing = 0;
}
// The market inputs read NaN while Whole market is off: their lane is never fetched.
const callNet = market ? (capped ? in_mkt_call_net_capped() : in_mkt_call_net()) : capped ? in_call_net_capped() : in_call_net();
const putNet = market ? (capped ? in_mkt_put_net_capped() : in_mkt_put_net()) : capped ? in_put_net_capped() : in_put_net();
const served = !isNaN(callNet) && !isNaN(putNet);
if (served) {
ncp += callNet;
npp += putNet;
rows += 1;
runRows += 1;
const contracts = market ? in_mkt_call_ask() - in_mkt_call_bid() - (in_mkt_put_ask() - in_mkt_put_bid()) : in_call_ask() - in_call_bid() - (in_put_ask() - in_put_bid());
if (!isNaN(contracts)) netContracts += contracts;
} else if (in_session() == 1.0) missing += 1;
const reading = rows > 0;
const net = ncp - npp;
out_ncp(reading ? ncp : NaN);
out_npp(reading ? npp : NaN);
out_zero(0.0);
out_net_premium(reading ? net : NaN);
out_net_contracts(reading ? netContracts : NaN);
out_bar_call_net(served ? callNet : NaN);
out_bar_put_net(served ? putNet : NaN);
out_missing_bars(f64(missing));
out_tone(reading ? (net > 0.0 ? 2.0 : net < 0.0 ? 0.0 : 1.0) : 1.0);
if (bar.isLast()) writeCard(net);
}How it works
One row a bar. options_flow serves one row of sums per bar: the premium and the contracts of every print, split by where the print traded against the quote (at the ask, at the bid, at the mid, or with no quote to side it) and by calls and puts. net_call_prem is the call premium at the ask minus the call premium at the bid, and net_put_prem the same on puts, so a print at the mid or with no quote counts in neither. A bar the class serves no row for reads NaN, because every input declares missing: "nan": the package can tell a bar with no row from a bar whose flow summed to zero.
Sums by session. time.trade_date changes on the first bar of each New York date, and the sums start again there: the green line is the net call premium since the session's first bar, the red line the net put premium. A bar without a row adds nothing. On a regular-session bar (time.session reads 1) it also counts in missing_bars, and the card prints that count: while it is above 0 the sums are a floor, the flow of those bars being unknown. A pre-market bar without a row counts as nothing.
The lean and the contracts. net_premium is the net call premium minus the net put premium: bullish flow (calls bought, puts sold) above zero and bearish flow (puts bought, calls sold) below. The card leads with it and colours its headline green or red. net_contracts is the same idea in contracts: calls bought at the ask minus sold at the bid, minus the puts'.
The market's lines. Whole market reads the same fields under scope: "market", every US listed underlying at once. Those inputs carry when: "market", so the chart fetches their rows only while the setting is on; off, they read their fill (NaN) and the package draws the chart's own underlying. Cap each print reads the _capped twins, where each print's premium counts up to 2,000,000.
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, so on a one-minute chart the older sessions draw nothing; an alert and a Protected indicator on OpenMarket's servers refuse the class by name.
When data is missing
The card says why instead of going blank: "No options flow on this chart yet" while no bar of the run carried a row, "No options flow yet this session" while the newest session has none (the lines are empty there until its first row). Bars with no flow row prints the regular-session bars of the session that carried none: above 0, read the sums as at least what they show. A refused source shows its own chip on the legend, with the reason.
Customize it
- The market's tide. Turn on Whole market to read every US listed underlying, the green and red lines then summing the whole market's sided premium.
- Blocks capped. Turn on Cap each print when a single block trade would otherwise decide the session.
- 0DTE only. Add
dte: "0dte"to the inputs to read the contracts expiring on the print's own trade date, ormoneyness: "otm"for the out-of-the-money contracts; each new set of knobs is one more read.
Run it
- In the editor's Explorer, press the Templates icon ("Browse starter templates") and pick Net Flow under Order flow.
- Press Run on a US stock chart: the official package draws the two lines under the chart and the card at the top right; a copy that cannot read US options says so on the legend.
- At the editor's Console prompt, type
ncp,nppandnet_premiumto read the session's sums on the live bar, ormissing_barsto see how many bars carried no row.
Concepts used
- Data sources for the
options_flowclass, its fields and thescope,dteandmoneynessknobs - Data sources for
time.trade_dateandtime.session - HUD cards and Blocks and tiles for the card, its pill and values
- Limitations for the US options rule and the upcoming OPRA sources