MasscrestMasscrest
Documentation · 01

Data dictionary

Field-level reference for every table Masscrest delivers.

Download .md

Scope and attribution. US single stocks and ETFs options. Flow attributed per print to retail, institutional, and market maker. Metrics per cohort: buy, sell, net (in shares equivalent), premium, and greek-adjusted notional (delta, gamma, vega).

Universe and coverage. ~4,000 tickers (full universe of listed US stocks + ETFs), including delisted tickers.

History. 2019-11-10 to T-1. PIT, FIGI-based identifiers with mapping table. T+1 delivery before US trading session starts.

Granularity.

  • Daily aggregated by composite FIGI + call/put
  • 10-minute aggregated by composite FIGI + call/put
  • Per-contract (expiry + strike + call/put) daily or 10-min

Delivery.

  • Programmatic via GCS: S3 compatible or warehouse-native grants (Snowflake, Databricks, BigQuery)
  • REST API
  • MCP server for LLM-consumable workflows

Investor segmentation

CohortDescription
InstitutionalLong-only, long/short, market-neutral, long-biased, multi-strat and other equity hedge funds; mutual funds, sovereign and endowment funds, family offices and other asset managers; proprietary trading desks, banks and dealers trading on behalf of customers.
RetailGlobal self-directed retail traders (not investment advisors or private-banking clients).
Interdealer / market maker~99% of trades take place against a market maker, so this cohort sits opposite Institutional and Retail flows. Interdealer trades are between dealers, typically to offset greek exposure.

Three layers of data aggregation

  1. Option flow by contract: buy, sell, and net trading imbalance for every listed contract, expressed in shares, premium, and greek-adjusted notional (delta, gamma, vega).
  2. Option flow by underlying ticker: the same trading imbalance aggregated to the underlying root symbol × callput.
  3. Signal: proprietary indicator of abnormal net options positioning (delta, gamma, vega exposure).
SurfaceGrainDaily10-min
Option flow by contract(date, figi, callput, strike, expiration)yesyes
Option flow by underlying ticker(date, figi, callput)yesyes
Option flow grouped (sector / industry / ...)(date, group_key, callput)yesno
Signal per underlying(date, figi, callput)yesno
Signal grouped (sector / industry / ...)(date, group_key, callput)yesno

Typical use cases

  • Chase institutional flow. See where hedge funds and asset managers are building or unwinding positions.
  • Spot retail-vs-institutional divergences. Identify names where the two cohorts sit on opposite sides.
  • Trade the optionality dimension. Separate directional bets from vega and gamma positioning.
  • Layer market narrative on price action. Explain why a name moved when the tape alone doesn't tell you: accumulation ahead of a break, capitulation into a bottom.
  • Control for flow in factor models. A signal orthogonal to price and volume. Wire it in as a regression control alongside conventional risk factors.

Delivery & availability

Daily datasets are keyed on US-equity trading dates and delivered one calendar day later (Friday's data lands Saturday).

Cadence

  • Frequency: T+1. Data for trading day T is delivered on calendar day T+1 (Friday's data lands Saturday).
  • Publish window: 01:00 to 05:00 ET on the calendar day after the trading date (window floats with DST).

option_flows_01_day

What it is

One row per unique option contract that traded on the session, keyed on (date, traded_symbol, callput, strikeprice, expirationdate). Each row carries buy / sell / net triples for retail (_r), institutional (_i), and market maker (_m), expressed four ways: contract count (_qty), dollar premium (_premium), and greek-weighted USD notional (_delta, _gamma, _vega).

Why it exists

The contract-grain view is where you see who's actually on which side of a specific position. Underlying-level aggregates are good for sector or cross-stock work, but questions like "is the institutional buyer in TSLA Jan 300 calls leaning in on a specific expiry?" or "is the put-side flow concentrated in the 20-delta wings?" only answer at this grain. This table is the base: the underlying rollups and the signal tables are summations of it.

How to use it

Filter on the dimensions that matter: date + figi (join via contract_id_mapping) + callput + the strike / expiry range. For a time series on one specific contract, resolve to contract_id once via contract_id_mapping and filter on that so your series survives corporate actions.

Three conventions to internalise before summing:

  • net_* = buy_* − sell_*. Delta notionals are naturally signed by option type (calls +, puts −) so net_*_delta already carries directional lean.
  • _i + _r + _m ≈ 0 on the notional columns, row by row (up to rounding and floor flow). Market-maker legs absorb the customer imbalance by construction.
  • qty is the trade count. Summing the six cohort legs (buy_r + sell_r + buy_i + sell_i + buy_m + sell_m) double-counts the MM leg and inflates volume by about 30 to 35 percent.

floor_* holds exchange-floor / broker-committed prints that Masscrest does not side-classify. Reported for volume completeness; do not include in buy / sell / net logic.

Examples

Reading institutional lean on a single strike. TSLA 2025-01-17 300 call on 2024-11-15: pull the one row keyed (2024-11-15, TSLA, C, 300, 2025-01-17). net_i_delta positive and large relative to qty × last_underprice × last_delta means institutional buyers are dominating the demand for that contract that day.

Reconstructing per-cohort premium across a split. NVDA Jan 2025 500 call across the June 2024 10-for-1 split: join contract_id_mapping on (traded_symbol, expirationdate, callput, traded_strikeprice) with a PIT clamp, then sum net_i_premium by contract_id. The resulting series runs clean across the ex-date even though the row keys differ pre and post split.

Context columns

Each row carries last_* columns (underprice, fwd_underprice, price, iv, delta, gamma, vega) observed at the last trade of the session. Useful as an end-of-day mark for the contract. Null when the contract had no trades in the session (rare at this grain, but can happen on open-position rows pulled from inventory).

  • Grain: (date, traded_symbol, callput, strikeprice, expirationdate)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, traded_symbol, callput, strikeprice, expirationdate)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
traded_symbolLowCardinality(String)ticker(date, traded_symbol, callput, strikeprice, expirationdate)Symbol (no-dot form: BRKB, not BRK.B). Numerated forms (WOLF1, XOM2) appear when a corporate action generates two series with different shares deliverable (typically reverse split, mergers, etc). Join key to contract_id_mapping for traded_underlying / contract_id / adj_shares_deliverable / figi.exchangeT+12019-11-10never nullAAPL
callputLowCardinality(String)option type(date, traded_symbol, callput, strikeprice, expirationdate)'C' (call) or 'P' (put).exchangeT+12019-11-10never nullC
strikepriceFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Contract strike price, in the deliverable equity's price units. Not split-adjusted at this grain; join to contract_id_mapping for cumulative_split_factor if you need a current-share-equivalent strike.exchangeT+12019-11-10never null215.00
expirationdateDateboundary(date, traded_symbol, callput, strikeprice, expirationdate)Contract expiration date (US equity calendar).exchangeT+12019-11-10never null2026-08-15
dteInt64days(date, traded_symbol, callput, strikeprice, expirationdate)Days to expiry: expirationdate − date (calendar days). 0 on the expiry-day trading session; can be negative on late prints of already-expired contracts (rare).derivedT+12019-11-10never null22
last_underpriceNullable(Float64)USD(date, traded_symbol, callput, strikeprice, expirationdate)Underlying spot price observed at the last option trade of the session. Not split-adjusted (contemporaneous mark).exchangeT+12019-11-10null when the contract has no trades in the session224.31
last_fwd_underpriceNullable(Float64)USD(date, traded_symbol, callput, strikeprice, expirationdate)Forward price on the underlying, adjusted for dividends and the risk-free rate. Not adjusted for splits.derivedT+12019-11-10null when the contract has no trades in the session224.68
last_priceNullable(Float64)USD(date, traded_symbol, callput, strikeprice, expirationdate)Last traded option price on the session. Per-contract price (not scaled by adj_shares_deliverable).exchangeT+12019-11-10null when the contract has no trades in the session9.42
last_ivNullable(Float64)annualised vol (0.30 = 30%)(date, traded_symbol, callput, strikeprice, expirationdate)Implied volatility at the last trade of the session, fit against last_fwd_underprice and last_price.derivedT+12019-11-10null when the contract has no trades / IV inversion fails0.2418
last_deltaNullable(Float64)delta per contract(date, traded_symbol, callput, strikeprice, expirationdate)Contract delta at the last trade of the session. Signed by option type (calls +, puts −).derivedT+12019-11-10null when the contract has no trades in the session0.5620
last_gammaNullable(Float64)gamma per contract(date, traded_symbol, callput, strikeprice, expirationdate)Contract gamma at the last trade of the session.derivedT+12019-11-10null when the contract has no trades in the session0.0184
last_vegaNullable(Float64)vega per contract(date, traded_symbol, callput, strikeprice, expirationdate)Contract vega at the last trade of the session (dollar P&L per 1 vol point change).derivedT+12019-11-10null when the contract has no trades in the session0.2712
qtyInt64contracts(date, traded_symbol, callput, strikeprice, expirationdate)Total unique traded contract volume for the session (each print contributes its quantity once; m_qty is not double-counted).exchangeT+12019-11-100 on no-volume rows (present only when other fields exist)1_842
buy_r_qty / sell_r_qty / net_r_qtyInt64contracts(date, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net contract count. net = buy − sell.modelT+12019-11-100 when no qualifying retail flow241
buy_r_premium / sell_r_premium / net_r_premiumFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Retail dollar premium paid on buys, received on sells, and their net. Signed on net. Scaled by adj_shares_deliverable (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow312_040.0
buy_r_delta / sell_r_delta / net_r_deltaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD notional × delta (deliverable-adjusted). Naturally signed by option type (calls +, puts −); net = buy − sell. Positive net = net long-delta demand from retail on this contract.modelT+12019-11-100.0 when no qualifying retail flow-38_720.55
buy_r_gamma / sell_r_gamma / net_r_gammaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD-notional-converted gamma (dollar notional multiplied by the per-contract gamma, deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow12_450.30
buy_r_vega / sell_r_vega / net_r_vegaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow88_012.40
buy_i_qty / sell_i_qty / net_i_qtyInt64contracts(date, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net contract count. Same convention as retail.modelT+12019-11-100 when no qualifying institutional flow987
buy_i_premium / sell_i_premium / net_i_premiumFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Institutional dollar premium buy / sell / net (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow1_120_338.55
buy_i_delta / sell_i_delta / net_i_deltaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD notional × delta (deliverable-adjusted). Naturally signed; positive net = net long-delta demand from the institutional class on this contract.modelT+12019-11-100.0 when no qualifying institutional flow892_310.00
buy_i_gamma / sell_i_gamma / net_i_gammaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD-notional-converted gamma (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow41_882.70
buy_i_vega / sell_i_vega / net_i_vegaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow312_048.80
buy_m_qty / sell_m_qty / net_m_qtyInt64contracts(date, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net contract count. Includes the residual absorption of customer (retail + institutional) net imbalance on the opposite side, so the row-level accounting identity _i + _r + _m ≈ 0 holds by construction on the notionals.modelT+12019-11-100 when no qualifying market-maker flow-1_098
buy_m_premium / sell_m_premium / net_m_premiumFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Market-maker dollar premium buy / sell / net (deliverable-adjusted). Mirror of _i + _r on the same row up to floor / rounding.modelT+12019-11-100.0 when no qualifying market-maker flow-1_432_378.65
buy_m_delta / sell_m_delta / net_m_deltaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD notional × delta (deliverable-adjusted). Mirror of _i + _r on the same row.modelT+12019-11-100.0 when no qualifying market-maker flow-853_590.00
buy_m_gamma / sell_m_gamma / net_m_gammaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD-notional-converted gamma (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying market-maker flow-54_333.00
buy_m_vega / sell_m_vega / net_m_vegaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying market-maker flow-400_172.00
floor_qtyInt64contracts(date, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed contract count (direct-negotiation outcry trades on the exchange floor). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100 when no floor prints24
floor_premiumFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed dollar premium (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints24_120.0
floor_deltaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD notional × delta (deliverable-adjusted). No directional classification provided; reported for volume completeness. Naturally signed by option type.exchangeT+12019-11-100.0 when no floor prints13_492.20
floor_gammaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD-notional-converted gamma (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints1_842.10
floor_vegaFloat64USD(date, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD-notional-converted vega (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints9_820.60

Reconstructing figi / adj_shares_deliverable from flat files

REST and MCP responses already carry figi, contract_id, traded_underlying, adj_shares_deliverable, and cumulative_split_factor on every row. The flat-file parquet drops them to keep file sizes small. Rebuild with a single PIT join to contract_id_mapping:

SELECT
  f.*,
  m.traded_underlying,
  m.contract_id,
  m.adj_shares_deliverable,
  m.cumulative_split_factor,
  m.underlying_figi AS figi
FROM option_flows_01_day AS f
INNER JOIN contract_id_mapping AS m
  ON  m.traded_symbol      = f.traded_symbol
  AND m.expirationdate     = f.expirationdate
  AND m.callput            = f.callput
  AND m.traded_strikeprice = f.strikeprice
  AND m.phase_start        <= f.date
  AND m.phase_end          >= f.date

Join stock_mapping on (underlying_figi, date BETWEEN phase_start AND phase_end) for underlying metadata (sector, industry, isEtf, isAdr, companyName).

option_flows_10_min

What it is

The 10-minute sibling of option_flows_01_day. One row per unique option contract that traded within the bucket, keyed on (date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate). Same cohort split and same metric families (_qty, _premium, _delta, _gamma, _vega across retail / institutional / MM), just resolved at 10-minute cadence instead of session-level.

Why it exists

Intraday matters when the question is about timing. Did the institutional accumulation front-run a specific headline? Did retail buy into the top of a move or chase a breakout? Session-level aggregates smear those entries. 10-minute buckets are the shortest grain Masscrest publishes and are the native cadence of the intraday flow story.

How to use it

Same convention as option_flows_01_day (buy / sell / net, net_* = buy_* − sell_*, cohort identity _i + _r + _m ≈ 0, signed delta notionals). Add the ten_min_timeframe dimension to your filter / join / group-by. Join to px_10_min on (date, ten_min_timeframe, figi) for the matching price context; both sides use the same bucket convention.

REST cap: 30-day request window per call. For multi-year intraday history pull parquet from gs://prod-us-option-tenmin-contract/option_flows/v0/10_min/.

Bucket boundary convention

ten_min_timeframe is the end of the 10-minute bucket, stored as a naive New York wall-clock DateTime. Bucket covers (ten_min_timeframe − 10 min, ten_min_timeframe]. First RTH bucket ends at 09:40:00, last at 16:00:00 (39 buckets per full session).

Examples

Front-running a headline. An M&A rumour hits at 11:12 ET. Filter (date, figi='<target>') across buckets 11:10:00 through 11:30:00. If net_i_delta on OTM calls spikes above the prior 90-minute average before the newswire print at 11:15, that's a timing signal.

Earnings-afternoon put flow. Day before earnings, pull (date, figi, callput='P') across buckets 15:00:00 to 16:00:00. Rising net_r_qty into the close on short-dated puts flags retail hedging (or chasing) activity ahead of the print.

  • Grain: (date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
ten_min_timeframeDateTimeNY wall-clock, bucket close(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)End timestamp of the 10-min bucket (naive NY-time). Bucket covers (ten_min_timeframe − 10 min, ten_min_timeframe].exchangeT+12019-11-10never null2026-07-24 09:40:00
traded_symbolLowCardinality(String)ticker(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Symbol (no-dot form: BRKB, not BRK.B). Numerated forms (WOLF1, XOM2) appear when a corporate action generates two series with different shares deliverable (typically reverse split, mergers, etc). Join key to contract_id_mapping for traded_underlying / contract_id / adj_shares_deliverable / figi.exchangeT+12019-11-10never nullAAPL
callputLowCardinality(String)option type(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)'C' (call) or 'P' (put).exchangeT+12019-11-10never nullC
strikepriceFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Contract strike price, in the deliverable equity's price units. Not split-adjusted at this grain; join to contract_id_mapping for cumulative_split_factor if you need a current-share-equivalent strike.exchangeT+12019-11-10never null215.00
expirationdateDateboundary(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Contract expiration date (US equity calendar).exchangeT+12019-11-10never null2026-08-15
dteInt64days(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Days to expiry: expirationdate − date (calendar days). 0 on the expiry-day trading session.derivedT+12019-11-10never null22
last_underpriceNullable(Float64)USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Underlying spot at the last option trade within the bucket. Not split-adjusted.exchangeT+12019-11-10null when the contract has no trades in the bucket224.10
last_fwd_underpriceNullable(Float64)USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Forward price on the underlying, adjusted for dividends and the risk-free rate. Not adjusted for splits.derivedT+12019-11-10null when the contract has no trades in the bucket224.48
last_priceNullable(Float64)USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Last traded option price in the bucket. Per-contract price (not scaled by adj_shares_deliverable).exchangeT+12019-11-10null when the contract has no trades in the bucket9.38
last_ivNullable(Float64)annualised vol (0.30 = 30%)(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Implied volatility at the last trade of the bucket.derivedT+12019-11-10null when the contract has no trades / IV inversion fails0.2418
last_deltaNullable(Float64)delta per contract(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Contract delta at the last trade of the bucket. Signed by option type.derivedT+12019-11-10null when the contract has no trades in the bucket0.5620
last_gammaNullable(Float64)gamma per contract(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Contract gamma at the last trade of the bucket.derivedT+12019-11-10null when the contract has no trades in the bucket0.0184
last_vegaNullable(Float64)vega per contract(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Contract vega at the last trade of the bucket.derivedT+12019-11-10null when the contract has no trades in the bucket0.2712
qtyInt64contracts(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total unique traded contract volume in the bucket.exchangeT+12019-11-100 on no-volume rows184
buy_r_qty / sell_r_qty / net_r_qtyInt64contracts(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net contract count within the bucket.modelT+12019-11-100 when no qualifying retail flow24
buy_r_premium / sell_r_premium / net_r_premiumFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Retail dollar premium buy / sell / net for the bucket (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow31_204.0
buy_r_delta / sell_r_delta / net_r_deltaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD notional × delta (deliverable-adjusted). Naturally signed by option type; positive net = net long-delta demand from retail on this contract in the bucket.modelT+12019-11-100.0 when no qualifying retail flow-3_872.55
buy_r_gamma / sell_r_gamma / net_r_gammaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD-notional-converted gamma (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow1_245.30
buy_r_vega / sell_r_vega / net_r_vegaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Retail buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying retail flow8_801.40
buy_i_qty / sell_i_qty / net_i_qtyInt64contracts(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net contract count for the bucket.modelT+12019-11-100 when no qualifying institutional flow98
buy_i_premium / sell_i_premium / net_i_premiumFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Institutional dollar premium buy / sell / net (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow112_034.55
buy_i_delta / sell_i_delta / net_i_deltaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD notional × delta (deliverable-adjusted). Positive net = net long-delta demand from the institutional class on this contract in the bucket.modelT+12019-11-100.0 when no qualifying institutional flow89_231.00
buy_i_gamma / sell_i_gamma / net_i_gammaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD-notional-converted gamma (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow4_188.70
buy_i_vega / sell_i_vega / net_i_vegaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Institutional buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying institutional flow31_204.80
buy_m_qty / sell_m_qty / net_m_qtyInt64contracts(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net contract count for the bucket. Includes the residual absorption of customer (retail + institutional) net imbalance on the opposite side.modelT+12019-11-100 when no qualifying market-maker flow-108
buy_m_premium / sell_m_premium / net_m_premiumFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Market-maker dollar premium buy / sell / net (deliverable-adjusted). Mirror of _i + _r on the same row up to floor / rounding.modelT+12019-11-100.0 when no qualifying market-maker flow-143_237.65
buy_m_delta / sell_m_delta / net_m_deltaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD notional × delta (deliverable-adjusted). Mirror of _i + _r on the same row.modelT+12019-11-100.0 when no qualifying market-maker flow-85_359.00
buy_m_gamma / sell_m_gamma / net_m_gammaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD-notional-converted gamma (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying market-maker flow-5_433.30
buy_m_vega / sell_m_vega / net_m_vegaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Market-maker buy / sell / net flow as USD-notional-converted vega (deliverable-adjusted).modelT+12019-11-100.0 when no qualifying market-maker flow-40_017.20
floor_qtyInt64contracts(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed contract count within the bucket (direct-negotiation outcry trades on the exchange floor). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100 when no floor prints4
floor_premiumFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed dollar premium within the bucket (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints4_120.0
floor_deltaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD notional × delta within the bucket (deliverable-adjusted). No directional classification provided; reported for volume completeness. Naturally signed.exchangeT+12019-11-100.0 when no floor prints2_349.20
floor_gammaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD-notional-converted gamma within the bucket (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints184.10
floor_vegaFloat64USD(date, ten_min_timeframe, traded_symbol, callput, strikeprice, expirationdate)Total floor-executed USD-notional-converted vega within the bucket (deliverable-adjusted). No directional classification provided; reported for volume completeness.exchangeT+12019-11-100.0 when no floor prints982.60

underlying_option_flows_01_day

What it is

Daily option flow aggregated per underlying × callput. One row per (date, figi, callput) with callput ∈ {'C', 'P', 'CP'}. Same cohort split as the contract-grain table (retail _r, institutional _i, market-maker _m) with the same four metric families, but all strikes and expirations collapsed into a single number per side.

Why it exists

Most flow questions aren't about one specific contract. "Is retail bullish on NVDA today?" / "Did institutions rotate into Energy puts this week?" need the total call-side and put-side demand on the name, not one strike at a time. This table is that collapse, done server-side with the correct per-contract deliverable multiplier baked in so you can plot a time series without joining anything else.

How to use it

Filter on (date, figi, callput). Three conventions to internalise:

  • Units are pre-multiplied. _shares = Σ (qty × adj_shares_deliverable) in underlying-share equivalents. _premium and the greeks are USD notionals, deliverable-adjusted. No contract_id_mapping join needed.
  • Cohort identity _i + _r + _m ≈ 0 holds row by row on the notionals. MM legs absorb customer imbalance.
  • The CP row is a pre-computed differential, not a sum of C and P. See "The CP row" below for the convention per column family.

Price and metadata aren't baked in: join px_01_day (plus split_factors for adjustment) and stock_mapping on figi.

No contract-level filters here (no dte, no abs_delta). For those, use option_flows_01_day at contract grain.

Examples

Retail bullishness on NVDA. Filter (date='2024-11-15', figi=<NVDA>, callput='CP') and read net_r_delta. Positive means retail net long delta on NVDA across the whole option chain that session (calls bought minus puts bought, net of sells, after delta-weighting).

Weekly institutional rotation into Energy puts. Join stock_mapping on figi to get sector, filter sector='Energy' and callput='P', sum net_i_premium across the FIGIs for each date in the window. Compare the 5-day sum against the prior 5-day sum.

  • Grain: (date, figi, callput)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, figi, callput)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
figiString(12)identifier(date, figi, callput)Composite FIGI for the underlying. Join key to stock_mapping, px_01_day, split_factors.exchangen/a2019-11-10never nullBBG000MM2P62
traded_underlyingStringticker(date, figi, callput)Listed equity ticker on date. Dotted dual-class form (BRK.B).exchangeT+12019-11-10never nullAAPL
callputString(2)option type(date, figi, callput)'C' (call side), 'P' (put side), or 'CP' (call-vs-put differential row).exchangeT+12019-11-10never nullC
n_contractsUInt32contracts(date, figi, callput)Distinct option contracts that traded on this underlying × callput on date.exchangeT+12019-11-100 on no-volume days184
total_sharesFloat64underlying-share equivalents(date, figi, callput)Sum of all traded contract volume converted to underlying-share equivalents (Σ qty × adj_shares_deliverable), regardless of side.exchangeT+12019-11-100.0 on no-volume days2_384_100.0
last_underpriceFloat64USD(date, figi, callput)Underlying spot price observed against the last option trade of the session. Not split-adjusted (contemporaneous mark).exchangeT+12019-11-10null on no-volume days224.31
atm_iv_30dFloat64annualised vol (0.30 = 30%)(date, figi, callput)Best-effort estimate of ATM 30-day implied volatility for the underlying on this session. Not derived from a fitted volatility surface. For high-accuracy implied-volatility estimation, particularly on less liquid contracts, we recommend using specialist third-party providers.derivedT+12019-11-10may be null when the figi had no option activity on the session0.2418
buy_r_shares / sell_r_shares / net_r_sharesFloat64underlying-share equivalents(date, figi, callput)Retail buy / sell / net share equivalents for the (underlying, callput) row.modelT+12019-11-100.0 when no qualifying volume24_100.0
buy_r_premium / sell_r_premium / net_r_premiumFloat64USD(date, figi, callput)Retail dollar premium buy / sell / net.modelT+12019-11-100.0 when no qualifying volume312_040.0
buy_r_delta / sell_r_delta / net_r_deltaFloat64USD(date, figi, callput)Retail buy / sell / net flow as USD notional × delta for the (underlying, callput) row. Naturally signed (calls +, puts −); positive net = net long-delta demand from retail on this call/put side.modelT+12019-11-100.0 when no qualifying volume-3_872_000.55
buy_r_gamma / sell_r_gamma / net_r_gammaFloat64USD(date, figi, callput)Retail buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume1_245_030.30
buy_r_vega / sell_r_vega / net_r_vegaFloat64USD(date, figi, callput)Retail buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume8_801_240.40
buy_i_shares / sell_i_shares / net_i_sharesFloat64underlying-share equivalents(date, figi, callput)Institutional buy / sell / net share equivalents.modelT+12019-11-100.0 when no qualifying volume128_450.0
buy_i_premium / sell_i_premium / net_i_premiumFloat64USD(date, figi, callput)Institutional dollar premium buy / sell / net.modelT+12019-11-100.0 when no qualifying volume412_034.55
buy_i_delta / sell_i_delta / net_i_deltaFloat64USD(date, figi, callput)Institutional buy / sell / net flow as USD notional × delta for the (underlying, callput) row. Positive net = net long-delta demand from the institutional class on this call/put side.modelT+12019-11-100.0 when no qualifying volume89_231_000.00
buy_i_gamma / sell_i_gamma / net_i_gammaFloat64USD(date, figi, callput)Institutional buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume4_188_270.70
buy_i_vega / sell_i_vega / net_i_vegaFloat64USD(date, figi, callput)Institutional buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume31_200_480.80
buy_m_shares / sell_m_shares / net_m_sharesFloat64underlying-share equivalents(date, figi, callput)Market-maker buy / sell / net share equivalents. _i + _r + _m ≈ 0 per row.modelT+12019-11-100.0 when no qualifying volume-130_860.0
buy_m_premium / sell_m_premium / net_m_premiumFloat64USD(date, figi, callput)Market-maker dollar premium buy / sell / net. Mirror of _i + _r.modelT+12019-11-100.0 when no qualifying volume-824_067.10
buy_m_delta / sell_m_delta / net_m_deltaFloat64USD(date, figi, callput)Market-maker buy / sell / net flow as USD notional × delta. Mirror of _i + _r.modelT+12019-11-100.0 when no qualifying volume-85_359_000.00
buy_m_gamma / sell_m_gamma / net_m_gammaFloat64USD(date, figi, callput)Market-maker buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume-5_433_300.00
buy_m_vega / sell_m_vega / net_m_vegaFloat64USD(date, figi, callput)Market-maker buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume-40_001_720.00

The CP row

callput = 'CP' is a pre-computed call-vs-put row on the same (date, figi) grain, stored alongside the 'C' and 'P' rows (not synthesised at query time). Convention per column type:

  • Cohort size columns (buy_*_shares / sell_*_shares / net_*_shares / buy_*_premium / sell_*_premium / net_*_premium for cohorts r, i, m): value is C − P (call side minus put side). Negative values on buy_* / sell_* are expected on put-heavy names; the sign reflects which side is bigger.
  • Cohort greek columns (buy_*_delta / sell_*_delta / net_*_delta and the _gamma / _vega equivalents): value is C + P. Put delta is stored negative on the 'P' row, so C + P is naturally signed (positive net_i_delta on CP = net long-delta demand from institutions once puts are netted).
  • Volume context (n_contracts, total_shares): value is C + P (underlying-level totals across both sides).
  • Underlying context (last_underprice, atm_iv_30d): same value as the 'C' and 'P' rows for the same (date, figi) (underlying-level, not per-side).

Read the CP row for a single call-vs-put summary per underlying per session. For gross activity split by side, read the 'C' and 'P' rows directly.

underlying_option_flows_10_min

What it is

The 10-minute sibling of underlying_option_flows_01_day. Daily option flow collapsed per underlying × callput, resolved at 10-minute cadence. One row per (date, ten_min_timeframe, figi, callput) with callput ∈ {'C', 'P', 'CP'}. Same cohort split and metric families, deliverable-multiplied at ingest.

Why it exists

The daily table answers "what happened today on this name"; this table answers "when during the day did it happen". The intraday footprint of cohort flow often matters more than the aggregate: institutional accumulation into the close looks different from the same volume at the open.

How to use it

Filter on (date, ten_min_timeframe, figi, callput). Same conventions as the daily sibling: pre-multiplied units (no contract_id_mapping join needed), _i + _r + _m ≈ 0 row-level identity, and the CP row as a pre-computed differential (see below).

Join px_10_min on (date, ten_min_timeframe, figi) for the matching price context. Both sides use the same NY-wall-clock bucket convention.

REST cap: 30-day request window per call. For multi-year intraday history pull parquet from gs://prod-us-option-tenmin-underlying/underlying_option_flows/v0/10_min/.

Bucket boundary convention

ten_min_timeframe is the end of the 10-minute bucket, stored as a naive New York wall-clock DateTime. Bucket covers (ten_min_timeframe − 10 min, ten_min_timeframe]. First RTH bucket ends at 09:40:00, last at 16:00:00.

Examples

Opening vs closing institutional flow on SPY. Pull (date, figi=<SPY>, callput='CP') for one session. Split net_i_delta into the 09:40:00 to 10:30:00 buckets (open) and 15:00:00 to 16:00:00 buckets (close) and compare. A big positive close with flat open signals mark-up / rebalancing behaviour; the inverse signals de-risking into the bell.

Headline-timing on a biotech. FDA PDUFA print lands around 14:00 ET. Filter (date, figi=<name>, callput='C') across the 13:40:00 to 14:00:00 buckets. Rising net_i_premium in the three buckets before 14:00 against zero-volume baseline is a leaked-print signal.

  • Grain: (date, ten_min_timeframe, figi, callput)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, ten_min_timeframe, figi, callput)Session date.exchangeT+12019-11-10never null2026-07-24
ten_min_timeframeDateTimeNY wall-clock, bucket close(date, ten_min_timeframe, figi, callput)End timestamp of the 10-min bucket (naive NY-time).exchangeT+12019-11-10never null2026-07-24T09:40:00
figiString(12)identifier(date, ten_min_timeframe, figi, callput)Composite FIGI for the underlying.exchangen/a2019-11-10never nullBBG000MM2P62
traded_underlyingStringticker(date, ten_min_timeframe, figi, callput)Listed equity ticker on date. Dotted dual-class form.exchangeT+12019-11-10never nullAAPL
callputString(2)option type(date, ten_min_timeframe, figi, callput)'C' (call side), 'P' (put side), or 'CP' (call-vs-put differential row).exchangeT+12019-11-10never nullC
n_contractsUInt32contracts(date, ten_min_timeframe, figi, callput)Distinct option contracts traded on this underlying × callput within the bucket.exchangeT+12019-11-100 on no-volume buckets48
total_sharesFloat64underlying-share equivalents(date, ten_min_timeframe, figi, callput)Sum of all traded volume converted to share equivalents within the bucket (Σ qty × adj_shares_deliverable).exchangeT+12019-11-100.0 on no-volume buckets184_500.0
last_underpriceFloat64USD(date, ten_min_timeframe, figi, callput)Underlying spot at the last option trade within the bucket. Not split-adjusted.exchangeT+12019-11-10null on no-volume buckets224.10
atm_iv_30dFloat64annualised vol(date, ten_min_timeframe, figi, callput)Best-effort estimate of ATM 30-day implied volatility for the underlying in this bucket. Not derived from a fitted volatility surface. For high-accuracy implied-volatility estimation, particularly on less liquid contracts, we recommend using specialist third-party providers.derivedT+12019-11-10may be null when the figi had no option activity in the bucket0.2418
buy_r_shares / sell_r_shares / net_r_sharesFloat64underlying-share equivalents(date, ten_min_timeframe, figi, callput)Retail buy / sell / net share equivalents within the bucket.modelT+12019-11-100.0 when no qualifying volume2_410.0
buy_r_premium / sell_r_premium / net_r_premiumFloat64USD(date, ten_min_timeframe, figi, callput)Retail dollar premium buy / sell / net.modelT+12019-11-100.0 when no qualifying volume31_204.0
buy_r_delta / sell_r_delta / net_r_deltaFloat64USD(date, ten_min_timeframe, figi, callput)Retail buy / sell / net flow as USD notional × delta within the bucket. Naturally signed by option type; positive net = net long-delta demand from retail on this call/put side in the bucket.modelT+12019-11-100.0 when no qualifying volume-387_200.55
buy_r_gamma / sell_r_gamma / net_r_gammaFloat64USD(date, ten_min_timeframe, figi, callput)Retail buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume124_530.30
buy_r_vega / sell_r_vega / net_r_vegaFloat64USD(date, ten_min_timeframe, figi, callput)Retail buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume880_140.40
buy_i_shares / sell_i_shares / net_i_sharesFloat64underlying-share equivalents(date, ten_min_timeframe, figi, callput)Institutional buy / sell / net share equivalents. _i + _r + _m ≈ 0 per row.modelT+12019-11-100.0 when no qualifying volume12_845.0
buy_i_premium / sell_i_premium / net_i_premiumFloat64USD(date, ten_min_timeframe, figi, callput)Institutional dollar premium buy / sell / net.modelT+12019-11-100.0 when no qualifying volume41_203.55
buy_i_delta / sell_i_delta / net_i_deltaFloat64USD(date, ten_min_timeframe, figi, callput)Institutional buy / sell / net flow as USD notional × delta. Positive net = net long-delta demand from the institutional class on this call/put side in the bucket.modelT+12019-11-100.0 when no qualifying volume8_923_100.00
buy_i_gamma / sell_i_gamma / net_i_gammaFloat64USD(date, ten_min_timeframe, figi, callput)Institutional buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume418_870.70
buy_i_vega / sell_i_vega / net_i_vegaFloat64USD(date, ten_min_timeframe, figi, callput)Institutional buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume3_120_080.80
buy_m_shares / sell_m_shares / net_m_sharesFloat64underlying-share equivalents(date, ten_min_timeframe, figi, callput)Market-maker buy / sell / net share equivalents.modelT+12019-11-100.0 when no qualifying volume-13_086.0
buy_m_premium / sell_m_premium / net_m_premiumFloat64USD(date, ten_min_timeframe, figi, callput)Market-maker dollar premium buy / sell / net. Mirror of _i + _r.modelT+12019-11-100.0 when no qualifying volume-82_406.71
buy_m_delta / sell_m_delta / net_m_deltaFloat64USD(date, ten_min_timeframe, figi, callput)Market-maker buy / sell / net flow as USD notional × delta. Mirror of _i + _r.modelT+12019-11-100.0 when no qualifying volume-8_535_900.00
buy_m_gamma / sell_m_gamma / net_m_gammaFloat64USD(date, ten_min_timeframe, figi, callput)Market-maker buy / sell / net flow as USD notional × gamma.modelT+12019-11-100.0 when no qualifying volume-543_330.00
buy_m_vega / sell_m_vega / net_m_vegaFloat64USD(date, ten_min_timeframe, figi, callput)Market-maker buy / sell / net flow as USD notional × vega.modelT+12019-11-100.0 when no qualifying volume-4_001_720.00

The CP row

callput = 'CP' is a pre-computed call-vs-put row on the same (date, ten_min_timeframe, figi) grain, stored alongside the 'C' and 'P' rows. Convention per column type:

  • Cohort size columns (buy_*_shares / sell_*_shares / net_*_shares / buy_*_premium / sell_*_premium / net_*_premium for cohorts r, i, m): value is C − P (call side minus put side) within the bucket. Negative values on buy_* / sell_* are expected on put-heavy buckets.
  • Cohort greek columns (buy_*_delta / sell_*_delta / net_*_delta and the _gamma / _vega equivalents): value is C + P. Put delta is stored negative on the 'P' row, so C + P is naturally signed.
  • Volume context (n_contracts, total_shares): value is C + P (bucket totals across both sides).
  • Underlying context (last_underprice, atm_iv_30d): same value as the 'C' and 'P' rows for the same bucket (underlying-level, not per-side).

Read the CP row for a single call-vs-put summary per underlying per bucket. For gross activity split by side, read the 'C' and 'P' rows directly.

underlying_flow_signals_01_day

What it is

The underlying-grain nets from underlying_option_flows_01_day, plus two trailing-window transforms of each metric: a 21-trading-day rolling sum (_21dma) and a 2-year rolling z-score of that sum (z_*). One row per (date, figi, callput) with callput ∈ {'C', 'P', 'CP'}.

Why it exists

Raw daily flow is noisy. The signal story is usually "flow is extreme against the stock's own history", not "flow is extreme in absolute terms". A $50M net call premium is unremarkable on SPY and a monster print on a small-cap. Normalising each raw net against its own 2-year baseline (sum first to smooth, then z-score) collapses the two onto a comparable scale and makes a quintile sort across names meaningful.

Published Masscrest research is built off this transform: the single-name spike predicate at /research/systematic-single-stock-signal and the industry-aggregated long-short performance at /research/industry-flow-signal. All fields are trailing-window only (no forward-looking data), so a row at date = D is safe to act on at close of D or open of D+1.

How to use it

Filter on (date, figi, callput). Grab the raw net, the 21dma, or the z-score depending on what question you're asking.

  • Raw net (net_i_shares, net_i_premium, etc.): one session's cohort flow. Use for single-day event studies.
  • 21dma (net_i_shares_21dma, etc.): trailing 21-session sum. Smooths the one-day noise, still trailing-only.
  • Z-score (z_net_i_shares_21dma, etc.): the 21dma expressed in units of its own 2-year rolling mean and standard deviation. |z| > 2 is the usual "extreme vs own history" threshold.

Each row is scoped to one callput. Filter to a single callput per query or you'll triple-count. Rolling windows partition by (figi, callput) independently, so z(C) + z(P) ≠ z(CP).

The CP row

Size measures (shares, premium) on the CP row are C − P: bullish call flow minus bearish put flow, both of which express the same bullish directional conviction. Greeks (delta, gamma, vega) are C + P: these columns are already signed on the C and P rows (put delta stored negative), so a straight sum gives net book exposure. net_*_delta on the CP row is net directional demand across the whole chain.

Examples

Spike screen. Filter callput='CP' and z_net_i_premium_21dma > 2 on a given date. Returns the names where institutional call-minus-put premium flow this month is more than two standard deviations above the name's own 2-year baseline.

Cohort-divergence signal. Compare z_net_i_delta vs z_net_r_delta on the same (date, figi, callput='CP'). Opposite signs flag names where institutions and retail are positioned against each other, a setup the flagship signals validate at single-name grain.

Signal design

The calculation is deliberately simple:

  • _21dma = 21-trading-day rolling SUM of the raw daily net, partitioned by (figi, callput). (Named for legacy reasons: it's a sum, not a mean, but the z-score is identical either way.)
  • z_net_*_21dma = z-score of _21dma against its 2-year (504 trading day) trailing mean and sample standard deviation. Minimum 63 non-null observations in the trailing window required, else null. Rounded to 2 decimals.

Masscrest does not cap, winsorise, or otherwise regularise the output. Z-scores can reach 3 or 4 SD during earnings, M&A, and macro catalysts. Keeping the formula plain avoids overfitting to any specific regime and lets clients replicate the transform end-to-end from underlying_option_flows_01_day.

Transform chain: daily net, then 21-day trailing sum (_21dma), then z-score against the 2y rolling μ/σ of the 21dma (z_*).

Warm-up

All fields computed from 2019-11-10 forward. The 21dma needs 21 non-null daily nets; the z-score needs at least 63 non-null observations in the trailing 504-session window. Below threshold, the value is null.

  • Grain: (date, figi, callput) where callput ∈ {'C', 'P', 'CP'}.

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, figi, callput)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
figiString(12)identifier(date, figi, callput)Composite FIGI for the underlying. Stable across ticker changes; join key to stock_mapping, px_01_day, split_factors.exchangen/a2019-11-10never nullBBG000MM2P62
underlying_tickerStringticker(date, figi, callput)Point-in-time listed equity ticker on date. Dotted dual-class form (BRK.B). Named underlying_ticker here rather than traded_underlying; the API SELECT aliases it back to traded_underlying on the wire.exchangeT+12019-11-10never nullAAPL
callputLowCardinality(String)enum(date, figi, callput)One of 'C', 'P', 'CP'. See the callput note above for the CP synthesis rule.derivedT+12019-11-10never nullCP
net_i_sharesFloat64underlying-share equivalents(date, figi, callput)Institutional net share-equivalent flow for the callput bucket. Calculated as sum(net_i_qty × adj_shares_deliverable): the underlying-share equivalent of the traded contracts (standard contract = 100 shares, adjusted for corporate actions). On callput='CP' rows: C − P (bullish-conviction sign convention).modelT+12019-11-100.0 when no qualifying institutional flow18_450.0
net_r_sharesFloat64underlying-share equivalents(date, figi, callput)Retail net share-equivalent flow. Same formula (sum(net_r_qty × adj_shares_deliverable)) and CP-synthesis rule as net_i_shares.modelT+12019-11-100.0 when no qualifying retail flow4_120.0
net_i_premiumFloat64USD(date, figi, callput)Institutional net dollar premium flow. On CP rows: C − P.modelT+12019-11-100.0 when no qualifying institutional flow1_820_038.55
net_r_premiumFloat64USD(date, figi, callput)Retail net dollar premium flow.modelT+12019-11-100.0 when no qualifying retail flow312_040.0
net_i_deltaFloat64USD(date, figi, callput)Institutional net flow as USD notional × delta. Naturally signed; positive = net long-delta demand. On CP rows: C + P (delta is signed by option type; put deltas are stored negative, so a straight sum gives net directional demand).modelT+12019-11-100.0 when no qualifying institutional flow12_530_000.00
net_r_deltaFloat64USD(date, figi, callput)Retail net flow as USD notional × delta.modelT+12019-11-100.0 when no qualifying retail flow-870_000.00
net_i_gammaFloat64USD × gamma(date, figi, callput)Institutional net flow as USD notional × gamma. On CP rows: C + P (gamma is naturally signed by option type). Positive = net gamma demand.modelT+12019-11-100.0 when no qualifying institutional flow84_000.0
net_r_gammaFloat64USD × gamma(date, figi, callput)Retail net gamma flow.modelT+12019-11-100.0 when no qualifying retail flow12_100.0
net_i_vegaFloat64USD × vega(date, figi, callput)Institutional net flow as USD notional × vega. On CP rows: C + P. Positive = net long-vol demand.modelT+12019-11-100.0 when no qualifying institutional flow1_400_000.0
net_r_vegaFloat64USD × vega(date, figi, callput)Retail net vega flow.modelT+12019-11-100.0 when no qualifying retail flow-210_000.0
net_i_shares_21dmaFloat64underlying-share equivalents(date, figi, callput)Rolling sum of net_i_shares over the prior 21 trading days (inclusive of date), scoped to this callput.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations319_284.0
net_r_shares_21dmaFloat64underlying-share equivalents(date, figi, callput)Rolling 21-day sum of net_r_shares.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations68_040.0
net_i_premium_21dmaFloat64USD(date, figi, callput)Rolling 21-day sum of net_i_premium.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations25_294_500.0
net_r_premium_21dmaFloat64USD(date, figi, callput)Rolling 21-day sum of net_r_premium.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations5_040_252.0
net_i_delta_21dmaFloat64USD(date, figi, callput)Rolling 21-day sum of net_i_delta.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations206_220_000.0
net_r_delta_21dmaFloat64USD(date, figi, callput)Rolling 21-day sum of net_r_delta.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations-13_020_000.0
net_i_gamma_21dmaFloat64USD × gamma(date, figi, callput)Rolling 21-day sum of net_i_gamma.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations1_512_000.0
net_r_gamma_21dmaFloat64USD × gamma(date, figi, callput)Rolling 21-day sum of net_r_gamma.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations218_400.0
net_i_vega_21dmaFloat64USD × vega(date, figi, callput)Rolling 21-day sum of net_i_vega.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations24_780_000.0
net_r_vega_21dmaFloat64USD × vega(date, figi, callput)Rolling 21-day sum of net_r_vega.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations-3_780_000.0
z_net_i_shares_21dmaFloat64z-score(date, figi, callput)Z-score of net_i_shares_21dma against its trailing 2-year rolling mean and sample standard deviation, computed at query time. Rounded to 2 decimals on emit.derivedT+12019-11-10null when the trailing 2y window has fewer than 63 non-null observations or its sample stddev is 01.34
z_net_r_shares_21dmaFloat64z-score(date, figi, callput)Same construction as z_net_i_shares_21dma, applied to the retail 21dma.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 00.42
z_net_i_premium_21dmaFloat64z-score(date, figi, callput)Standardised institutional-premium 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 01.18
z_net_r_premium_21dmaFloat64z-score(date, figi, callput)Standardised retail-premium 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 0-0.24
z_net_i_delta_21dmaFloat64z-score(date, figi, callput)Standardised institutional-delta 21dma vs its 2y baseline. Masscrest's canonical "spike" predicate is z > 2 (2σ) or z > 3 (3σ) with callput = 'CP'.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 02.08
z_net_r_delta_21dmaFloat64z-score(date, figi, callput)Standardised retail-delta 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 0-0.55
z_net_i_gamma_21dmaFloat64z-score(date, figi, callput)Standardised institutional-gamma 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 00.86
z_net_r_gamma_21dmaFloat64z-score(date, figi, callput)Standardised retail-gamma 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 00.11
z_net_i_vega_21dmaFloat64z-score(date, figi, callput)Standardised institutional-vega 21dma vs its 2y baseline. Masscrest's ≤ −3σ vega tail is historically followed by ~1.6 vol-point IV declines over 21 days.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 0-1.42
z_net_r_vega_21dmaFloat64z-score(date, figi, callput)Standardised retail-vega 21dma vs its 2y baseline.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 00.30

Z-score precision

z_* fields are rounded to 2 decimals on emit. When comparing a client-side derived spike predicate (z > 2) against the rounded value, expect edge cases on low-vol names where the true unrounded z sits just under 2.00 but the rounded value equals 2.00.

CP synthesis is pre-computed, not query-time

The 'CP' rows are materialised at publish time, not derived at query time. Each (figi, callput) bucket has its own independent 21dma and 2y baseline trajectory; z_net_i_delta_21dma on the CP row is derived from CP's own trailing series, not from summing per-side z-scores.

grouped_flow_signal_01_day

What it is

Daily flow signals rolled up above the single-name level. Same transform chain as underlying_flow_signals_01_day (raw net, then 21-day trailing sum, then 2-year rolling z-score), but aggregated across groups: sector, industry, ETF, ADR, single-stock-aggregate, and market-wide. One row per (date, group_key, callput).

Why it exists

Single-name signals get noisy and hard to compare across the universe. A sector- or industry-level roll-up smooths the noise, surfaces regime shifts that show up as cross-stock coordination, and gives you a universe-level dial on the retail vs institutional gap. Masscrest's industry-aggregated long-short validation at /research/industry-flow-signal runs off this table.

How to use it

Filter on (date, group_key, callput). group_key is a single string column that holds either the canonical sector name (Technology, Financial Services, ...), the canonical industry name (Semiconductors, Software - Application, ...), or one of the four rollup literals (etf, adr, single_stock, all). The REST endpoint accepts the same values case-insensitively on group_type and echoes them back in group_key.

As with underlying_flow_signals_01_day: filter to one callput per query, and read the raw / 21dma / z-score column depending on what you're asking.

Examples

Sector-rotation scan. Pull group_type='sector', callput='CP', one trading day. Sort z_net_i_premium_21dma descending. Top rows are the sectors where institutional net premium over the trailing month is most extreme against the sector's own 2-year baseline. Compare against the bottom rows for rotation candidates.

Industry-level long/short screen. Daily cross-section on group_type='industry', callput='CP', filtered to n_figi >= 10 (drop thin-breadth days), long the top quintile of z_net_i_delta_21dma, short the bottom quintile. This is the construction the /research/industry-flow-signal validation uses.

  • Grain: (date, group_key, callput)

Group semantics

Query group_typegroup_key shape (on the row)Row count per (date, callput)Notes
sectorCanonical mixed-case sector name (e.g. Technology, Financial Services)~11 distinct valuesExcludes ETFs and ADRs. Figis missing a sector label drop from these rows.
industryCanonical mixed-case industry name (e.g. Semiconductors, Software - Application)~150 distinct values (matches the reference industry taxonomy on stock_mapping)Excludes ETFs and ADRs. Same drop-on-missing rule. Every industry-day is emitted (gap-free time series) with n_figi populated for downstream filtering (see "Thin-industry handling" below).
etfLiteral string etf1Aggregate over all figis with isEtf='true' in the latest-known phase (live or delisted).
adrLiteral string adr1Aggregate over all figis with isAdr='true' in the latest-known phase (live or delisted).
single_stockLiteral string single_stock1Everything else (non-ETF, non-ADR). Figis missing from stock_mapping are treated as single_stock.
allLiteral string all1Every figi in the flow universe, no filter.

Valid sector values

The ~11 distinct group_key strings emitted when group_type = 'sector'. Case-insensitive on input; canonical mixed-case on the wire. As-of 2026-08-14 snapshot; may drift as figis are reclassified or new sectors are added.

SectorDescription
Basic MaterialsCompanies extracting and processing raw commodities used across the economy: metals and mining, chemicals, forestry, and construction materials. Cyclically exposed to global industrial demand.
Communication ServicesFirms that carry information or entertainment to end users: telecom carriers, media conglomerates, interactive-media platforms, publishers, and gaming. Combines legacy telecom with digital-native content.
Consumer CyclicalBusinesses selling discretionary goods and services whose demand rises and falls with the business cycle: automakers, apparel, restaurants, travel, leisure, and specialty retail. Highly sensitive to consumer confidence.
Consumer DefensiveProducers of everyday essentials that consumers buy regardless of the economy: packaged food, beverages, tobacco, household staples, and discount retailers. Stable revenue through downturns.
EnergyThe oil, gas, coal and related supply chain: exploration and production, refining, midstream infrastructure, and oilfield services. Tied to commodity prices and global demand cycles.
Financial ServicesFirms that intermediate capital: banks, insurers, asset managers, brokerages, exchanges, and credit-services companies. Rate-sensitive and cycle-exposed.
HealthcareBusinesses across the health value chain: drug and biotech developers, medical-device makers, diagnostics labs, healthcare providers, health insurers, and pharma distributors. Mix of defensive demand and secular growth.
IndustrialsCapital goods and services that keep the economy moving: aerospace, defence, machinery, transportation, construction, and logistics. Cyclically exposed to capex spending.
Real EstateProperty-owning and property-servicing companies, dominated by REITs across residential, retail, office, industrial, healthcare, and specialty sub-types. Highly interest-rate-sensitive.
TechnologyHardware and software firms including semiconductors, application and infrastructure software, IT services, computer hardware, and consumer electronics. Growth-biased, with high cyclicality on the semiconductor side.
UtilitiesRegulated and independent providers of electricity, natural gas, water, and renewable power. Defensive cash flows, bond-like return profile.

Valid industry values

IndustryDescription
Advertising AgenciesAgencies that plan, create and place advertising across traditional and digital channels for brand and direct-response clients.
Aerospace & DefenseManufacturers of civil aircraft, military platforms, missiles, satellites and defence-electronics systems for governments and commercial airlines.
Agricultural - MachineryProducers of tractors, combines, harvesters and other equipment used in commercial farming.
Agricultural Farm ProductsGrowers, processors and marketers of grains, oilseeds, produce, meat and other primary agricultural output.
Agricultural InputsSuppliers of seeds, fertilisers, pesticides and other inputs consumed by farms.
Airlines, Airports & Air ServicesPassenger and cargo airlines, airport operators, ground-handling firms and other air-transport service providers.
AluminumMiners, smelters and rolled-product manufacturers of aluminium.
Apparel - Footwear & AccessoriesDesigners and makers of shoes, handbags, jewellery, watches and other fashion accessories.
Apparel - ManufacturersCompanies that design and produce clothing lines under owned or licensed brands.
Apparel - RetailRetailers selling clothing and accessories through stores or online, including specialty and off-price chains.
Asset ManagementFirms managing pooled investment vehicles (mutual funds, ETFs, separate accounts) across broad multi-asset strategies.
Asset Management - BondsManagers specialising in fixed-income mutual funds and bond ETFs.
Asset Management - CryptocurrencyManagers of cryptocurrency-linked funds, trusts and ETFs.
Asset Management - GlobalManagers of internationally-diversified equity and multi-asset funds.
Asset Management - IncomeManagers focused on dividend-equity and income-oriented fund products.
Asset Management - LeveragedSponsors of leveraged and inverse ETFs.
Auto - DealershipsRetailers of new and used vehicles, plus related service, parts and financing operations.
Auto - ManufacturersOEMs producing passenger cars, trucks and, increasingly, electric vehicles.
Auto - PartsSuppliers of components, systems and modules used in vehicle assembly and aftermarket repair.
Auto - Recreational VehiclesMakers of RVs, motorcycles, boats and other personal-use motorised recreational vehicles.
Banks - DiversifiedLarge multi-line banks combining consumer, commercial, investment-banking and wealth divisions.
Banks - RegionalBanks concentrated in a specific geographic footprint, focused on retail deposits and commercial lending.
Beverages - AlcoholicBrewers, distillers and vintners producing beer, spirits and packaged alcoholic drinks at scale.
Beverages - Non-AlcoholicProducers of soft drinks, bottled water, juices, energy drinks and other non-alcoholic packaged beverages.
Beverages - Wineries & DistilleriesCraft and specialty wine and spirits producers, generally smaller-scale than the mass-market alcoholic-beverage majors.
BiotechnologyCompanies developing novel therapeutics using biological processes, typically pre-commercial or single-product-focused.
BroadcastingOwners of TV and radio stations, local networks and syndicated content distributors.
Business Equipment & SuppliesManufacturers of office equipment, printers, copiers and workplace supplies.
ChemicalsProducers of commodity petrochemicals, plastics, industrial gases and basic chemical intermediates.
Chemicals - SpecialtyProducers of higher-value differentiated chemicals used in coatings, adhesives, catalysts and formulations.
CoalMiners and marketers of thermal and metallurgical coal.
Communication EquipmentMakers of network hardware, routers, switches and telecom infrastructure gear.
Computer HardwareManufacturers of PCs, servers, storage systems and other computing hardware.
ConglomeratesMulti-industry holding companies spanning several unrelated business lines.
ConstructionGeneral contractors and construction firms building commercial, industrial and infrastructure projects.
Construction MaterialsProducers of cement, aggregates, gypsum, insulation and other bulk building materials.
Consulting ServicesManagement, technology, HR and strategy consulting firms selling professional advisory services.
Consumer ElectronicsMakers of smartphones, tablets, audio devices, wearables and other consumer-facing electronic products.
CopperMiners and processors of copper ore and refined copper products.
Department StoresMulti-category retailers under a single store format spanning apparel, home and accessories.
Discount StoresMass-market retailers competing primarily on price across broad general merchandise.
Diversified UtilitiesUtilities operating across multiple regulated categories (electric, gas, water) rather than a single service.
Drug Manufacturers - GeneralLarge pharmaceutical companies with broad marketed portfolios and multi-therapeutic pipelines.
Drug Manufacturers - Specialty & GenericPharma companies focused on generic drugs, biosimilars or niche specialty therapeutic areas.
Education & Training ServicesFor-profit universities, career schools, tutoring and corporate training providers.
Electrical Equipment & PartsManufacturers of electrical components, motors, transformers, cables and industrial electrical systems.
Electronic Gaming & MultimediaVideo-game publishers, developers, esports operators and interactive-entertainment firms.
Engineering & ConstructionFirms providing engineering-design and heavy-construction services for large infrastructure projects.
EntertainmentFilm, TV and content-production studios and integrated entertainment companies.
Environmental ServicesWaste-water treatment, environmental consulting and remediation service providers.
Financial - Capital MarketsInvestment banks, broker-dealers and trading firms operating in equity, fixed-income and derivatives markets.
Financial - ConglomeratesDiversified financial holding companies spanning several finance subsectors.
Financial - Credit ServicesConsumer-finance companies, credit-card networks, payment processors and buy-now-pay-later providers.
Financial - Data & Stock ExchangesExchange operators, index providers, financial-data vendors and market-infrastructure firms.
Financial - DiversifiedMiscellaneous financial-services companies that do not fit the more specific finance subsectors.
Financial - MortgagesMortgage originators, servicers and secondary-market intermediaries.
Food ConfectionersProducers of chocolate, candy, chewing gum and other confectionery products.
Food DistributionWholesalers distributing food and related goods to restaurants, retailers and institutional customers.
Furnishings, Fixtures & AppliancesMakers of home furniture, bedding, kitchen appliances and household fixtures.
Gambling, Resorts & CasinosOperators of casinos, integrated resorts and online-gambling platforms.
General TransportationDiversified transportation companies that do not fit the more specific rail, trucking or air subsectors.
GoldMiners and refiners of gold ore and physical gold, plus gold-focused streaming and royalty firms.
Grocery StoresTraditional supermarkets and grocery chains selling food and household goods.
Hardware, Equipment & PartsGeneral hardware and industrial-equipment manufacturers and distributors.
Home ImprovementBig-box home-improvement retailers and specialty tool, paint and hardware chains.
Household & Personal ProductsManufacturers of cleaning products, personal-care items and consumer packaged household goods.
Independent Power ProducersNon-utility power generators selling electricity into wholesale or contracted markets.
Industrial - DistributionWholesalers of industrial equipment, parts, fasteners and MRO supplies.
Industrial - Infrastructure OperationsOperators of ports, pipelines, terminals and other industrial-infrastructure assets.
Industrial - MachineryManufacturers of heavy machinery for construction, mining, agriculture and industrial processes.
Industrial - Pollution & Treatment ControlsProviders of air, water and industrial pollution-control equipment and services.
Industrial - SpecialtiesSpecialty industrial firms in niche categories that do not fit broader industrial subsectors.
Industrial MaterialsProducers of steel-alternatives, industrial ceramics, composites and other engineered materials.
Information Technology ServicesIT-consulting, systems-integration, outsourcing and managed-services firms.
Insurance - BrokersInsurance and reinsurance brokers acting as intermediaries between clients and underwriters.
Insurance - DiversifiedMulti-line insurers writing across life, P&C and other coverage types.
Insurance - LifeInsurers focused on individual and group life insurance, plus annuity products.
Insurance - Property & CasualtyInsurers writing property, auto, liability and other short-tail coverage.
Insurance - ReinsuranceFirms providing insurance to primary insurers to cover concentrated or catastrophic risk.
Insurance - SpecialtyInsurers focused on niche or hard-to-place risks (marine, aviation, cyber, professional liability).
Integrated Freight & LogisticsMulti-modal freight and logistics operators spanning trucking, rail, air and ocean.
Internet Content & InformationDigital-content platforms, search engines, social networks and online-information providers.
Investment - Banking & Investment ServicesFull-service investment banks and firms providing M&A, underwriting and advisory services.
LeisureManufacturers of leisure goods, hobby products, toys and personal-recreation equipment.
Luxury GoodsProducers of high-end fashion, jewellery, watches, leather goods and other luxury consumer categories.
Manufacturing - Metal FabricationFirms fabricating metal parts, structures and assemblies for industrial and consumer use.
Manufacturing - MiscellaneousDiversified manufacturers that do not fit more specific industrial subsectors.
Manufacturing - TextilesProducers of yarn, fabric and finished textiles for apparel and industrial applications.
Manufacturing - Tools & AccessoriesMakers of power tools, hand tools and related industrial and consumer tool accessories.
Marine ShippingOcean-freight carriers operating tankers, dry-bulk vessels and container ships.
Media & EntertainmentDiversified media conglomerates spanning television, film, publishing and digital content.
Medical - Care FacilitiesHospital operators, nursing-home chains and specialty care-facility providers.
Medical - DevicesManufacturers of medical implants, surgical tools, monitoring and therapeutic devices.
Medical - Diagnostics & ResearchClinical-diagnostics labs, life-science research tools and diagnostic-imaging providers.
Medical - DistributionWholesale distributors of drugs, medical supplies and healthcare products.
Medical - Equipment & ServicesProviders of medical equipment plus related installation, maintenance and services.
Medical - Healthcare Information ServicesHealth-IT firms providing electronic health records, clinical software and data-analytics platforms.
Medical - Healthcare PlansManaged-care organisations and health-insurance plans covering employer, individual and government populations.
Medical - Instruments & SuppliesManufacturers of medical instruments, consumables and disposable healthcare supplies.
Medical - PharmaceuticalsBroadly-focused pharmaceutical companies not classified under the more specific drug-manufacturer subsectors.
Medical - SpecialtiesSpecialty medical companies operating in niche healthcare categories.
Oil & Gas DrillingContract drilling firms operating onshore and offshore rigs for oil-and-gas producers.
Oil & Gas EnergyDiversified oil-and-gas firms that do not fit the more specific upstream, midstream or downstream categories.
Oil & Gas Equipment & ServicesOilfield-services providers offering drilling, completion and reservoir-management equipment and expertise.
Oil & Gas Exploration & ProductionUpstream producers exploring for and producing crude oil and natural gas.
Oil & Gas IntegratedIntegrated majors operating across upstream, midstream and downstream oil-and-gas businesses.
Oil & Gas MidstreamPipelines, storage terminals and processing operators moving oil and gas from wellhead to market.
Oil & Gas Refining & MarketingDownstream refiners and fuel-marketing companies converting crude into gasoline, diesel and petrochemical feedstocks.
Other Precious MetalsMiners of platinum, palladium and other precious metals not classified under gold or silver.
Packaged FoodsProducers of branded packaged and processed foods sold through retail and foodservice channels.
Packaging & ContainersManufacturers of paper, plastic, glass and metal packaging for consumer and industrial products.
Paper, Lumber & Forest ProductsProducers of pulp, paper, lumber and other wood-based industrial and consumer products.
Personal Products & ServicesPersonal-care product makers, beauty firms and personal-services businesses.
PublishingPublishers of books, newspapers, magazines and other periodical content in print and digital.
REIT - DiversifiedREITs owning property portfolios spanning multiple real-estate categories.
REIT - Healthcare FacilitiesREITs owning hospitals, medical-office buildings, senior housing and other healthcare properties.
REIT - Hotel & MotelREITs owning hotel, motel and hospitality properties.
REIT - IndustrialREITs owning warehouses, logistics facilities and light-industrial properties.
REIT - MortgageMortgage REITs earning spread on residential and commercial mortgage assets rather than owning property directly.
REIT - OfficeREITs owning office buildings across urban and suburban markets.
REIT - ResidentialREITs owning apartment buildings, single-family rentals and manufactured-home communities.
REIT - RetailREITs owning shopping malls, strip centres and standalone retail properties.
REIT - SpecialtyREITs owning niche property types such as data centres, cell towers, self-storage and infrastructure.
RailroadsClass I and short-line freight-rail operators plus passenger-rail companies.
Real Estate - DevelopmentProperty developers building residential, commercial and mixed-use projects for sale or lease.
Real Estate - DiversifiedDiversified real-estate operating companies not structured as REITs.
Real Estate - ServicesReal-estate brokerages, property-management firms and title-and-appraisal services.
Regulated ElectricRate-regulated electric utilities serving retail customers in defined service territories.
Regulated GasRate-regulated natural-gas distribution utilities.
Regulated WaterRate-regulated water and wastewater utilities.
Renewable UtilitiesUtilities and power generators focused on solar, wind, hydro and other renewable-energy assets.
Rental & Leasing ServicesFirms renting equipment, vehicles and other assets to industrial and consumer customers.
Residential ConstructionHomebuilders constructing single-family homes and residential communities for sale.
RestaurantsRestaurant operators and franchisors across quick-service, casual-dining and fine-dining categories.
Security & Protection ServicesProviders of guarding, cash-in-transit, alarm-monitoring and security-technology services.
SemiconductorsDesigners and manufacturers of integrated circuits, memory, logic chips and related semiconductor equipment.
Shell CompaniesPublicly-listed holding entities without significant operations, often SPACs or blank-check vehicles.
SilverMiners and refiners of silver ore and silver products.
Software - ApplicationVendors of packaged and SaaS application software for business, industry-vertical and consumer use.
Software - InfrastructureVendors of infrastructure software including databases, operating systems, security and developer tools.
Software - ServicesSoftware-enabled services firms delivering platform-hosted business solutions.
SolarManufacturers of solar panels, inverters and installers of solar-power systems.
Specialty Business ServicesBusiness-services firms in specialty categories (data processing, marketing services, testing, inspection).
Specialty RetailRetailers focused on specific product categories such as electronics, sporting goods, jewellery or auto parts.
Staffing & Employment ServicesTemporary-staffing, executive-search and human-capital-management firms.
SteelIntegrated and mini-mill producers of carbon and specialty steel products.
Technology DistributorsDistributors of IT hardware, software and networking products to resellers and enterprise buyers.
Telecommunications ServicesWireless and wireline telecom carriers providing voice and data services to consumers and businesses.
TobaccoCigarette manufacturers plus producers of cigars, smokeless tobacco and next-generation nicotine products.
Travel LodgingHotel and lodging operators and franchisors across the value, mid-scale and luxury segments.
Travel ServicesOnline travel agencies, tour operators and travel-booking platforms.
TruckingLong-haul and less-than-truckload freight-trucking companies.
UraniumMiners of uranium ore and producers of nuclear-fuel feedstock.
Waste ManagementSolid-waste collection, disposal, recycling and hazardous-waste management firms.

Signal formula

  • _21dma = 21-trading-day rolling SUM of the raw group-level net, PARTITION BY (group_key, callput). (Historically shipped as a rolling mean; arithmetically equivalent for the z-score, since the sum is just the mean multiplied by 21. The distributed series is now the sum.)
  • z_net_*_21dma = z-score of _21dma against its 2-year (504 trading day) trailing mean and sample standard deviation, computed at query time. Minimum 63 non-null observations in the trailing window required, else null. Rounded to 2 decimals on emit.

Every industry-day is emitted with populated rolling / z fields. The 21dma and 2y baseline include ALL days regardless of n_figi. Thin days contribute their (smaller) raw values to the trailing window, so each industry's z is calibrated against its own historical breadth mix and populates on every day. Cross-industry z magnitudes aren't directly comparable when breadth profiles differ meaningfully; filter on n_figi at query time if comparability matters. See "Thin-industry handling" below.

Aggregate identities

  • single_stock + etf + adr = all per (date, callput), 0 residual.
  • sum(sector) ≈ single_stock per (date, callput); small residual (< 1% of gross flow) attributable to single-stock figis without a sector label.
  • Because sector / industry exclude ETFs and ADRs, sum(sector) ≠ all. The difference is precisely the etf + adr gross flow. Historical rollups produced before 2026-08-11 rolled ETFs+ADRs into sector; those runs have been superseded.
  • sum(industry) ≈ single_stock per (date, callput). Every industry row (thin or full-breadth) contributes its raw flow to the sum, so this identity holds tighter than it did before 2026-08-28.

Thin-industry handling

Every group with any flow on a given date is emitted, so the time series is gap-free. Every row (thin or full-breadth) carries populated raw daily nets AND populated rolling / z fields:

  • n_figi is populated with the honest constituent count (visible in the response); consumers filter thin-breadth days at query time.
  • Raw daily nets (net_i_shares, net_r_shares, ..., net_r_vega) are populated with the actual group sum.
  • Rolling / derived columns (_21dma, z_*) are populated on every row. The 21dma and 2y baseline include ALL days regardless of n_figi. Thin days contribute their (smaller) raw values to the trailing window and to the 2y μ/σ used to z-score. Each industry's z-score is therefore calibrated against its own historical breadth mix: a chronically-thin industry's baseline is set to its own thin-day scale, so z fires when flow is unusually large for that industry. Cross-industry z magnitudes aren't directly comparable when breadth profiles differ meaningfully (a +2σ on a 5-FIGI industry is not directly comparable to a +2σ on a 50-FIGI industry). Filter on n_figi at query time when magnitude comparability matters.

Classification is not strictly PIT

The underlying flow data is point-in-time, but each figi's sector, industry, and ETF / ADR classification uses the most recent labelling rather than the label in force at the time. META, for example, rolls up under Communication Services for its entire history even though it was classified as Technology in 2019. This keeps historical rollups comparable under a single consistent scheme as companies drift between sectors over time.

callput semantics

Every row is scoped to one callput bucket. C = calls only, P = puts only, CP = pre-computed combined view. Two rules apply on the CP row, split by metric family: size measures (shares, premium) are C − P (unsigned magnitudes, sign-flipped on puts to produce a directional bullish-minus-bearish net); greeks (delta, gamma, vega) are C + P (already signed by option type on the C and P rows, so a straight sum captures net book exposure). Rolling windows partition by (group_key, callput); each callput row has its own independent trailing 21dma and 2y baseline, so z(C) + z(P) ≠ z(CP). Filter to exactly one callput per query or triple-count.

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, group_key, callput)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
group_keyLowCardinality(String)textkeyCanonical mixed-case sector or industry name (e.g. Communication Services, Semiconductors); or the literal etf / adr / single_stock / all for the rollup branches. Single key column, the same value you pass in the REST group_type query parameter.derivedT+12019-11-10never nullTechnology
callputLowCardinality(String)enumkeyOne of C, P, CP. See the callput note above.derivedT+12019-11-10never nullCP
n_figiNullable(Int64)count(date, group_key, callput)Number of distinct FIGIs contributing to this group-day. Every row is emitted with raw nets AND rolling / z fields populated; the 21dma and 2y baseline include all days regardless of n_figi, so each industry's z is calibrated against its own historical breadth mix. Consumers filter on n_figi at query time when z-magnitude comparability across breadth matters. Sector, etf, adr, single_stock, and all branches always carry n_figi >> 10; only certain industries can dip below.derivedT+12019-11-10never null in practice24
net_i_sharesFloat64underlying-share equivalents(date, group_key, callput)Institutional net share-equivalent flow, summed across every figi in this grouping. On CP rows: C − P (bullish-conviction sign convention). Populated on every emitted row (even n_figi < 10 days).modelT+12019-11-100.0 when no qualifying institutional flow1_820_000.0
net_r_sharesFloat64underlying-share equivalents(date, group_key, callput)Retail net share-equivalent flow, same CP-synthesis rule.modelT+12019-11-100.0 when no qualifying retail flow412_000.0
net_i_premiumFloat64USD(date, group_key, callput)Institutional net dollar premium flow.modelT+12019-11-100.0 when no qualifying institutional flow182_000_000.0
net_r_premiumFloat64USD(date, group_key, callput)Retail net dollar premium flow.modelT+12019-11-100.0 when no qualifying retail flow31_000_000.0
net_i_deltaFloat64USD(date, group_key, callput)Institutional net USD × delta flow. On CP rows: C + P (delta is signed by option type; put deltas are stored negative). Positive = net long-delta demand across the grouping.modelT+12019-11-100.0 when no qualifying institutional flow1_250_000_000.0
net_r_deltaFloat64USD(date, group_key, callput)Retail net USD × delta flow.modelT+12019-11-100.0 when no qualifying retail flow-87_000_000.0
net_i_gammaFloat64USD × gamma(date, group_key, callput)Institutional net USD × gamma. On CP rows: C + P.modelT+12019-11-100.0 when no qualifying institutional flow8_400_000.0
net_r_gammaFloat64USD × gamma(date, group_key, callput)Retail net USD × gamma.modelT+12019-11-100.0 when no qualifying retail flow1_210_000.0
net_i_vegaFloat64USD × vega(date, group_key, callput)Institutional net USD × vega. On CP rows: C + P. Positive = net long-vol demand across the grouping.modelT+12019-11-100.0 when no qualifying institutional flow140_000_000.0
net_r_vegaFloat64USD × vega(date, group_key, callput)Retail net USD × vega.modelT+12019-11-100.0 when no qualifying retail flow-21_000_000.0
net_*_21dmaFloat64(matches base metric)(date, group_key, callput)21-trading-day rolling SUM of the corresponding raw net, scoped to this (group_key, callput). Ten of these, one per raw metric.derivedT+12019-11-10null when the trailing 21-day window has fewer than 21 non-null observations(varies by metric)
z_net_*_21dmaFloat64z-score(date, group_key, callput)Z-score of the corresponding 21dma against its trailing 2-year (504-session) rolling mean and sample standard deviation, computed at query time. Rounded to 2 decimals on emit. Masscrest's canonical spike predicate for the group-level table is z_net_i_delta_21dma > 2 AND callput = 'CP' (or > 3 for 3σ), same rule as the underlying-level table. Ten of these.derivedT+12019-11-10null when the trailing 2y window is unwarmed or its sample stddev is 0(varies)

px_01_day

What it is

Daily OHLCV for the US single-stock and ETF universe. One row per (date, traded_underlying). Prices are unadjusted (contemporaneous print values).

Why it exists

You can't build a clean option-flow story without a matching price context. Masscrest ships the same daily close used internally for signal construction, keyed to the same ticker / FIGI layer as the flow tables, so there's no cross-vendor join drift between flow and price.

How to use it

The table isn't exposed directly over REST or MCP. The adjusted price columns (adj_close, adj_open, adj_volume, total_turnover) are LEFT-joined onto the underlying_option_flows endpoints: pass traded_underlying=<TICKER> and callput='CP' to get_underlying_option_flows_daily (MCP) or GET /v0/underlying_option_flows/01_day (REST) and you get one row per date with prices attached to the flow payload.

For bulk historical pulls, the parquet is fanned out across the daily buckets your plan includes: gs://prod-us-option-daily-underlying/px_data/v0/01_day/, gs://prod-us-option-daily-contract/px_data/v0/01_day/, or the trial subset at gs://trial-us-option-daily-underlying/px_data/v0/01_day/.

Unadjusted semantics

Prices and volume are unadjusted. To compute split-adjusted series (safe cross-split returns, share-count normalisation, historical strike comparisons), join to split_factors on figi with date BETWEEN valid_from AND valid_to and apply cum_split_factor. Keeping the two sides separate lets you reload px_01_day and split_factors independently and gives you an explicit knob when you need to invert an adjustment.

  • Grain: (date, traded_underlying)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
dateDatetrading day (UTC)(date, traded_underlying)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
traded_underlyingStringticker(date, traded_underlying)Point-in-time listed ticker on date. Dotted dual-class form (BRK.B). Use the (ticker, date) pair as the join key; the same ticker can be recycled across entities over time, so resolve to figi via stock_mapping before any long-horizon series work.exchangeT+12019-11-10never nullBRK.B
openFloat64USD(date, traded_underlying)Session opening print (unadjusted, contemporaneous). No split adjustment applied.exchangeT+12019-11-10null on non-trading days for the underlying (delistings, halts)223.85
closeFloat64USD(date, traded_underlying)Session closing print (unadjusted, contemporaneous). No split adjustment applied.exchangeT+12019-11-10null on non-trading days for the underlying224.31
volumeFloat64shares(date, traded_underlying)Session share volume (unadjusted, contemporaneous). No split adjustment applied (a share count on a pre-split day is a real economic quantity that doesn't rescale post-split).exchangeT+12019-11-10null on non-trading days52_308_400.0
turnoverFloat64USD(date, traded_underlying)Dollar turnover of the underlying on date (sum(price × volume) across all intraday prints). Dollar amounts are unit-invariant across splits.exchangeT+12019-11-10null on non-trading days11_724_390_012.0

px_10_min

What it is

10-minute OHLCV bars for the US single-stock and ETF universe. Same universe and join key as px_01_day, one row per (date, ten_min_timeframe, traded_underlying). Prices unadjusted.

Why it exists

Intraday flow is only useful against intraday price. Shipping the bars directly off the same feed keeps the flow and price sides in lockstep at the 10-minute cadence so bucket joins are clean.

How to use it

Not exposed directly. The adjusted price columns (adj_close, adj_open, adj_volume, total_turnover) are LEFT-joined onto get_underlying_option_flows_10min (MCP), intraday_flow_day (MCP fat tool for a single symbol × single day), and GET /v0/underlying_option_flows/10_min (REST). The join emits one row per (date, ten_min_timeframe, figi) with prices attached.

For bulk historical pulls, parquet lives on the 10-min bucket your plan includes: gs://prod-us-option-tenmin-underlying/px_data/v0/10_min/ or gs://prod-us-option-tenmin-contract/px_data/v0/10_min/.

Bucket boundary convention

ten_min_timeframe is the end of the 10-minute bucket, stored as a naive New York wall-clock DateTime. Bucket covers (ten_min_timeframe − 10 min, ten_min_timeframe]. First RTH bucket ends at 09:40:00, last at 16:00:00 (39 buckets per full session). On-boundary 1-minute bars (e.g. 10:00:00 close) land in the closing bucket, not the opening bucket of the next.

Coverage

Regular trading hours only (09:30 to 16:00 ET). No pre-market, no after-hours. Halted sessions produce no rows for the halted intervals.

Unadjusted semantics

Prices and volume are unadjusted. To compute split-adjusted series join to split_factors on figi with date BETWEEN valid_from AND valid_to and apply cum_split_factor. Keeping the two sides separate lets you reload px_10_min and split_factors independently and gives you an explicit knob when you need to invert an adjustment.

  • Grain: (date, ten_min_timeframe, traded_underlying)

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
ten_min_timeframeDateTimeNY wall-clock, bucket close(date, ten_min_timeframe, traded_underlying)End timestamp of the 10-min bucket (naive NY-time). Bucket covers (ten_min_timeframe − 10 min, ten_min_timeframe].exchangeT+12019-11-10never null2026-07-24 09:40:00
dateDatetrading day (UTC)(date, ten_min_timeframe, traded_underlying)Session date. US equity trading calendar.exchangeT+12019-11-10never null2026-07-24
traded_underlyingStringticker(date, ten_min_timeframe, traded_underlying)Point-in-time listed ticker on date. Dotted dual-class form. Use (ticker, date) as the join key; resolve to figi via stock_mapping for any long-horizon series work.exchangeT+12019-11-10never nullAAPL
openFloat64USD(date, ten_min_timeframe, traded_underlying)Unadjusted price of the first 1-minute bar in the bucket. The source 1-minute feed carries open and close only (no high/low), so bar-internal high/low is not available at this grain.exchangeT+12019-11-10null when the underlying has no trading activity in the bucket223.90
closeFloat64USD(date, ten_min_timeframe, traded_underlying)Unadjusted price of the last 1-minute bar in the bucket.exchangeT+12019-11-10null when the underlying has no trading activity in the bucket224.02
volumeFloat64shares(date, ten_min_timeframe, traded_underlying)Underlying share volume within the bucket. Unadjusted (contemporaneous).exchangeT+12019-11-10null when the underlying has no trading activity1_204_812.0
turnoverFloat64USD(date, ten_min_timeframe, traded_underlying)Dollar turnover of the underlying within the bucket. Dollar amounts are unit-invariant across splits.exchangeT+12019-11-10null when the underlying has no trading activity270_881_400.0

stock_mapping

What it is

A lookup table from FIGI to listed ticker, with company name, sector, industry, and ETF / ADR flags attached. One row per contiguous period during which a given FIGI traded under a given ticker.

Why it exists

Tickers move. Companies rename themselves, merge, get taken private, delist. Tickers get recycled. A sector breakdown or historical PnL series keyed on the ticker alone is wrong whenever one of those events happens. Masscrest pins everything to FIGI (the stable company identifier) first and resolves the ticker as a function of the date. This table is where that resolution lives.

How to use it

Join to any flow or price table on figi with date BETWEEN phase_start AND phase_end. That gives you the ticker, company name, sector, industry, and flags that were true on that trading day. If you want a single row per entity today, filter phase_end = '2099-12-31' (the open-phase sentinel).

Examples

Facebook to Meta. One company, one FIGI (BBG000MM2P62), two rows. FB from inception through 2022-06-08, META from 2022-06-09 onward. A flow query joined on this FIGI returns a clean history across the rename with no manual ticker-swap logic.

XOM restructure. On 2026-07-07 Exxon reorganised into ExxonMobil Holdings. Same traded_underlying='XOM', new FIGI. Two rows with different FIGIs under the same ticker. A query filtered by traded_underlying='XOM' returns both; the PIT join on date picks the right one for each trading day.

Quirks

  • ETFs. sector and industry describe the fund's issuer entity (typically Financial Services), not the underlying exposure. For ETF-aggregate flow use the etf rollup on grouped_flow_signal_01_day.
  • ADRs. sector and industry describe the foreign company's actual business. HDB is Financial Services (HDFC Bank), BABA is Consumer Cyclical (Alibaba). Filter on isAdr if you need to separate US-domiciled sector totals from foreign ADRs.
  • Rollup already clean. The served grouped_flow_signal_01_day rollup excludes ETFs and ADRs from its sector / industry group_keys and routes them to their own etf and adr buckets. The two quirks above only matter when joining stock_mapping directly.

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
traded_underlyingStringticker(figi, traded_underlying, phase_start)Point-in-time listed ticker during [phase_start, phase_end]. Dotted dual-class form (BRK.B).exchangen/a2019-11-10never nullFB
figiString(12)identifier(figi, traded_underlying, phase_start)Composite FIGI for the underlying entity. Stable across ticker changes.exchangen/an/anever nullBBG000NDYB67
phase_startDateboundary(figi, traded_underlying, phase_start)First trading date this (ticker, figi) pair was active. 2019-11-10 is a "no constraint" sentinel for entities with unknown pre-history.exchangen/an/anever null2012-05-18
phase_endDateboundary(figi, traded_underlying, phase_start)Last trading date this (ticker, figi) pair was active. 2099-12-31 sentinel means the phase is currently open (still trading under this ticker today).exchangen/an/anever null2022-06-08
companyNameStringname(figi, traded_underlying, phase_start)Registered legal / operating name for the entity during this phase.exchangedaily refreshn/anull when no vendor record is available for the phaseMeta Platforms, Inc.
sectorStringsector(figi, traded_underlying, phase_start)Sector classification for the entity during this phase. Investor-oriented 11-sector taxonomy (companies grouped by demand-cycle and macro exposure, not by production process).exchangedaily refreshn/anull when unresolvedTechnology
industryStringindustry(figi, traded_underlying, phase_start)Industry classification for the entity during this phase. Investor-oriented ~150-industry taxonomy nested under the 11 sectors.exchangedaily refreshn/anull when unresolvedSoftware - Application
isEtfStringflag ('true' / 'false')(figi, traded_underlying, phase_start)Whether the underlying is an ETF or ETP. String-typed for compatibility with the customer-facing endpoints.exchangedaily refreshn/a'false' when unresolved (defaults to non-ETF)false
isAdrStringflag ('true' / 'false')(figi, traded_underlying, phase_start)Whether the underlying is an American Depositary Receipt.exchangedaily refreshn/a'false' when unresolvedfalse
isActivelyTradingStringflag ('true' / 'false')(figi, traded_underlying, phase_start)Whether the entity is actively trading today. Falls back to 'true' when the phase is still open (phase_end = 2099-12-31) and the vendor lacks an explicit record, 'false' otherwise.exchangedaily refreshn/anever null (default fallback based on phase_end)true

contract_id_mapping

What it is

A stable Masscrest identifier (contract_id) for every option contract, plus the per-phase shares-deliverable, strike, and symbol as they stood during each corporate-action epoch. One row per contract phase.

Why it exists

Reverse splits, spinoffs, and M&A baskets change what an option delivers without necessarily changing the strike or the symbol. After a 1-for-10 reverse split, an existing XYZ 50 call still has strike 50 but now delivers 10 shares instead of 100. The clearing house usually numerates the adjusted root (XYZ1) and a fresh standard XYZ 50 call relists shortly after, delivering the usual 100 shares. Two contracts with identical callput / strike / expiration and overlapping symbols, radically different payoffs. Keying on strike + symbol alone silently mixes them. contract_id disambiguates.

Forward splits are cleaner: the strike divides by the split ratio and the deliverable stays at 100 shares, so pre-split and post-split rows carry different strikes under the same contract_id without any ambiguity on the key.

How to use it

In most cases you don't. The REST and MCP option_flows_* responses already carry contract_id, adj_shares_deliverable, figi, and traded_underlying on every row, resolved server-side.

You need to join this table in two cases:

  1. Reading raw flat-file parquet. The base flat-file tables drop mapping columns to keep file sizes small. Join on (traded_symbol, expirationdate, callput, traded_strikeprice) with date BETWEEN phase_start AND phase_end to attach contract_id, underlying_figi, adj_shares_deliverable, and traded_underlying.
  2. Reconciling your own option books to Masscrest flow. Look up the contract_id for the position on the trade date, then filter Masscrest flow on that contract_id to get the full history of that economic contract across any rename or split.

Examples

Reverse split (SOXS, 3x-inverse semiconductor ETF). SOXS reverse-splits often. After a 1-for-10 reverse split, the existing SOXS 50 call keeps strike 50 but drops to 10 shares deliverable. The clearing house numerates the adjusted root to SOXS1. A fresh SOXS 50 call relists shortly after at the standard 100 deliverable. In contract_id_mapping you get two separate contract_ids on the same (50, C, expiration) key: the adjusted series with traded_symbol=SOXS1, adj_shares_deliverable=10, cumulative_split_factor=0.1, and the fresh series with traded_symbol=SOXS, adj_shares_deliverable=100, cumulative_split_factor=1.0. Join on contract_id and the two never get confused.

Forward split (NVDA 10-for-1, June 2024). All open NVDA options had their strikes divided by 10 on the ex-date. The Jan 2025 500 call becomes a Jan 2025 50 call. Two phase rows under the same contract_id: pre-split with traded_strikeprice=500, post-split with traded_strikeprice=50. adj_shares_deliverable=100 on both (forward splits don't touch the deliverable). A PnL series joined on contract_id tracks the position clean across the ex-date with no manual strike scaling.

Relation to split_history

split_history carries the underlying-level split factor per stock. contract_id_mapping carries the contract-level deliverable per contract. The two agree on reverse splits (adj_shares_deliverable = 100 × cumulative_split_factor, both < 1.0). They diverge on forward splits (deliverable stays at 100 while cumulative_split_factor climbs above 1.0, because the strike absorbs the adjustment) and on M&A baskets (deliverable becomes a bespoke cash+share mix not a clean multiple of anything).

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
contract_idStringidentifier(contract_id, phase_start, phase_end)Masscrest-built stable contract identifier. Deterministic string built from (root_symbol, expiration, callput, root-strike × 1000, incarnation counter) where the trailing _NN counter indexes the nth economic contract to use this (symbol, expiration, callput, strike) key over time. Most contracts take _00 (first and only economic contract on this key); the counter increments only when the symbol is re-listed for a new contract after the prior one was adjusted away. The same contract_id value follows the economic contract across every corporate action in its chain.deriveddaily refreshn/anever nullAAPL 260815C000215000_00
traded_symbolStringticker(contract_id, phase_start, phase_end)Symbol (no-dot form: BRKB, not BRK.B). Post-corporate-action, the root can change (numerated forms like AAPL1); this column echoes the declared root for the phase.exchangedaily refreshn/anever nullAAPL
traded_underlyingStringticker(contract_id, phase_start, phase_end)Point-in-time listed equity ticker for the deliverable underlying during the phase. Dotted dual-class form (BRK.B). Resolved via join to stock_mapping on root_symbol.deriveddaily refreshn/anever nullAAPL
root_symbolStringticker(contract_id, phase_start, phase_end)Listing root of the contract (first-appearance symbol), stable across the contract's corporate-action lineage. Used as the join key into stock_mapping to recover the point-in-time listed traded_underlying.deriveddaily refreshn/anever nullAAPL
root_strikepriceFloat64USD(contract_id, phase_start, phase_end)Original listed strike at contract inception, stable across the lineage. traded_strikeprice is the strike adjusted for the phase; root_strikeprice is the strike before any corporate-action adjustment. On standard non-adjusted contracts the two are equal.deriveddaily refreshn/anever null215.00
underlying_figiStringidentifier(contract_id, phase_start, phase_end)Composite FIGI of the deliverable underlying for this phase. Carries the full point-in-time entity binding directly on the row, so no stock_mapping hop is needed for FIGI resolution. Null when the underlying is cash-settled (index) or when the historical phase pre-dates FIGI coverage.deriveddaily refreshn/anull on index-settled rows / pre-coverage historical phasesBBG000B9XRY4
expirationdateDateboundary(contract_id, phase_start, phase_end)Contract expiration date (US equity calendar).exchangen/an/anever null2026-08-15
callputString(1)option type(contract_id, phase_start, phase_end)'C' (call) or 'P' (put).exchangen/an/anever nullC
traded_strikepriceFloat64USD(contract_id, phase_start, phase_end)Contract strike as declared for the phase. Post-split-adjusted contracts show the adjusted strike; pre-split contracts show the raw strike.exchangen/an/anever null215.00
adj_shares_deliverableFloat64shares per contract(contract_id, phase_start, phase_end)Shares conversion adjustment for the phase. Standard equity option delivers 100 shares, so 100.0. Post-corporate-action phases carry the declared per-contract share count (fractional, cash+share baskets, etc.). Post-split standard contracts equal 100 × cumulative_split_factor.exchangedaily refreshn/anever null100.0
cumulative_split_factorFloat64multiplier(contract_id, phase_start, phase_end)Cumulative split factor applied to the underlying over the contract's lineage from inception through the end of this phase. 1.0 on contracts that have not seen a split. On reverse-split-only mechanics, adj_shares_deliverable = 100 × LEAST(cumulative_split_factor, 1.0). Forward splits leave the deliverable at 100.deriveddaily refreshn/anever null1.0
phase_startDateboundary (inclusive)(contract_id, phase_start, phase_end)First trading date this phase applied to. Inclusive. Grounded in the contract's first listing date; clipped to the 2019-11-10 data-coverage floor when the first-listing date pre-dates it.deriveddaily refreshn/anever null2019-11-10
phase_endDateboundary (inclusive)(contract_id, phase_start, phase_end)Last trading date this adj_shares_deliverable applied to. Inclusive. On the final (or only) phase of a contract this equals the contract's expirationdate; earlier phases end on the trading day before a corporate-action takes effect.deriveddaily refreshn/anever null2024-05-17
is_indexBoolflag(contract_id, phase_start, phase_end)Whether the underlying is a cash-settled index. Filtered out at export, so always false on the customer surface.exchangen/an/anever nullfalse

split_factors

What it is

Cumulative split-adjustment factors per FIGI, structured as a slowly-changing dimension. One row per continuous phase with a given factor; non-overlapping intervals covering each FIGI's timeline. date BETWEEN valid_from AND valid_to matches exactly one row per (figi, date).

Why it exists

px_01_day and px_10_min ship contemporaneous prints, not adjusted prices. You need an adjustment factor to compare prices across a split or to normalise share counts to current units. Shipping the factor as a separate SCD2 table (instead of baking an adj_close into the price table) means you can invert Masscrest's adjustment to recover the raw print whenever you need it, and reload prices and splits independently when one changes.

How to use it

Divide a raw close on any date X by the cum_split_factor whose [valid_from, valid_to] covers X. That gives you a current-shares-equivalent price. The factor is 1.0 on and after the most recent split, > 1 before a forward split (e.g. 4.0 before a 4-for-1), < 1 before a reverse split.

Join pattern: split_factors on figi with date BETWEEN valid_from AND valid_to.

Examples

Forward split (AAPL 4-for-1, 2020-08-31). Pre-split phase through 2020-08-30 carries cum_split_factor = 4.0. A raw close of 500 on 2020-07-01 gives an adjusted close of 125 (500 / 4). Post-split phase from 2020-08-31 carries cum_split_factor = 1.0 (nothing to adjust against the current unit).

Reverse split (SOXS 1-for-10). Pre-split phase carries cum_split_factor = 0.1. A raw close of 2 on a pre-split day gives an adjusted close of 20 (2 / 0.1). The adjusted series stays on a consistent share-count basis across the reverse split.

  • Grain: (figi, valid_from, valid_to).
  • History: full history for each covered FIGI back to the earliest known split. Pre-split-history phases use valid_from = 2019-11-10.
  • Timing: rebuilt in full each trading day whenever a new split lands.

Fields

field_nametypeunitgraindescriptionprovenancetiming_laghistory_startnull_semanticsexample
figiString(12)identifier(figi, valid_from, valid_to)Composite FIGI for the underlying entity. Join key to stock_mapping, px_01_day, px_10_min.exchangen/an/anever nullBBG000MM2P62
valid_fromDateboundary (inclusive)(figi, valid_from, valid_to)Start date this cumulative factor applies to. 1900-01-01 for the earliest period.derivedn/a1900-01-01 (sentinel)never null2019-08-30
valid_toDateboundary (inclusive)(figi, valid_from, valid_to)Last date this cumulative factor applies to. Open-ended periods (no future split yet) use the phase's phase_end; entities in a currently-active phase carry 2099-12-31.derivedn/an/anever null2020-08-30
cum_split_factorFloat64ratio(figi, valid_from, valid_to)Cumulative product of all splits from valid_from + 1 day forward within the entity's active phase. Divide a raw close on any date in [valid_from, valid_to] by this factor to get a current-shares-equivalent price. 1.0 for the most recent period (no pending split).derivedn/an/anull for extreme reverse-split cases where the true cumulative factor underflows Float64 precision (~1e-15). Treat null as "no valid split adjustment available for this period" and either skip or fail loudly; never coerce to 0.0.4.0
Documentation · 02

REST API

Every endpoint the Masscrest API exposes, with query URL, Python sample, and JSON response inline.

Base URL https://api.masscrest.com

Summary

Every REST endpoint at a glance. HTTP 429 (rate-limit) applies universally; the Errors column lists the endpoint-specific ones an integration engineer should handle.

EndpointGrainFrequencyErrors
GET /v0/option_flows/01_dayPer-underlying daily flow with contract-level filters (dte, delta, callput).Daily400 (missing figi/ticker), 429
GET /v0/option_flows/10_minPer-underlying 10-minute intraday flow with contract-level filters.10-min400 (>30d window, bad params), 429
GET /v0/underlying_option_flows/01_dayPer-underlying × callput daily aggregate flow (pre-aggregated).Daily400 (missing figi/ticker), 429
GET /v0/underlying_option_flows/01_day/snapshotCross-sectional single-day snapshot, one row per (figi, callput).Snapshot400 (bad params), 429
GET /v0/underlying_option_flows/10_minPer-underlying × callput 10-minute aggregate flow (pre-aggregated).10-min400 (>30d window, bad params), 429
GET /v0/underlying_option_flows/10_min/snapshotCross-sectional single-bucket intraday snapshot.Snapshot400 (bad params), 429
GET /v0/underlying_flow_signals/01_dayPer-underlying × callput daily z-score signal on aggregate flow.Daily400 (missing figi/ticker), 429
GET /v0/underlying_flow_signals/01_day/snapshotCross-sectional single-day snapshot of signal rows per (figi, callput).Snapshot400 (bad params), 429
GET /v0/grouped_flow_signal/01_daySector / industry / ETF / ADR / single-stock cohort-level daily signal.Daily400 (bad group_type / category, w/ fuzzy suggestion), 429
GET /v0/grouped_flow_signal/01_day/snapshotCross-sectional snapshot across all group_keys (or filter to one family via category).Snapshot400 (bad group_type / category, w/ fuzzy suggestion), 429
GET /v0/stock_mappingTicker → FIGI / sector / industry reference table (~42k rows).Snapshot400 (bad filter combo), 429
POST /v0/aggregateServer-side aggregation over any of the six tables. Metric expressions and cross-table price joins.Any400 (bad request), 429
GET/v0/option_flows/01_day

Option Flows: Daily

Per-underlying daily aggregated option flow joined with the split-adjusted underlying price.

Description

Retrieve daily option-flow rows for a single underlying, aggregated per (date, figi): one row per trading day. Every row combines all contracts on that underlying (after any optional filters) into a single set of signed retail / institutional / dealer aggregates, joined to the split-adjusted underlying price on the same day.

Exactly one of figi or traded_underlying must be supplied. If traded_underlying is used, it is resolved server-side to the corresponding FIGI. Supplying both, or neither, returns HTTP 400. An unknown traded_underlying returns HTTP 404, not an empty [].

Optional filters, applied BEFORE aggregation

callput, dte_min / dte_max, and abs_delta_min / abs_delta_max narrow the contract set inside the underlying-level GROUP BY. The aggregate reflects only contracts matching the filter. dte_min <= dte_max and abs_delta_min <= abs_delta_max are cross-validated (400 on mismatch).

Bucketed axes (optional)

Three optional booleans add fixed-cardinality axes to the GROUP BY. Each defaults to false; the response is byte-identical to the pre-bucket behaviour when none are set.

  • by_callput=true, splits each (date, figi) row into a call row and a put row. Adds callput ("C" / "P") to the row.
  • by_expiry_bucket=true, adds a 4-bucket days-to-expiry axis. Bucket labels: "0-7d", "8-30d", "31-90d", "91+d". DTE is computed on the source contract as expirationdate − date (calendar days). Rows are returned in semantic DTE order (short-dated first), not lexicographic.
  • by_moneyness_bucket=true, adds a 5-bucket |delta| axis. Bucket labels: "0-0.2" (OTM), "0.2-0.4", "0.4-0.6" (near-the-money), "0.6-0.8", "0.8-1.0" (deep ITM).

Axes compose: passing all three yields at most 2 × 4 × 5 = 40 rows per (date, figi). Response headers add X-Bucket-Axes: <csv> listing the axes that ended up in the GROUP BY (in add order). Bucket columns appear on the row only when their axis is on; the flow-aggregate columns (net_r_delta / net_i_premium / ...) are unchanged in shape but reflect only the contracts falling into that bucket.

Bucket axes are additive to the pre-aggregation filters above, so callput=C&by_expiry_bucket=true returns 4 rows per (date, figi), all calls, split across the four DTE buckets.

Row-count cap

Projected row count is deterministic from the request: days × (1 for daily) × 2^by_callput × 4^by_expiry_bucket × 5^by_moneyness_bucket. If the projection exceeds 25,000 rows and format=json, the request returns HTTP 400 with a structured body:

{ "error": "row_cap_exceeded", "projected_rows": 47000, "threshold": 25000, "hint": "Add format=parquet" }

Set format=parquet to bypass the cap; Parquet is ~5 to 10× smaller on the wire and streams straight from the router. Daily worst case (all three axes, one year) sits at 40 × 252 ≈ 10k rows so the cap only bites on multi-year full-cube pulls.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. When any bucket axis is active, the response also carries X-Bucket-Axes: <csv> (subset of callput,expiry_bucket,moneyness_bucket in add order). Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Aggregation

All fields are summed raw across the contracts passing the filters. No call/put sign flip is applied. For a call-vs-put directional view, pass by_callput=true (or query callput=C and callput=P separately and subtract client-side).

  • *_shares: sum(qty × adj_shares_deliverable). Units are shares (contract count × current per-contract deliverable).
  • *_premium: sum(premium). Dollar notional.
  • *_delta / *_gamma / *_vega: summed raw. Delta is naturally signed by option type on the source contract (calls +, puts −), so net_i_delta already carries directional lean.

Prefixes are _r_ (retail), _i_ (institutional), _m_ (dealer). The floor bucket is not exposed via this endpoint.

Parameters

NameTypeDescription
figistringComposite FIGI of the underlying. Mutually exclusive with `traded_underlying`.
traded_underlyingstringStock ticker; resolved to FIGI via the `dict_symbol_to_figi` dictionary. Mutually exclusive with `figi`.
callputC | PRestrict the contract set to Calls (`C`) or Puts (`P`) BEFORE aggregation. Case-insensitive (`c`, `C`, `p`, `P` all accepted). `CP` is rejected here, this endpoint is contract-grain and every contract is either a call or a put; for a summed C+P view use `/v0/underlying_option_flows/*` with `callput=CP`.
dte_minintegerMinimum days-to-expiration (inclusive). Applied per contract before aggregation.
dte_maxintegerMaximum days-to-expiration (inclusive). Applied per contract before aggregation.
abs_delta_minnumberMinimum `|last_delta|` in `[0.0, 1.0]`. Applied per contract before aggregation.
abs_delta_maxnumberMaximum `|last_delta|` in `[0.0, 1.0]`. Applied per contract before aggregation.
by_callputbooleanWhen `true`, splits each row into a call row (`callput="C"`) and a put row (`callput="P"`). Adds a `callput` column to the response. Default `false` (calls + puts summed together, no `callput` column).
by_expiry_bucketbooleanWhen `true`, adds a fixed 4-bucket days-to-expiry axis to the GROUP BY. Bucket labels: `"0-7d"`, `"8-30d"`, `"31-90d"`, `"91+d"`. Rows are returned in semantic DTE order (short-dated first), not lexicographic. Adds an `expiry_bucket` column to the response. Default `false`.
by_moneyness_bucketbooleanWhen `true`, adds a fixed 5-bucket `|delta|` axis to the GROUP BY. Bucket labels: `"0-0.2"` (OTM), `"0.2-0.4"`, `"0.4-0.6"` (near-the-money), `"0.6-0.8"`, `"0.8-1.0"` (deep ITM). Delta is signed by option type on the source contract, so the axis is on the absolute value, a 20-delta OTM call and a 20-delta OTM put both land in `"0-0.2"`. Adds a `moneyness_bucket` column to the response. Default `false`.
start_datestringEarliest trading day to include (inclusive, YYYY-MM-DD).
end_datestringLatest trading day to include (inclusive, YYYY-MM-DD).
formatjson | parquetResponse format. `json` returns a JSON array (default). `parquet` streams a binary Parquet file (~5 to 10× smaller). When any `by_*` axis pushes projected row count over 25,000, `json` returns HTTP 400 (`row_cap_exceeded`) and the caller must pass `parquet` to proceed.
GET/v0/option_flows/10_min

Option Flows: 10-minute

Per-underlying 10-minute aggregated option flow joined with the intraday split-adjusted underlying price.

Description

Retrieve 10-minute-bucket option-flow rows for a single underlying, aggregated per (date, ten_min_timeframe, figi): one row per 10-minute bucket. Every row combines all contracts on that underlying (after any optional filters) into a single set of signed retail / institutional / dealer aggregates, joined to the split-adjusted underlying price for the same bucket.

Exactly one of figi or traded_underlying must be supplied. If traded_underlying is used, it is resolved server-side to the corresponding FIGI. Supplying both, or neither, returns HTTP 400. An unknown traded_underlying returns HTTP 404, not an empty [].

30-day window cap. Any single call returns at most 30 days of history. If start_date / end_date are omitted, the response covers the most recent 30 days. Requests spanning more than 30 days return HTTP 400. For multi-year intraday history, use the flat-files bucket gs://prod-us-option-tenmin-contract/option_flows/v0/10_min/. For longer-horizon signals, use the daily variant /v0/option_flows/01_day.

Optional filters, applied BEFORE aggregation

callput, dte_min / dte_max, and abs_delta_min / abs_delta_max narrow the contract set inside the bucket-level GROUP BY. The aggregate reflects only contracts matching the filter. dte_min <= dte_max and abs_delta_min <= abs_delta_max are cross-validated (400 on mismatch).

Bucketed axes (optional)

Same three optional booleans as the daily variant, by_callput, by_expiry_bucket, by_moneyness_bucket, each adding a fixed-cardinality axis to the GROUP BY. Axes compose: passing all three yields at most 2 × 4 × 5 = 40 rows per (date, ten_min_timeframe, figi). Bucket labels, ordering rules, and response-column shape match the daily variant; see /v0/option_flows/01_day for the full spec. Response header X-Bucket-Axes reports which axes ended up in the GROUP BY.

Row-count cap

Projected row count is days × 39 × 2^by_callput × 4^by_expiry_bucket × 5^by_moneyness_bucket (39 = full RTH 10-min buckets per session). If the projection exceeds 25,000 rows and format=json, the request returns HTTP 400 ({"error": "row_cap_exceeded", ...}), pass format=parquet to bypass. The intraday cap bites earliest here: 15 days × all three axes ≈ 23k rows, so week-scale intraday full-cube pulls stay within JSON; multi-week ones need Parquet.

Sign convention

Same as the daily variant. See /v0/option_flows/01_day for the full sign / prefix / unit notes.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. When any bucket axis is active, the response also carries X-Bucket-Axes: <csv> (subset of callput,expiry_bucket,moneyness_bucket in add order). Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
figistringComposite FIGI of the underlying. Mutually exclusive with `traded_underlying`.
traded_underlyingstringStock ticker; resolved to FIGI via the `dict_symbol_to_figi` dictionary. Mutually exclusive with `figi`.
callputC | PRestrict the contract set to Calls (`C`) or Puts (`P`) BEFORE aggregation. Case-insensitive (`c`, `C`, `p`, `P` all accepted). `CP` is rejected here, this endpoint is contract-grain and every contract is either a call or a put; for a summed C+P view use `/v0/underlying_option_flows/*` with `callput=CP`.
dte_minintegerMinimum days-to-expiration (inclusive). Applied per contract before aggregation.
dte_maxintegerMaximum days-to-expiration (inclusive). Applied per contract before aggregation.
abs_delta_minnumberMinimum `|last_delta|` in `[0.0, 1.0]`. Applied per contract before aggregation.
abs_delta_maxnumberMaximum `|last_delta|` in `[0.0, 1.0]`. Applied per contract before aggregation.
by_callputbooleanWhen `true`, splits each 10-min-bucket row into a call row (`callput="C"`) and a put row (`callput="P"`). Adds a `callput` column to the response. Default `false`.
by_expiry_bucketbooleanWhen `true`, adds a fixed 4-bucket days-to-expiry axis to the GROUP BY. Bucket labels: `"0-7d"`, `"8-30d"`, `"31-90d"`, `"91+d"`. Rows are returned in semantic DTE order (short-dated first), not lexicographic. Adds an `expiry_bucket` column to the response. Default `false`.
by_moneyness_bucketbooleanWhen `true`, adds a fixed 5-bucket `|delta|` axis to the GROUP BY. Bucket labels: `"0-0.2"` (OTM), `"0.2-0.4"`, `"0.4-0.6"` (near-the-money), `"0.6-0.8"`, `"0.8-1.0"` (deep ITM). Adds a `moneyness_bucket` column to the response. Default `false`.
start_datestringEarliest trading day (inclusive, YYYY-MM-DD). Combined with `end_date` must span at most 30 days; if both are omitted the response covers the most recent 30 days.
end_datestringLatest trading day (inclusive, YYYY-MM-DD). Combined with `start_date` must span at most 30 days.
formatjson | parquetResponse format. `json` returns a JSON array (default). `parquet` streams a binary Parquet file (~5 to 10× smaller). When any `by_*` axis pushes projected row count over 25,000, `json` returns HTTP 400 (`row_cap_exceeded`) and the caller must pass `parquet` to proceed. The 25k threshold bites earliest here: 15 days × all three axes ≈ 23k rows.
GET/v0/underlying_option_flows/01_day

Underlying Option Flows: Daily

Per-underlying × callput daily aggregated option flow (pre-aggregated), joined with adjusted underlying price.

Description

Retrieve daily underlying-level option-flow rows (pre-aggregated across every contract on the same (date, figi, callput)), joined to the PIT stock_mapping row for (figi, date). Rows are already grouped by (date, figi, callput), so unlike /v0/option_flows/01_day no contract-level filters are available. In exchange, cross-underlying scans (see the sibling /snapshot endpoint) are significantly cheaper.

Provide at most one of figi or traded_underlying. If traded_underlying is used, it is resolved server-side to the corresponding FIGI. Supplying both returns HTTP 400. An unknown traded_underlying returns HTTP 404, not an empty []. The underlying selector may be omitted entirely when at least one stock_mapping filter (is_etf / is_adr / industry / sector) is provided; the response then covers every figi matching the metadata predicate for the date range.

callput

  • C: calls-only row per bucket (direct scan).
  • P: puts-only row per bucket (direct scan).
  • CP: default; C and P summed at query time. One row per bucket with callput = "CP" in the output.

Case-insensitive on input, c, Cp, CP, p all accepted; the emitted row always carries the canonical upper-case form.

Response columns

date, figi, traded_underlying, callput, n_contracts, total_shares, last_underprice, atm_iv_30d, the 45 flow aggregates (buy/sell/net × shares/premium/delta/gamma/vega × r/i/m), adj_open / adj_close / adj_volume / total_turnover from the matched underlying, and industry / sector / isAdr / isEtf from stock_mapping (ranged endpoints intentionally omit companyName).

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
figistringComposite FIGI of the underlying. Mutually exclusive with `traded_underlying`. Optional when a `stock_mapping` filter is supplied.
traded_underlyingstringStock ticker; resolved via the `dict_symbol_to_figi` dictionary. Mutually exclusive with `figi`. Optional when a `stock_mapping` filter is supplied.
callputC | P | CP`C`, `P`, or `CP` (default). `CP` sums the C and P rows at query time.
start_datestringEarliest trading day (inclusive, YYYY-MM-DD).
end_datestringLatest trading day (inclusive, YYYY-MM-DD).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (binary, ~5 to 10× smaller).
GET/v0/underlying_option_flows/01_day/snapshot

Underlying Option Flows: Daily Snapshot

Single-day cross-sectional snapshot of pre-aggregated option flow for every underlying, joined with adjusted price.

Description

Cross-sectional snapshot: returns one row per (figi, callput) (every underlying with flow on the requested date, C and P kept as separate rows), joined to the split-adjusted underlying close AND to the PIT stock_mapping row for (figi, date).

No underlying selector is required; the response covers the full universe. Optional stock_mapping-backed filters (is_etf / is_adr / industry / sector) narrow the returned figis to those matching the predicate.

Typical response is ~10,000 rows per trading day (roughly 5,000 underlyings × C+P). Prefer format=parquet for anything larger than a browser preview.

Response columns

Standard flow-aggregate columns plus companyName / industry / sector / isAdr / isEtf from stock_mapping.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
datestringTrading day (YYYY-MM-DD) to snapshot. Optional, defaults to the latest available trading day when omitted (matches the `grouped_flow_signal` snapshot behaviour).
figistringOptional comma-separated list of composite FIGIs to restrict the snapshot to those symbols (paid tier only, trials are blocked at the router with HTTP 403 before this filter is evaluated).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (recommended for full-universe pulls).
GET/v0/underlying_option_flows/10_min

Underlying Option Flows: 10-minute

Per-underlying × callput 10-minute aggregated option flow (pre-aggregated), joined with intraday adjusted price.

Description

Retrieve 10-minute-bucket underlying-level option-flow rows (pre-aggregated across every contract on the same (date, ten_min_timeframe, figi, callput)), joined to the PIT stock_mapping row for (figi, date). Rows are already grouped by (date, ten_min_timeframe, figi, callput); no contract-level filters are available on this endpoint.

Provide at most one of figi or traded_underlying. If traded_underlying is used, it is resolved server-side to the corresponding FIGI. Supplying both returns HTTP 400. An unknown traded_underlying returns HTTP 404, not an empty []. The underlying selector may be omitted entirely when at least one stock_mapping filter (is_etf / is_adr / industry / sector) is provided; the response then covers every figi matching the metadata predicate for the date range.

30-day window cap. At most 30 days of history per call. If start_date / end_date are omitted, the response covers the most recent 30 days. Requests spanning more than 30 days return HTTP 400. For multi-year intraday history use the flat-files bucket gs://prod-us-option-tenmin-underlying/underlying_option_flows/v0/10_min/.

callput

Same semantics as the daily variant: C, P, or CP (default). CP sums C and P at query time. Case-insensitive on input.

Response columns

Standard flow-aggregate columns plus industry / sector / isAdr / isEtf from stock_mapping (ranged endpoints intentionally omit companyName).

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
figistringComposite FIGI of the underlying. Mutually exclusive with `traded_underlying`. Optional when a `stock_mapping` filter is supplied.
traded_underlyingstringStock ticker; resolved via the `dict_symbol_to_figi` dictionary. Mutually exclusive with `figi`. Optional when a `stock_mapping` filter is supplied.
callputC | P | CP`C`, `P`, or `CP` (default).
start_datestringEarliest trading day (inclusive, YYYY-MM-DD). Combined with `end_date` must span at most 30 days.
end_datestringLatest trading day (inclusive, YYYY-MM-DD).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (binary, ~5 to 10× smaller).
GET/v0/underlying_option_flows/10_min/snapshot

Underlying Option Flows: 10-minute Snapshot

Single 10-minute-bucket cross-sectional snapshot of pre-aggregated option flow for every underlying.

Description

Cross-sectional snapshot at a specific 10-minute bucket: returns one row per (figi, callput) (every underlying with flow at the requested (date, ten_min_timeframe), C and P kept as separate rows), joined to the split-adjusted underlying price for the same bucket AND to the PIT stock_mapping row for (figi, date).

No underlying selector is required. Optional stock_mapping-backed filters (is_etf / is_adr / industry / sector) narrow the returned figis to those matching the predicate.

The ten_min_timeframe param must fall on the requested date. Typical response is ~5,000 to 10,000 rows per bucket. Prefer format=parquet for full-universe pulls.

Response columns

Standard flow-aggregate columns plus companyName / industry / sector / isAdr / isEtf from stock_mapping.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

* required
NameTypeDescription
datestringTrading day (YYYY-MM-DD) the bucket falls on. Optional, defaults to the latest available trading day when omitted.
ten_min_timeframe *stringBucket start timestamp (`YYYY-MM-DD HH:MM:SS`). Must fall on `date`.
figistringOptional comma-separated list of composite FIGIs to restrict the snapshot to those symbols (paid tier only, trials are blocked at the router with HTTP 403 before this filter is evaluated).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (recommended for full-universe pulls).
GET/v0/underlying_flow_signals/01_day

Underlying Flow Signals: Daily

Per-underlying × callput daily flow signals (raw nets, 21-day rolling sums, 2y z-scores across shares / premium / delta / gamma / vega).

Description

Retrieve daily flow-signal rows from the pre-aggregated underlying_flow_signals_01_day table, joined to the PIT stock_mapping row for (figi, date). One row per (date, figi, callput).

The source table stores 34 columns per row: 4 keys (date, figi, underlying_ticker, callput) plus a 3-layer × 10-metric flow block. The 10 metrics are institutional + retail × shares / premium / delta / gamma / vega. The 3 layers are raw daily nets, _21dma (21-trading-day rolling sums), and z_net_..._21dma (z-scored against a trailing 2-year rolling mean and sample stddev of the 21dma). The raw μ / σ of the 2-year window are computed at query time and are NOT distributed as separate columns. Rolling windows PARTITION BY (figi, callput); each callput row has its own independent trailing stats, so z(C) + z(P) ≠ z(CP).

The callput dimension

Every source row exists in three variants: C (calls only), P (puts only), and CP (synthetic combined view). CP is built at ingest time so consumers do not have to reconstruct call-vs-put semantics themselves:

  • Directional nets (shares, premium) → CP = C − P. Bullish call flow and bearish put flow both express long-delta demand; the sign-flipped difference is the net directional pressure.
  • Naturally-signed greeks (delta, gamma, vega) → CP = C + P. Calls carry positive delta, puts negative; the straight sum through-adds correctly.

The default callput is CP; omitting the filter would triple-count each figi-date across the three variants.

Deriving fire / holding-period predicates

Fire flags and holding-period flags are not persisted on this table; derive them at query time from the z-score column:

  • 2σ institutional-delta fire: WHERE z_net_i_delta_21dma > 2 AND callput = 'CP'
  • 3σ institutional-delta fire: WHERE z_net_i_delta_21dma > 3 AND callput = 'CP'

Holding-period semantics (fire within the last 22 trading days AND same-metric z has not gone negative since) can be reconstructed with a self-lag or a rolling max(z) window function. The pipeline does not persist this because the same rule can be expressed on any of the 10 z-columns × any threshold, and pre-materialising that cross-product would add 60+ columns of low-density flags.

Underlying selector

Provide at most one of figi or traded_underlying. If traded_underlying is used, it is resolved server-side to the corresponding FIGI. Supplying both returns HTTP 400. An unknown traded_underlying returns HTTP 404, not an empty []. The underlying selector may be omitted entirely when at least one stock_mapping filter (is_etf / is_adr / industry / sector) is provided; the response then covers every figi matching the metadata predicate for the date range.

Response columns

date, figi, traded_underlying, callput, the 10 raw daily nets, 10 _21dma (21-day rolling sums), and 10 z-scores (against the trailing 2-year rolling μ/σ of the 21dma, computed at query time). Plus industry / sector / isAdr / isEtf from stock_mapping (ranged endpoint intentionally omits companyName).

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
figistringComposite FIGI of the underlying. Mutually exclusive with `traded_underlying`. Optional when a `stock_mapping` filter is supplied.
traded_underlyingstringStock ticker; resolved via the `dict_symbol_to_figi` dictionary. Mutually exclusive with `figi`. Optional when a `stock_mapping` filter is supplied.
callputC | P | CP`C` (calls only), `P` (puts only), or `CP` (default; synthetic combined view). CP is pre-computed, split by metric family: size measures (`shares`, `premium`) are `C − P` (unsigned magnitudes, sign-flipped on puts to produce a directional bullish-minus-bearish net); greeks (`delta`, `gamma`, `vega`) are `C + P` (naturally signed by option type, with put deltas stored negative, so a straight sum captures net book exposure). **The default is `CP` for a reason: every source row triples up on callput and omitting the filter triple-counts.** Case-insensitive on input (`c`, `Cp`, `CP`, `p` all accepted); the emitted row always carries the canonical upper-case form.
start_datestringEarliest trading day (inclusive, YYYY-MM-DD).
end_datestringLatest trading day (inclusive, YYYY-MM-DD).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (binary, ~5 to 10× smaller).
GET/v0/underlying_flow_signals/01_day/snapshot

Underlying Flow Signals: Daily Snapshot

Single-day × callput cross-sectional snapshot of daily flow signals for every underlying with a row on `date`.

Description

Cross-sectional snapshot: returns one row per (figi, callput) with a signal-table row on the requested date, joined to the PIT stock_mapping row for (figi, date).

No underlying selector is required; the response covers the full universe on the requested callput (default CP). Optional stock_mapping-backed filters (is_etf / is_adr / industry / sector) narrow the returned figis.

Typical unfiltered response is ~4,000 to 5,000 rows per trading day per callput variant. Adding a z-score filter (e.g. WHERE z_net_i_delta_21dma > 2) is left to the client; the endpoint returns the full universe row-set and clients filter downstream. Prefer format=parquet for anything larger than a browser preview.

Callput semantics

Same as the ranged endpoint: C, P, or CP. Default is CP when the callput parameter is omitted. Case-insensitive on input. See /v0/underlying_flow_signals/01_day for the C−P / C+P synthesis convention. Omitting the callput filter would triple-count; the endpoint enforces the default.

Deriving fire predicates

The endpoint returns the full universe row-set for the requested date × callput; filter downstream on the emitted z-score columns:

rows = requests.get(...).json()
fires = [r for r in rows if r["z_net_i_delta_21dma"] and r["z_net_i_delta_21dma"] > 2]

Response columns

The 34 signal-table columns (4 keys + 30 flow, 10 raw nets, 10 21-day rolling sums, 10 z-scores against the trailing 2-year μ/σ of the 21dma) plus companyName / industry / sector / isAdr / isEtf from stock_mapping.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
datestringTrading day (YYYY-MM-DD) to snapshot. Optional, defaults to the latest available trading day when omitted (matches the `grouped_flow_signal` snapshot behaviour).
callputC | P | CP`C` (calls only), `P` (puts only), or `CP` (default; synthetic combined view). CP is pre-computed, split by metric family: size measures (`shares`, `premium`) are `C − P` (unsigned magnitudes, sign-flipped on puts to produce a directional bullish-minus-bearish net); greeks (`delta`, `gamma`, `vega`) are `C + P` (naturally signed by option type, with put deltas stored negative, so a straight sum captures net book exposure). **The default is `CP` for a reason: every source row triples up on callput and omitting the filter triple-counts.** Case-insensitive on input (`c`, `Cp`, `CP`, `p` all accepted); the emitted row always carries the canonical upper-case form.
figistringOptional comma-separated list of composite FIGIs to restrict the snapshot to those symbols (paid tier only, trials are blocked at the router with HTTP 403 before this filter is evaluated).
is_etfbooleanFilter to ETFs (`true`) or non-ETFs (`false`). Applied to the joined `stock_mapping` row.
is_adrbooleanFilter to ADRs (`true`) or non-ADRs (`false`).
industrystringFilter on `stock_mapping.industry`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known industry returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown industry: 'semis'. Did you mean 'Semiconductors'?"`).
sectorstringFilter on `stock_mapping.sector`. Case-insensitive on input; the emitted row carries the canonical mixed-case form. A value that doesn't match any known sector returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. `"Unknown sector: 'technolgy'. Did you mean 'Technology'?"`).
formatjson | parquet`json` (default) or `parquet` (recommended for full-universe pulls).
GET/v0/grouped_flow_signal/01_day

Grouped Flow Signal: Daily

Group-level (sector / industry / etf / adr / single_stock / all) × callput daily flow-signal rollups.

Description

Retrieve group-level daily flow-signal rows from grouped_flow_signal_01_day. One row per (date, group_key, callput). No stock_mapping join; the group_key ARE the classification.

The source table sums the underlying_flow_signals_01_day raw nets across every figi in the selected bucket, then re-runs the 21-day rolling sum + 2-year z-score cascade on the group-level series. Row shape mirrors underlying_flow_signals_01_day (10 raw nets + 10 _21dma + 10 z-scores; the raw μ / σ of the trailing 2-year window are computed at query time and are NOT distributed as separate columns). Callput semantics are identical (default CP, C−P for nets, C+P for greeks).

Single-axis contract

Query with one group_type value, either a canonical mixed-case sector or industry name (e.g. "Technology", "Software - Application") or one of the rollup literals "etf" / "adr" / "single_stock" / "all". There is no paired second parameter, the family (sector vs industry vs rollup) is inferred from the value itself. The response row echoes the same value in the group_key STRING column; matching is case-insensitive on input and the wire always carries the canonical mixed-case form.

Signal formula

  • _21dma = 21-trading-day rolling SUM of the group-level raw net, scoped to (group_key, callput).
  • z_net_*_21dma = z-score of _21dma against its trailing 2-year (504 trading day) rolling mean and sample standard deviation, computed at query time. Minimum 63 non-null observations in the trailing window required, else null. Rounded to 2dp on emit. The raw μ / σ are not distributed as separate columns.

Every industry-day is emitted with populated rolling-window fields (21dma, z_*) and n_figi exposed on the row. The 21dma and 2y baseline include ALL days regardless of n_figi, thin days contribute their (smaller) raw values to the trailing window, so each industry's z is calibrated against its own historical breadth mix and populates on every day. Cross-industry z magnitudes aren't directly comparable when breadth profiles differ meaningfully, filter on n_figi at query time when comparability matters. Sector, etf, adr, single_stock, and all branches always clear the 10-FIGI mark and are effectively unchanged; only industries (Conglomerates, Regulated Water, Uranium, etc.) are materially affected.

⚠ Not point-in-time for group classification

The sector, industry, ETF, and ADR labels used to bucket each figi come from the CURRENT stock_mapping phase (row with phase_end = 2099-12-31), applied uniformly to every historical date. Sector / industry labels churn (10 to 20 reclassifications per year across the covered universe); clients expect historical rollups to use today's classification for consistency with the current portfolio view.

The signal rolling window itself is per bucket; only the group-membership assignment is applied retroactively.

If you need a fully point-in-time sector rollup, bucket at query time against stock_mapping filtered by date BETWEEN phase_start AND phase_end, using the underlying_flow_signals_01_day table directly.

Group-type branches

  • sector: one row per distinct sector name per (date, callput). group_key is the mixed-case sector string (~11 distinct values). ETFs and ADRs are excluded from sector rollups, they have their own top-level etf and adr branches, and their reference-mapping "issuer sector" is typically the legal-entity domicile (Financial Services for most ETFs), not the underlying-exposure sector.
  • industry: one row per distinct industry name per (date, callput). group_key is the mixed-case industry string (~150 distinct values matching the reference industry taxonomy on stock_mapping). Same ETF/ADR exclusion. n_figi is exposed on every row so consumers can filter thin-breadth days at query time, see "Thin-industry handling" below.
  • etf: every figi with isEtf = 'true' on the current phase, aggregated. group_key = literal "etf".
  • adr: every figi with isAdr = 'true' on the current phase, aggregated. group_key = literal "adr".
  • single_stock: every figi that is neither ETF nor ADR (figis missing from stock_mapping default here). group_key = literal "single_stock".
  • all: every figi in the underlying flow table. group_key = literal "all".

Aggregate identities

single_stock + etf + adr = all exactly per (date, callput). sum(sector) ≈ single_stock per (date, callput) within < 1% of gross flow (residual = single-stock figis without a current sector label). Note: because ETFs and ADRs no longer contribute to sector / industry rollups, sum(sector) ≠ all, the diff is the etf + adr contribution. sum(industry) ≈ single_stock too, every industry with any flow now emits a row (raw nets populated), and the honest sum lines up within the same < 1% single-stock-without-industry residual.

Thin-industry handling

Every industry-day is emitted with populated raw daily nets AND populated rolling / z fields. n_figi is exposed on every row so consumers can filter thin-breadth days at query time. The 21dma and 2y baseline include ALL days regardless of n_figi, thin days contribute their (smaller) raw values to the trailing window and to the 2y μ/σ used to z-score. Each industry's z-score is therefore calibrated against its own historical breadth mix, a chronically-thin industry's baseline is set to its own thin-day scale, so z fires when flow is unusually large for that industry. Cross-industry z magnitudes aren't directly comparable when breadth profiles differ meaningfully. Structurally-thin industries (historically ~46, including Copper, Uranium, Consulting Services, Regulated Water, Conglomerates, Waste Management, Railroads) now emit continuous rows with populated raw flow AND populated z-scores rather than being dropped entirely. Sector, etf, adr, single_stock, and all branches always exceed the threshold and the caveat is a no-op for them.

Valid group_key: sector (~11 total)

Sector matching is case-insensitive on input; the row-emitted group_key is always the canonical mixed-case form. For a one-line description of each sector, see the Data dictionary → grouped_flow_signal_01_day section.

Basic Materials, Communication Services, Consumer Cyclical, Consumer Defensive, Energy, Financial Services, Healthcare, Industrials, Real Estate, Technology, Utilities.

Valid group_key: industry (~150 total)

Case-insensitive on input; canonical mixed-case on the wire. Full enumerated list in the Data dictionary → grouped_flow_signal_01_day section (with a one-line description of each). Passing a group_type value that doesn't match any known sector / industry name (or rollup literal) returns HTTP 400 with a fuzzy-match suggestion in the error body (e.g. requesting "Semi" returns "Did you mean 'Semiconductors'?").

Roughly 46 named industries are structurally thin (never clear the 10-FIGI mark). They appear in the response every day with raw daily nets AND rolling / z fields populated; the z magnitude on those rows is not directly comparable to full-breadth days (see the thin-industry handling section above, filter on n_figi at query time when comparability matters). The sector / industry enumerations may drift as existing figis are reclassified or new labels are added. For the live authoritative list, call the snapshot endpoint with category=sector (or industry) and take the distinct group_key values from the response.

Response columns

date, group_key, callput, n_figi, plus the 30 group-level flow columns (10 raw nets, 10 _21dma rolling sums, 10 z-scores; same layer shape as underlying_flow_signals_01_day; no metadata columns, none needed).

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

* required
NameTypeDescription
group_type *stringA single canonical `group_key` value. Either a mixed-case sector name (e.g. `Technology`, `Financial Services`), a mixed-case industry name (e.g. `Software - Application`, `Semiconductors`), or one of the rollup literals `etf` / `adr` / `single_stock` / `all`. Case-insensitive on input; the row-emitted `group_key` is always the canonical mixed-case form. A value that doesn't match any known name returns HTTP 400 with a fuzzy-match suggestion in the body (e.g. requesting `Semi` returns `Did you mean 'Semiconductors'?`). See the endpoint description for the full enumerated lists (~11 sectors, ~150 industries, plus 4 rollup literals). Time series is gap-free, industries with <10 contributing FIGIs on the day still emit rows with raw nets populated but rolling / z fields NULL. To sweep a whole family in one call, use the snapshot endpoint's `category` parameter instead.
callputC | P | CP`C` (calls only), `P` (puts only), or `CP` (default; synthetic combined view). CP is pre-computed, split by metric family: size measures (`shares`, `premium`) are `C − P` (unsigned magnitudes, sign-flipped on puts to produce a directional bullish-minus-bearish net); greeks (`delta`, `gamma`, `vega`) are `C + P` (naturally signed by option type, with put deltas stored negative, so a straight sum captures net book exposure). **The default is `CP` for a reason: every source row triples up on callput and omitting the filter triple-counts.** Case-insensitive on input (`c`, `Cp`, `CP`, `p` all accepted); the emitted row always carries the canonical upper-case form.
start_datestringEarliest trading day (inclusive, YYYY-MM-DD).
end_datestringLatest trading day (inclusive, YYYY-MM-DD).
formatjson | parquet`json` (default) or `parquet` (recommended for full-history sector sweeps).
GET/v0/grouped_flow_signal/01_day/snapshot

Grouped Flow Signal: Daily Snapshot

Single-day cross-sectional snapshot of group-level daily flow-signal rows. Omit both filters to return every group_key (~168 rows / callput / day across ~11 sectors, ~150 industries, and 4 rollup literals).

Description

Cross-sectional snapshot of grouped_flow_signal_01_day on a single trading day. Every input parameter is optional; with no filters the endpoint returns every group_key with any flow on the latest available day (~168 rows on the default callput = 'CP').

  • date omitted → latest available trading day.
  • Neither category nor group_type set → every group_key with flow returned in the same payload (~11 sectors + up to ~150 industries + etf + adr + single_stock + all, on the default callput = 'CP'). Industry rows with fewer than 10 contributing FIGIs on the requested date carry populated raw nets but NULL rolling / z fields (see "Thin-industry handling" below), they are NOT dropped.
  • category set → restrict the payload to a whole family: industry returns every industry row (up to ~150), sector returns just the ~11 sector rows, rollup returns the 4 rollup rows, all returns every family. Snapshot-only parameter.
  • group_type set → restrict to a single group_key value (a mixed-case sector / industry name or one of the rollup literals etf / adr / single_stock / all). Case-insensitive on input; a value that doesn't match any known name returns HTTP 400 with a fuzzy-match suggestion in the body.
  • category and group_type are mutually exclusive. Pass one or the other, never both.

Response shape is identical to the ranged endpoint: single group_key STRING column + date + callput + 30 flow columns per row (10 raw nets, 10 _21dma rolling sums, 10 z-scores against the trailing 2-year μ/σ of the 21dma), no stock_mapping join. See the ranged endpoint for the full sector / industry enumeration, the CP synthesis rule, and the z-score formula.

⚠ Not point-in-time for group classification

Same load-bearing caveat as the ranged endpoint: the sector / industry / ETF / ADR labels used to bucket each figi come from the CURRENT stock_mapping phase (row with phase_end = 2099-12-31), applied uniformly to every historical date. The signal rolling window (21dma + 2y baseline) is per bucket; only the group-membership assignment is applied retroactively.

For strict-PIT sector rollups, use the underlying_flow_signals/01_day/snapshot endpoint and bucket client-side against a PIT stock_mapping snapshot.

ETF / ADR exclusion from sector & industry

Sector and industry aggregations EXCLUDE ETFs and ADRs (which get their own top-level etf / adr rollups). The reference mapping tags ETFs with the issuer's legal-entity sector (typically Financial Services), which is meaningless for underlying exposure, so pooling ETFs into sector rollups would inflate Financial Services by 85%+ of gross flow. The exclusion is applied at the underlying-figi filter step, not on the emitted rows.

Thin-industry handling

Every industry-day is emitted with populated raw daily nets AND populated rolling / z fields (21dma, z_*). n_figi is exposed on every row so consumers can filter thin-breadth days at query time. The 21dma and 2y baseline include ALL days regardless of n_figi, so each industry's z is calibrated against its own historical breadth mix and populates on every day. Cross-industry z magnitudes aren't directly comparable when breadth profiles differ meaningfully, filter on n_figi at query time when comparability matters. Structurally-thin industries (historically ~46, including Copper, Uranium, Consulting Services, Regulated Water, Conglomerates, Waste Management, Railroads) now emit continuous rows with populated raw flow AND populated z-scores rather than being absent. Sector, etf, adr, single_stock, and all branches always exceed the 10-FIGI mark and the caveat is a no-op for them.

Response headers & errors

Every /v0/* list response carries X-Row-Count: <N>. Unknown query parameters return 400 Bad Request with the list of accepted names in the body.

Parameters

NameTypeDescription
datestringTrading day (YYYY-MM-DD) to snapshot. Defaults to the latest available day.
categoryindustry | sector | rollup | allFamily-level filter. One of `industry` / `sector` / `rollup` / `all`. Restricts the snapshot to the intended grouping family, `industry` returns every industry row with flow on the day (up to ~150), `sector` returns just the ~11 sector rows, `rollup` returns the `etf` / `adr` / `single_stock` / `all` rollup rows, `all` returns every family. Mutually exclusive with `group_type`, pass one or the other, not both. Snapshot-only; not accepted on the ranged endpoint.
group_typestringSingle-key filter, a canonical `group_key` value (mixed-case sector or industry name, or one of the rollup literals `etf` / `adr` / `single_stock` / `all`). Case-insensitive on input; the row-emitted `group_key` is always the canonical mixed-case form. A value that doesn't match any known name returns HTTP 400 with a fuzzy-match suggestion in the body. Mutually exclusive with `category`, use `group_type` when you want a single specific `group_key`, `category` when you want a whole family.
callputC | P | CP`C` (calls only), `P` (puts only), or `CP` (default; synthetic combined view). CP is pre-computed, split by metric family: size measures (`shares`, `premium`) are `C − P` (unsigned magnitudes, sign-flipped on puts to produce a directional bullish-minus-bearish net); greeks (`delta`, `gamma`, `vega`) are `C + P` (naturally signed by option type, with put deltas stored negative, so a straight sum captures net book exposure). **The default is `CP` for a reason: every source row triples up on callput and omitting the filter triple-counts.** Case-insensitive on input (`c`, `Cp`, `CP`, `p` all accepted); the emitted row always carries the canonical upper-case form.
formatjson | parquet`json` (default) or `parquet` (binary, ~5 to 10× smaller).
GET/v0/stock_mapping

Stock Mapping

Ticker → FIGI / sector / industry reference table (~21.6k rows).

Description

Retrieve the stock_mapping reference table, which maps every ticker Masscrest tracks to its identifiers and company metadata (FIGI, sector, industry, ETF/ADR/fund flags) and includes historical ticker changes via phase_start / phase_end windows. Filters are optional and combine with AND for exact match.

The full table is ~21,600 rows. Default limit is 10 rows to keep interactive docs responsive; pass limit=0 for the full table. When the default limit clipped the rowset, the response carries X-Truncated: true (absent when the full result fit).

Response headers (shared with every /v0/* list endpoint)

  • X-Row-Count: <N>, number of rows in the response, regardless of format.
  • X-Truncated: true, stock_mapping only: default limit=10 clipped the rowset.

Error responses

  • 400 Bad Request, unknown query parameter. The response body enumerates the accepted names for this endpoint.

Parameters

NameTypeDescription
traded_underlyingstringPoint-in-time listed ticker. Dotted dual-class form (`BRK.B`).
figistringComposite FIGI identifier.
sectorstringSector classification.
industrystringIndustry classification.
limitintegerMaximum number of rows to return. `0` means no cap (full table). Defaults to `10`.
formatjson | parquetResponse format. `json` returns a JSON array (default). `parquet` streams a binary Parquet file (~5 to 10× smaller, faster to parse).

Aggregate

POST/v0/aggregate

One endpoint over any of the six Masscrest tables. Server-side aggregation, metric expressions, cross-table joins.

What it is

A query-composer REST endpoint. You pick a table, name the dimensions to group by, declare the metrics you want computed, and get back exactly that rowset. One POST, one purpose-built response.

Why it exists

The rest of the REST API is table-oriented: each endpoint returns rows from one table shaped by a fixed set of query parameters. That is the right shape when you want the raw rows. It is the wrong shape when the question is "top 20 stocks by 90-day institutional net delta", "total premium across SPY / QQQ / IWM for two years", or "tech-sector call premium overlaid with QQQ close". Aggregate flips the model so you describe the rowset you want and Masscrest composes it server-side.

You still hit the table endpoints directly when you want raw rows. Aggregate is the surface for computed answers.

How to use it

Every request carries table, filters, and metrics. group_by, order_by, joins, and limit are optional. X-API-Key in headers.

{
  "table": "v0.underlying_flow_signals_01_day",
  "filters": { "date_range": { "start": "2026-06-01", "end": "2026-09-01" }, "callput": "CP" },
  "group_by": ["figi"],
  "metrics": [
    { "field": "net_i_delta", "agg": "sum", "alias": "net_i_delta_sum" }
  ],
  "order_by": [{ "field": "net_i_delta_sum", "dir": "desc" }],
  "limit": 20
}

Mental model: SELECT metrics FROM table WHERE filters GROUP BY group_by ORDER BY order_by LIMIT limit. Metrics take one of two shapes (shorthand aggregate or free-form expression). Filters cover date range, callput, figi-or-symbols, server-side metadata (sector / industry / ETF flag / etc.), group-key, and intraday bucket. joins optionally LEFT-JOINs one of the two price tables for benchmark overlays.

Examples

Example 1: top 20 stocks by trailing-90-day institutional net delta.

{
  "table": "v0.underlying_flow_signals_01_day",
  "filters": {
    "date_range": { "start": "2026-06-19", "end": "2026-09-19" },
    "callput": "CP"
  },
  "group_by": ["figi"],
  "metrics": [
    { "field": "net_i_delta", "agg": "sum", "alias": "net_i_delta_sum" }
  ],
  "order_by": [{ "field": "net_i_delta_sum", "dir": "desc" }],
  "limit": 20
}

Sums net_i_delta across the 90-day window per figi, sorts largest first, returns the top 20. One metric, one group-by axis, no joins. The right shape for a single-measure leaderboard.

Example 2: tech-sector institutional call premium overlaid with QQQ close.

{
  "table": "v0.underlying_option_flows_01_day",
  "filters": {
    "date_range": { "start": "2024-01-01", "end": "2026-09-19" },
    "callput": "C",
    "metadata": { "sector": "Technology" }
  },
  "group_by": ["date"],
  "joins": [
    {
      "target": "v0.px_01_day_adj",
      "on_figi_literal": "BBG000BSWKH7",
      "on": ["date"],
      "alias": "qqq"
    }
  ],
  "metrics": [
    { "field": "net_i_premium", "agg": "sum", "alias": "net_i_premium_sum" },
    { "expr": "max(qqq.adj_close)", "alias": "qqq_close" }
  ],
  "order_by": [{ "field": "date", "dir": "asc" }]
}

Filters to Technology-sector figis via a server-side PIT lookup on stock_mapping, groups by date, scalar-broadcast joins QQQ's adjusted close onto every date, surfaces it via a qualified max(qqq.adj_close) expression metric. The response is a plot-ready two-column series: flow on one axis, benchmark on the other. Showcases three features at once: metadata filter, expression metric, scalar-broadcast join.

Tables covered

TableGrainPurpose
v0.underlying_flow_signals_01_daydate × figi × callputDaily net flow with 21-day rolling sums and 2-year z-scores.
v0.underlying_option_flows_01_daydate × figi × callputDaily underlying-level option flow (per-participant nets, greeks).
v0.underlying_option_flows_10_mindate × ten_min_timeframe × figi × callputIntraday underlying-level option flow. Intraday windows capped at 30 days per call.
v0.grouped_flow_signal_01_daydate × group_key × callputGroup-level flow rollups (sector, industry, ETF, ADR, single_stock, all).
v0.px_01_day_adjdate × figiDaily split-adjusted OHLCV.
v0.px_10_min_adjdate × ten_min_timeframe × figiIntraday split-adjusted OHLCV. Intraday windows capped at 30 days per call.

Coverage starts on 2019-11-10; date_range.start earlier than that is rejected with date_before_data_start.

Contract-grain v0.option_flows_{01_day,10_min} are not valid table targets on Aggregate. Requests carrying "table": "v0.option_flows_01_day" (or the 10-min sibling) return HTTP 400 with {"error": "retired_table", "hint": "Use GET /v0/option_flows/{01_day,10_min} directly for contract-grain flow; the direct endpoints support the same dte / abs_delta / callput filters and the by_callput / by_expiry_bucket / by_moneyness_bucket bucket axes."}. For cross-underlying contract-level analytics beyond what the direct endpoint offers, contact support.

Request body reference

metrics

Two shapes. Both live in the same array; pick per metric.

Shorthand:

{ "field": "net_i_vega", "agg": "sum", "alias": "net_i_vega_sum" }

agg is one of sum, avg, min, max, count. field is a physical column on the table. alias is optional for shorthand; when omitted it defaults to <field>_<agg>.

Expression:

{ "expr": "sum_if_call(net_i_premium) - sum_if_put(net_i_premium)", "alias": "net_i_call_minus_put_prem" }

alias is required on expression metrics.

Expression grammar

Supported inside expr:

  • Aggregate functions over a column: sum(x), avg(x), min(x), max(x), count().
  • Arithmetic between aggregates and numeric literals: +, -, *, /, unary -, parentheses.
  • Qualified column refs for joined targets: alias.column, e.g. max(qqq.adj_close).
  • Contract-grain helpers sum_if_call(x), sum_if_put(x), count_if_call(), count_if_put() are rejected on the aggregate-grain tables (v0.underlying_flow_signals_01_day, v0.underlying_option_flows_{01_day,10_min}, v0.grouped_flow_signal_01_day), where a synthetic CP row already carries combined values. Contract-grain flow lives on the direct GET /v0/option_flows/{01_day,10_min} endpoints; the composer does not aggregate over that grain.

Not supported:

  • Subqueries.
  • Window functions.
  • CASE, IF, COALESCE, NULLIF.
  • String operators, comparison operators.
  • Bare column refs at the top level. Every column reference must sit inside an aggregate. sum(net_i_delta) / count() is fine; net_i_delta / count() is rejected with expr_invalid.

Combining call and put

The callput filter is required on tables that carry a callput axis (everything except v0.px_01_day_adj and v0.px_10_min_adj, plus v0.grouped_flow_signal_01_day when queried without a figi predicate).

  • callput: "CP". The row already carries the combined view. Two rules apply on the aggregate-grain tables, split by metric family:
    • Greeks (net_*_delta, net_*_gamma, net_*_vega) are naturally signed on the underlying C and P rows (puts carry negative delta, calls positive), so the CP row is a straight sum C + P. SUM(net_*_delta) under callput: "CP" gives net directional demand; same idea for gamma and vega.
    • Size measures (net_*_shares, net_*_premium) are unsigned magnitudes on the underlying C and P rows, so the pipeline sign-flips the put side on the CP row: CP = C − P. SUM(net_*_premium) under callput: "CP" is a directional read (bullish call premium minus bearish put premium), not a gross activity total. For gross activity, sum across callput: "C" and callput: "P" separately and add.

filters

  • date_range: required. {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}. start must be 2019-11-10 or later.
  • callput: required on tables with a callput axis. C, P, or CP.
  • figi (list) or symbols (list): mutually exclusive. symbols resolves case-insensitively via the terminal-symbol map.
  • range: no-op on every composer-supported table. The composer does not aggregate over contract-grain tables, so dte / abs_delta / strike filters have no home here; pass them on the direct GET /v0/option_flows/{01_day,10_min} endpoints instead.
  • metadata: optional, tables with a figi axis only. Server-side lookup with per-day-of-row semantics.
    • Keys: sector, industry, is_etf, is_adr, is_actively_trading, companyName. market_cap is not available.
    • Sector / industry are case-insensitive on input; the fuzzy-match suggestion in the error body helps recover from typos.
  • group_key: grouped table only. A list of specific group-key strings, e.g. ["Technology", "Healthcare"] for sectors.
  • ten_min_timeframe: 10-min tables only. A list of ISO datetimes for time-of-day slices.

group_by

Rows in the response are one per unique combination of group_by values plus every metric. Empty group_by returns a single row of scalar aggregates.

Allowed dimensions per table:

TableAllowed group_by values
v0.underlying_flow_signals_01_daydate, figi, callput
v0.underlying_option_flows_01_daydate, figi, callput
v0.underlying_option_flows_10_mindate, ten_min_timeframe, figi, callput
v0.grouped_flow_signal_01_daydate, group_key, callput
v0.px_01_day_adjdate, figi
v0.px_10_min_adjdate, ten_min_timeframe, figi

Contract-grain axes (contract_id, expiry_bucket, moneyness_bucket) are not available on any composer table; the composer does not aggregate over the contract grain. Use the direct GET /v0/option_flows/{01_day,10_min} endpoint with its native by_expiry_bucket / by_moneyness_bucket / by_callput axes instead.

joins

LEFT JOIN any of the two price tables onto the base rowset. Max 3 joins per request. Structured shape:

"joins": [
  { "target": "v0.px_01_day_adj", "on": ["figi", "date"], "alias": "px" },
  { "target": "v0.px_01_day_adj", "on_figi_literal": "BBG000BSWKH7", "on": ["date"], "alias": "qqq" }
]

Two shapes:

  • Constituent join. "on": ["figi", "date"]: each base row joins to its own price row.
  • Scalar broadcast. "on_figi_literal": "<figi>": every base row joins to the same price row for the specified figi. This is how a "flow overlaid with QQQ" chart works.

Rules:

  • Allowed targets: v0.px_01_day_adj, v0.px_10_min_adj.
  • LEFT JOIN only. Missing right-side rows produce NULL columns.
  • Grain-mixed joins broadcast: joining v0.underlying_option_flows_10_min to v0.px_01_day_adj on date-only replicates the daily close across all 39 intraday buckets.

Reference joined columns in metric expressions via the alias: {"expr": "max(qqq.adj_close)", "alias": "qqq_close"}.

order_by

Array of {"field": "<name>", "dir": "asc"|"desc"} entries. field must be either a group_by dimension or a metric alias (including expression aliases). Anything else is rejected with unknown_field.

limit

Default 10000. Max 100000. Requests above return 400 limit_out_of_range_for_tier.

Response

JSON up to 25,000 rows:

{
  "rows": [ { "figi": "...", "net_i_delta_sum": 12345.6 }, ... ],
  "row_count": 20,
  "columns": [
    { "name": "figi", "type": "String" },
    { "name": "net_i_delta_sum", "type": "Float64" }
  ]
}

Above 25,000 rows the response streams parquet instead of JSON (same rowset, same columns, binary body). The response header X-Auto-Parquet: true marks the switch; without it the body is JSON.

Every response also carries:

  • X-Row-Count: <N>: number of rows in the response.
  • X-Truncated: true: present when row_count == limit, meaning the result was clipped by your limit.

Errors

Every error body follows {"detail": {"error": "<code>", ...context}}. HTTP status listed alongside.

CodeHTTPMeaning
callput_required400The requested table needs an explicit callput filter (C, P, or CP).
date_before_data_start400date_range.start is earlier than 2019-11-10.
date_range_required400filters.date_range missing. Required on every request.
date_range_too_wide400Intraday tables cap the range at 30 days per call.
unknown_field400A field name in metrics / group_by / order_by isn't valid on the requested table. Body carries the list of allowed names.
unknown_agg400Shorthand metric with agg outside {sum, avg, min, max, count}.
unknown_group_by_dimension400group_by value isn't allowed on the requested table (see the per-table grid above).
unknown_symbol400A value in filters.symbols doesn't resolve. Body includes the unresolved input.
retired_table400table targets v0.option_flows_{01_day,10_min}; contract-grain tables are not composer targets. Use GET /v0/option_flows/{01_day,10_min} directly.
unknown_range_field400filters.range key outside {dte, abs_delta, strike}.
invalid_range_value400min > max, non-numeric bound, or otherwise malformed.
expr_invalid400Expression parse / semantic failure. Body carries a sub-reason (bare column, unsupported operator, unknown function).
expr_alias_required400Expression metric without an alias.
metric_shape_ambiguous400Metric object mixes shorthand (field+agg) and expression (expr) keys.
metric_alias_collision400Two metrics resolve to the same alias.
helper_not_allowed_on_table400sum_if_call / sum_if_put / count_if_call / count_if_put used on an aggregate-grain table where callput is already a row axis.
metadata_attribute_not_available400filters.metadata key not in the supported set. market_cap returns this.
metadata_filter_not_supported_on_table400filters.metadata supplied on a table without a figi axis.
invalid_metadata_value400Sector / industry value doesn't match the taxonomy. Body carries a fuzzy-match suggestion.
join_target_not_allowed400joins[].target outside {v0.px_01_day_adj, v0.px_10_min_adj}.
join_alias_invalid400joins[].alias missing or malformed.
join_figi_literal_invalid400on_figi_literal isn't a well-formed composite FIGI.
too_many_joins400More than 3 entries in joins.
limit_out_of_range_for_tier400limit above the max cap.
Documentation · 03

Programmatic delivery

Read-only bucket access for systematic pipelines: warehouse integrations (recommended) or HMAC key pairs.

Bucket layout

Each bucket is scoped to a specific (tier, grain, aggregation) combination. The bucket names make the scope explicit.

BucketTierGrainAggregationSurfaces
prod-us-option-daily-underlyingpaid01_dayunderlyingunderlying_option_flows/v0/01_day/, underlying_flow_signals/v0/01_day/, grouped_flow_signal/v0/01_day/, px_data/v0/01_day/, option_mapping/v0/ (contract_id_mapping), stock_mapping/v0/, split_history/v0/, _manifests/v0/
prod-us-option-tenmin-underlyingpaid10_minunderlyingunderlying_option_flows/v0/10_min/, px_data/v0/10_min/, stock_mapping/v0/, split_history/v0/, _manifests/v0/
prod-us-option-daily-contractpaid01_daycontractoption_flows/v0/01_day/, px_data/v0/01_day/, option_mapping/v0/ (contract_id_mapping), stock_mapping/v0/, split_history/v0/
prod-us-option-tenmin-contractpaid10_mincontractoption_flows/v0/10_min/, px_data/v0/10_min/, option_mapping/v0/ (contract_id_mapping), stock_mapping/v0/, split_history/v0/
trial-us-option-daily-underlyingtrial01_dayunderlyingunderlying_option_flows/v0/01_day/, px_data/v0/01_day/, stock_mapping/v0/, split_history/v0/, _manifests/v0/. Every partition is 180 days behind the current session.

Within every bucket, the layout is <surface>/v0/<sub_freq>/year=YYYY/date=YYYY-MM-DD/*.parquet, hive-partitioned by year + date where date is the trading date. Reference surfaces (stock_mapping/, split_history/, option_mapping/, grouped_flow_signal/) are full-snapshot reloads: same filename pattern each day, no partition hierarchy. New partitions for date = T-1 land in the 05:00 to 09:00 UTC window on calendar day T.

The _manifests/v0/year=YYYY/date=YYYY-MM-DD/manifest.json file is written as the very last step of the daily publish on each bucket that carries one and is the delivery-complete signal for polling clients (see the Delivery section below for the payload schema).

Call GET /v0/flat-files/credentials to see which buckets your account is allowlisted against; the response includes a buckets array with each bucket root. Pick the one matching the surface you want to read and append <surface>/v0/<sub_freq>/ to compose read paths.

Trial subset

Trial accounts with the programmatic entitlement land on gs://trial-us-option-daily-underlying. Same <surface>/v0/<sub_freq>/ layout as the production buckets, but a strict subset: underlying-level daily flows and daily px only, and every partition is 180 days behind the current session.

gs://trial-us-option-daily-underlying/
├── _manifests/v0/
│   └── year=YYYY/date=YYYY-MM-DD/manifest.json           # delivery-complete signal, written last
├── stock_mapping/v0/
│   └── stock_mapping_*.parquet                           # fresh; full snapshot
├── split_history/v0/
│   └── split_history_*.parquet                           # fresh; full snapshot
├── underlying_option_flows/v0/
│   └── 01_day/year=YYYY/date=YYYY-MM-DD/*.parquet        # T-180d lag
└── px_data/v0/
    └── 01_day/year=YYYY/date=YYYY-MM-DD/*.parquet        # T-180d lag

The trial bucket does not carry contract-level option_flows, 10-minute intraday, underlying_flow_signals, or grouped_flow_signal. A daily append extends the lagged tables by one partition (T-180d). Reference data (stock_mapping/, split_history/) refreshes on the same daily cadence as the paid buckets.

On upgrade to a paid account, Masscrest adds new principal grants (or HMAC keys) against the paid buckets you've been allowlisted for; your trial bucket binding remains readable so your lagged pipeline keeps running during the transition.

Warehouse integration (recommended)

Flat-files delivery is for clients whose corporate environment cannot make outbound calls to api.masscrest.com. Your pipeline reads Parquet exports directly from gs://prod-us-option-daily-underlying/. The recommended path is a warehouse integration: bind a Google service account minted by Snowflake, Databricks, BigQuery, or your own GCP runtime and Masscrest grants read-only IAM against it, with no HMAC pair in the loop. HMAC keys remain available as a fallback for Python / CLI users (section 3 below).

Warehouse integrations not enabled

Bucket delivery is a separate entitlement from the REST API. Contact us at sales@masscrest.com to enable it on your account.

Bind a warehouse principal (Snowflake storage integration, Databricks storage credential, BigQuery cloud-resource connection, or a raw Google service account) directly to each bucket you want to read. Masscrest grants read-only IAM against your principal server-side; the warehouse issues short-lived credentials to itself and streams parquet without any secret material leaving your infrastructure. Provision, list, and revoke bindings from the Warehouse integrations card above (one grant per (principal, bucket) pair).

Snowflake storage integration

Snowflake mints the delegated Google service account when you create the storage integration. Paste that service account into the Warehouse integrations card, pick the Masscrest bucket you want to bind to, and click Grant access. Once the grant flips to active, create an external stage pointing at the bucket and read parquet directly.

-- Step 1: create the storage integration. You can list every Masscrest
-- bucket you plan to read in STORAGE_ALLOWED_LOCATIONS at once. One
-- Snowflake integration + one Masscrest grant per bucket binds them all.
CREATE STORAGE INTEGRATION masscrest_flatfiles
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'GCS'
  ENABLED = TRUE
  STORAGE_ALLOWED_LOCATIONS = (
    'gcs://prod-us-option-daily-underlying/',
    'gcs://prod-us-option-daily-contract/'
  );

-- Step 2: get the Snowflake-minted service-account email.
DESC STORAGE INTEGRATION masscrest_flatfiles;
-- Copy the STORAGE_GCP_SERVICE_ACCOUNT value (looks like
--   abcd1234@gcpuscentral1-1dfa.iam.gserviceaccount.com) into the
-- Warehouse integrations card. Repeat Grant access once per bucket.

Once each grant is active, create an external stage per bucket and run an end-to-end verification against the sentinel file (present in every bucket):

CREATE STAGE masscrest_daily_underlying_stage
  STORAGE_INTEGRATION = masscrest_flatfiles
  URL = 'gcs://prod-us-option-daily-underlying/'
  FILE_FORMAT = (TYPE = PARQUET);

SELECT $1
  FROM @masscrest_daily_underlying_stage/_ping/hello.txt
  (FILE_FORMAT => (TYPE = CSV));

Databricks storage credential

Databricks on GCP mints a Google service account per storage credential. Paste that email into the Warehouse integrations card, bind to each Masscrest bucket you need, then create one external location per bucket and query parquet through Unity Catalog.

-- In your Databricks workspace:
CREATE STORAGE CREDENTIAL masscrest_flatfiles
  WITH DATABRICKS_MANAGED_IDENTITY
  COMMENT 'Masscrest flat-files read-only';

-- Grab the service account email:
DESCRIBE STORAGE CREDENTIAL masscrest_flatfiles;
-- Paste the "Service Account" value into the Warehouse
-- integrations card and click "Grant access" per bucket.

CREATE EXTERNAL LOCATION masscrest_daily_underlying
  URL 'gs://prod-us-option-daily-underlying/'
  WITH (STORAGE CREDENTIAL masscrest_flatfiles);

Once the grant is active:

SELECT *
FROM parquet.`gs://prod-us-option-daily-underlying/underlying_option_flows/v0/01_day/year=2026/date=2026-08-14/`
LIMIT 10;

BigQuery cloud-resource connection

BigQuery uses a cloud-resource connection whose delegated service account you paste into the Warehouse integrations card. One connection + one grant per bucket.

bq mk --connection \
  --location=us \
  --project_id=<your-gcp-project> \
  --connection_type=CLOUD_RESOURCE \
  masscrest_flatfiles

bq show --format=prettyjson --connection \
  <your-gcp-project>.us.masscrest_flatfiles | jq .cloudResource.serviceAccountId
# Paste that service account into the Warehouse integrations card and
# click "Grant access" against each bucket you want to read.

Once the grant is active, create an external table per surface:

CREATE OR REPLACE EXTERNAL TABLE `<your-project>.masscrest.underlying_option_flows_01_day`
WITH CONNECTION `us.masscrest_flatfiles`
OPTIONS (
  format = 'PARQUET',
  uris = ['gs://prod-us-option-daily-underlying/underlying_option_flows/v0/01_day/year=*/date=*/*.parquet'],
  hive_partition_uri_prefix = 'gs://prod-us-option-daily-underlying/underlying_option_flows/v0/01_day/'
);

Raw Google service account

For any GCP-native runtime (Cloud Run, GKE, Composer, Dataflow, custom Compute Engine), create a service account, paste its email into the Warehouse integrations card, bind to the bucket(s) you need, and read with the standard google-cloud-storage client library once each grant is active.

gcloud iam service-accounts create masscrest-reader \
  --project=<your-gcp-project> \
  --display-name="Masscrest flat-files reader"

gcloud iam service-accounts list \
  --project=<your-gcp-project> \
  --filter="displayName:Masscrest flat-files reader" \
  --format="value(email)"
# Paste the resulting email into the Warehouse integrations card.

AWS and Azure runtimes

Warehouse-integration-style principal delegation is not available directly: GCS does not accept AWS IAM roles or Azure Managed Identities as readers. Use the HMAC key pair instead (see section 3 for boto3 and AWS CLI patterns usable from any AWS or Azure runtime).

Grant lifecycle

Each principal binding runs through pending to active to failed or revoked server-side. The Warehouse integrations card polls until the grant flips terminal, then displays a gsutil cat command against the sentinel file so you can prove end-to-end reachability from your warehouse. On tier upgrade (trial to paid), your existing trial bucket binding is left in place and new paid-bucket grants are added, so your existing scripts keep working while you point new ones at the paid bucket set.

HMAC access keys (Python / CLI users)

For local scripts, notebooks, and edge runtimes that can't use a warehouse integration, Masscrest also issues HMAC key pairs that authenticate against the GCS S3-compatible endpoint. Provision or rotate the pair from the HMAC access key card below. This path is a fallback; for production pipelines, prefer the warehouse integration in section 2 (no secret material, shorter blast radius on rotation, native to the SQL engine you're already running).

Not enabled

Programmatic delivery not enabled. Bucket delivery is a separate entitlement from the REST API. Contact us at sales@masscrest.com to enable it on your account.

S3-compatible access

GCS speaks the S3 protocol at https://storage.googleapis.com. HMAC keys authenticate against each bucket the same way native S3 keys do. Bucket names are listed in the table at the top of this page (drop the gs:// prefix in your S3 client); every other path segment is a key prefix. One HMAC pair is bound to one service account that has read-only IAM on the buckets your plan includes, so the same credentials work across all of them.

import boto3
from datetime import date, timedelta

s3 = boto3.client(
    "s3",
    endpoint_url="https://storage.googleapis.com",
    aws_access_key_id="<your HMAC access key>",
    aws_secret_access_key="<your HMAC secret>",
    region_name="auto",
)

d = date.today() - timedelta(days=1)
prefix = f"px_data/v0/01_day/year={d.year}/date={d.isoformat()}/"
for obj in s3.list_objects_v2(
    Bucket="prod-us-option-daily-underlying", Prefix=prefix
).get("Contents", []):
    print(obj["Key"], obj["Size"])

From AWS (Lambda, EC2, ECS, Glue)

The same boto3 client works unchanged from any AWS runtime. No GCP setup is required; the Masscrest HMAC key is just another set of S3 credentials, and the --endpoint-url override reroutes the request to storage.googleapis.com. The recommended pattern is to store your Masscrest HMAC pair in AWS Secrets Manager (or SSM Parameter Store) and fetch it at runtime:

import boto3, json

secrets = boto3.client("secretsmanager", region_name="us-east-1")
creds = json.loads(secrets.get_secret_value(SecretId="masscrest/hmac")["SecretString"])

s3 = boto3.client(
    "s3",
    endpoint_url="https://storage.googleapis.com",
    aws_access_key_id=creds["access_key"],
    aws_secret_access_key=creds["secret"],
    region_name="auto",
)

The AWS CLI works with the same override:

aws s3 --endpoint-url https://storage.googleapis.com ls s3://prod-us-option-daily-underlying/
aws s3 --endpoint-url https://storage.googleapis.com cp \
    s3://prod-us-option-daily-underlying/px_data/v0/01_day/year=2026/date=2026-07-24/ ./local/ --recursive

Common downstream patterns from AWS:

  • Sync into your own S3 for Athena or Redshift Spectrum. Schedule a small EC2, Lambda, or Glue job that copies each day's partition into a bucket you own, then query the parquet directly. No format translation, no re-shaping needed.
  • External stages in Snowflake or Databricks on AWS. Both support S3-compatible sources; point the external stage at s3://prod-us-option-daily-underlying/... with the --endpoint-url override and load daily.
  • Step Functions or Glue Workflows. Poll s3://prod-us-option-daily-underlying/_manifests/v0/year=YYYY/date=YYYY-MM-DD/manifest.json as the "day is ready" signal; the manifest also enumerates every parquet the run wrote on that bucket (fields: workflow_run_id, trading_date, published_at, files: [{path, byte_count}]), so it doubles as the file list for your job.

Notes

  • Credentials are read-only and list-only. They cannot upload, delete, or overwrite objects.
  • Disabling flat-files access on the account revokes the key server-side immediately.
  • For high-volume downloads, run your workload in the same region as the bucket to avoid egress charges.

Data sync

Data can be synced as each daily publish lands: push via an HTTPS webhook you register, or pull by polling the per-bucket manifest file that gets written as the last step of the run.

Webhook

Register an HTTPS endpoint via POST /v1/webhooks (returns HTTP 201) with your API key. When each day's data lands, we push a signed JSON payload. Wire the delivery handler to your flat-files sync job and the day's parquets pull automatically the moment they're published, without polling the manifest.

{
  "event_type": "daily_options_flow.available",
  "trading_date": "2026-07-24",
  "published_at": "2026-07-25T04:37:12Z",
  "workflow_run_id": "aa1cb0c2-5b47-46f1-9c9a-8c9d1c2b0e33"
}

Field semantics:

  • trading_date: the US equity session the data covers, in YYYY-MM-DD (UTC-derived, but represents the exchange trading session, not the write time).
  • published_at: ISO-8601 UTC timestamp of when the notification was dispatched. Data is fully written before this fires, so it is safe to start reading immediately.
  • workflow_run_id: unique id for the publishing run. Use as your idempotency key: dedupe on this if you receive the same message twice.
  • event_type: always daily_options_flow.available today. See "Supported events" below.

Verifying the signature

Every request carries two headers:

  • X-Masscrest-Timestamp: <unix-seconds>
  • X-Masscrest-Signature: sha256=<hex>

Compute HMAC-SHA256 over {timestamp}.{raw_body} using your webhook secret. Reject requests where the timestamp is more than 300 seconds from your clock to block replay attacks.

The secret returned at registration is a 64-character hex string. Decode it to bytes before using it as the HMAC key; UTF-8-encoding the hex string produces the wrong signature.

Python:

import hmac, hashlib, time

def verify(secret_hex: str, timestamp: str, raw_body: bytes, received_sig: str) -> bool:
    # Reject stale requests (replay protection).
    if abs(int(time.time()) - int(timestamp)) > 300:
        return False
    key = bytes.fromhex(secret_hex)                                # NOT secret_hex.encode()
    expected = hmac.new(key, f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
    # Signature header format: "sha256=<hex>[,sha256=<hex>]" (comma-separated during rotation grace)
    for sig in received_sig.split(","):
        if hmac.compare_digest(f"sha256={expected}", sig.strip()):
            return True
    return False

Node.js:

const crypto = require("crypto");

function verify(secretHex, timestamp, rawBody, receivedSig) {
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const key = Buffer.from(secretHex, "hex");                       // NOT Buffer.from(secretHex)
  const expected = crypto
    .createHmac("sha256", key)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");
  // Header may contain two signatures separated by "," during the 24h rotation grace window.
  return receivedSig.split(",").some((sig) =>
    crypto.timingSafeEqual(Buffer.from(`sha256=${expected}`), Buffer.from(sig.trim()))
  );
}

Retry policy

Retry on any non-2xx response or timeout after 10 seconds. One initial delivery plus up to six retries (seven POSTs worst case), spaced 30s, 2m, 10m, 1h, 6h, 24h. Use workflow_run_id as your idempotency key. After the sixth failed retry we disable the webhook and email you.

Supported events

One event type is live today:

Event typeFires when
daily_options_flow.availableThe daily options-flow pipeline completes for a session (T+1).

Managing webhooks

Self-service via your API key. Every endpoint below authenticates with the same X-API-Key you use for /v0/* reads, and each key only sees and manages its own webhooks.

MethodPathPurpose
POST/v1/webhooksRegister a URL. Returns webhook_id + secret (secret shown once).
GET/v1/webhooksList your webhooks (secrets redacted).
GET/v1/webhooks/{id}Fetch one webhook by id (secret redacted).
DELETE/v1/webhooks/{id}Remove a webhook.
POST/v1/webhooks/{id}/rotate-secretGenerate a new secret. Old secret stays valid for 24h.
POST/v1/webhooks/{id}/send-testFire a synthetic delivery to the URL for integration testing. Add ?with_retries=true to engage the retry ladder on 5xx for failure-handling validation.

Polling (alternative)

Each daily publish lands a manifest file on every bucket your plan includes; poll it programmatically inside the 01:00 to 05:00 ET publish window on the day after each session to detect delivery:

gs://<your-bucket>/_manifests/v0/year=YYYY/date=YYYY-MM-DD/manifest.json

Replace <your-bucket> with whichever of your allowlisted buckets you're reading from (see Bucket layout above for the full matrix). The manifest is written as the very last step of the daily publish on each bucket, after every parquet landed on that bucket. Its existence is the delivery-complete signal for that bucket; once visible it is safe to start reading immediately.

Sample payload (polled on prod-us-option-daily-underlying; the files array enumerates only the parquets landed on that bucket):

{
  "workflow_run_id": "aa1cb0c2-5b47-46f1-9c9a-8c9d1c2b0e33",
  "trading_date": "2026-07-30",
  "published_at": "2026-07-31T06:42:22Z",
  "files": [
    { "path": "gs://prod-us-option-daily-underlying/underlying_option_flows/v0/01_day/year=2026/date=2026-07-30/000000000000.parquet", "byte_count": 1479423 },
    { "path": "gs://prod-us-option-daily-underlying/underlying_flow_signals/v0/01_day/year=2026/date=2026-07-30/000000000000.parquet", "byte_count": 3512032 },
    { "path": "gs://prod-us-option-daily-underlying/px_data/v0/01_day/year=2026/date=2026-07-30/000000000000.parquet", "byte_count": 824384 },
    "…"
  ]
}

Data corrections

When we detect an error in already-published data, we notify the client's primary technical contact and communicate the fix within 48 hours.

Corrections are written in place, keyed on the same row grain (date, figi, and contract_id where applicable). Re-sync the affected partition to pick up corrected values.

Documentation · 04

MCP server

Query Masscrest data conversationally from Claude, ChatGPT, and other MCP-aware clients.

MCPhttps://mcp.masscrest.com/mcp

What it is

A Model Context Protocol server that exposes Masscrest's options-flow and price data as tools an LLM can call during a chat. You ask a question in natural language; the assistant picks the right tools, pulls live data, and answers with real numbers.

Why it exists

The flow surface rewards ad-hoc exploration: "what's going on in semis today", "did institutions buy puts on AMD before the print", "which large caps have retail selling into institutional buying right now". Writing a REST client for every one-off question is the slow path. MCP collapses it: the assistant becomes the analyst, Masscrest becomes the data layer, and you stay in the conversation.

The server ships two layers. The data layer: 16 tools covering flow signals, price history, portfolio snapshots, grouped sector rollups, and research search. The editorial layer: analyst playbooks (starting with investigate-flow-event), cohort-behaviour priors sourced from Masscrest research (how retail / institutional / dealer flows typically read), a canonical event taxonomy, and every published research piece. The assistant doesn't just return numbers, it frames them the way a Masscrest analyst would.

How to use it

Connect once per client (see "How to connect it" below; first connect opens a browser sign-in with your Masscrest account, no API key to paste), then ask in natural language. The assistant chains tool calls automatically. A portfolio question typically routes through list_my_portfolios, then portfolio_signal_snapshot, then summarize_flow without you naming the tools.

Tool access mirrors your account entitlements. If a dataset isn't enabled on your subscription, the corresponding tool returns a permission error and the assistant moves on rather than retry.

Examples

Example 1: cross-cohort divergence screen

You: What are the large caps that experienced surging institutional net-delta buying but retail selling today?

Claude: Scanning Masscrest flow signals for today.

calls get_underlying_flow_signals filtered on z_net_i_delta_21dma > 2, z_net_r_delta_21dma < -1, callput='CP'; joins get_stock_mapping to filter to large-caps

Five large-caps where institutions were buying delta aggressively today while retail was net-selling, both measured against each name's own 2-year baseline:

TickerInst z (delta)Retail z (delta)Sector
UNH3.1−2.0Healthcare
META2.8−1.6Communication Services
XOM2.6−1.8Energy
AMD2.4−1.2Technology
JPM2.1−1.4Financial Services

Masscrest's institutional-leads prior from the research archive flags this cohort split as a bullish-skew setup: institutional long-delta accumulation that retail is fading tends to resolve in the institution's direction over the following 5 to 20 sessions. Not every divergence works, but the 2-year context here (institutional z-scores above 2 are top-decile against each name's own history) is what the backtest selects for. UNH and META are the sharpest splits in the group.

Example 2: intraday event investigation

You: Walk me through what institutional and retail did on AMD yesterday before and after earnings.

Claude: Let me pull the intraday cohort split.

loads the investigate-flow-event playbook, then calls intraday_flow_day for AMD on 2026-11-05

Earnings landed after the close (16:00 ET). Here's what each cohort did through the day:

WindowInst net deltaRetail net deltaMM net deltaNotes
09:40 to 11:00+$8.2M−$1.1M−$7.1MInstitutional accumulation pre-print
11:00 to 15:00+$3.4M+$6.8M−$10.2MRetail joins; both sides adding
15:00 to 16:00−$2.1M+$12.4M−$10.3MRetail chase into the close; institutions trimming

Reading this through the contrarian-retail prior: institutional accumulation from the open followed by a heavy retail chase into the final hour is a classic setup where the initial leg has already priced in whatever the smart money knew. The 15:00 to 16:00 retail surge on top of an intraday move is the tell. If the print disappoints, those late buyers tend to supply the selling in the following session.

What's inside

  • Flow signals. Daily and intraday institutional, retail, and dealer net-flow reads on any covered underlying.
  • Portfolio snapshots. The same signal snapshots served on your /portfolios/* pages, callable by name.
  • Grouped & sector rollups. Flow aggregated to industry and sector levels.
  • Guided investigation. The investigate-flow-event playbook, which walks the assistant through the same cohort-behaviour checks a Masscrest analyst runs when an unusual flow event fires.
  • Research archive. Every published Masscrest research piece, searchable by prompt; the assistant cites priors by slug.

For per-tool field definitions, see the Data dictionary and REST API sections above. The MCP tools return the same schemas.

How to connect it

Pick your client below. First connect opens a browser sign-in.

Claude (claude.ai)

  1. Go to claude.ai/settings/connectors (or open Settings → Connectors from the sidebar).
  2. Click Add custom connector at the bottom of the page.
  3. Give it a name (Masscrest) and paste https://mcp.masscrest.com/mcp as the Remote MCP server URL.
  4. Click Add, then sign in with your Masscrest account when the browser window opens.

Claude Desktop

  1. Open Claude Desktop → Settings → Connectors.
  2. Click Add MCP server (or Add custom connector on newer builds).
  3. Enter Masscrest as the name and https://mcp.masscrest.com/mcp as the URL, then confirm.
  4. Complete the browser sign-in flow when it launches.

ChatGPT

Verified on the free tier. Pro / Team / Enterprise may differ.

  1. Open ChatGPT and go to Settings → Connectors.
  2. Click the + button in the top-right of the Connectors page.
  3. Choose Add custom connection.
  4. Enter the Masscrest MCP URL: https://mcp.masscrest.com/mcp.
  5. Click Create.
  6. Open the new connection and enable the individual tools (or toggle "always allow"); tool access is limited by default.

Use the full URL https://mcp.masscrest.com/mcp. The bare host https://mcp.masscrest.com returns 404; the /mcp suffix is required.

GitHub Copilot Chat (VS Code, Agent mode) reads the same .vscode/mcp.json config as the VS Code snippet in the sidebar, so Copilot is supported out of the box on VS Code; the JetBrains and Visual Studio plug-ins don't expose MCP yet.

Notes

  • Access to individual tools mirrors your account entitlements. If a dataset isn't enabled on your subscription, the corresponding tool returns a permission error.
  • Portfolio management (create, rename, add or remove tickers) stays in the web app. The MCP surface is read-only for data queries.
  • Signing out of your Masscrest account also revokes the MCP connector's access on the next tool call.