Liquidity gate
Open interest, day volume, bid size, ask size, and notional premium decide whether the contract belongs in the ranking set.
Reject: low OI, no bid, empty size.
A practical scanner architecture for turning listed expirations and chain snapshots into ranked option candidates with liquidity, moneyness, Greeks, quote age, and spread-aware rejects.
List expirations, pull one chain per expiration, paginate fully, normalize contract fields, reject stale or too-wide quotes, rank by liquidity and Greek constraints, and inspect a contract snapshot before showing an alert.
Primary endpoint
Options chain
Request one underlying and expiration at a time, then paginate through the full chain.
Core filters
Liquidity + quote quality
Use open interest, volume, bid/ask spread, quote age, moneyness, and delta range together.
Final check
Snapshot
Use the contract snapshot to confirm latest quote, latest trade, day stats, Greeks, and IV before alerting.
Scanner input
Begin with Expirations so the scanner only requests dates that are actually listed for the underlying. A hard-coded monthly date will miss 0DTE, weekly, quarterly, and holiday-adjusted cycles, while a guessed weekly date can produce empty chains or misleading coverage gaps.
Once the expiration list is known, request the Options Chain API one expiration at a time. Keep pagination explicit and store the page cursor, because incomplete pagination can look like low liquidity when the real problem is an unfinished chain request.
Ranking model
A scanner should reject bad markets before it ranks exciting ones. Use bid, ask, midpoint, spread percent, quote age, open interest, volume, moneyness, delta, gamma, theta, vega, and implied volatility in the body of the scoring model, not as a separate glossary pasted above the result table.
For unusual activity or momentum scans, join Trades with Quotes so a high-volume contract does not pass just because old prints are large. A useful scanner row should explain whether the signal came from volume/open-interest pressure, a fresh bid/ask update, a price response, or a stale quote that should be rejected.
Open interest, day volume, bid size, ask size, and notional premium decide whether the contract belongs in the ranking set.
Reject: low OI, no bid, empty size.
Strike relative to underlying price controls DTE, delta bucket, and whether the row is ATM, OTM, or deep ITM.
Reject: outside strategy range.
Quote age and spread percent protect the scanner from stale or untradable rows.
Reject: stale quote, crossed market.
Alert handoff
Use the Option Contract Snapshot page model before a row becomes an alert, watchlist item, or dashboard drilldown. A chain row gives scanner breadth; a contract snapshot gives the latest quote, latest trade, day bar, Greeks, IV, and open-interest context for the exact OCC ticker.
The final alert payload should include the underlying, OCC ticker, expiration, strike, side, DTE, moneyness, delta, bid, ask, midpoint, spread percent, quote age, day volume, open interest, alert score, and reject reason if the row was suppressed. That makes the scanner auditable when a user asks why one contract appeared and another did not.
Implementation detail
Treat this page as an implementation boundary, more than a short answer. For How to Build an Options Chain Scanner, the practical record should include the request inputs, timestamp context, selected object, freshness state, and the reason a row was accepted or rejected. That is what keeps a concise answer usable when it becomes a scanner, dashboard, backtest, alert, or support note.
The core page facts are Primary endpoint: Options chain; Core filters: Liquidity + quote quality; Final check: Snapshot. Those values should map to fields in code or to visible labels in the interface. If a developer cannot point to the endpoint family, market-data object, date window, entitlement state, or review artifact behind the answer, the workflow is still too vague for production use.
A reliable implementation should store source URLs or endpoint paths, query parameters, pagination state, market timestamps, application timestamps, and any reject reason beside the result. That evidence makes it possible to rerun the answer later, compare it with another provider, and explain why a value changed after a calendar update, feed repair, plan change, or data-quality review.
In the checklist, the handoff starts with Load listed expirations and ends with Snapshot before alerting. Keep those steps connected with stable identifiers rather than display labels, especially when the workflow moves from stocks into options, from chains into exact contracts, or from current snapshots into historical replay.
Separate discovery, filtering, scoring, and alert handoff so each failure mode is visible.
| Layer | Data to request | Implementation detail |
|---|---|---|
| Discovery | Expirations | Fetch listed dates per underlying; label 0DTE, weekly, monthly, and quarterly cycles in the UI. |
| Universe | Chain pages | Pull each expiration, paginate fully, and normalize every contract into one scanner schema. |
| Quality gate | Quotes and chain quote fields | Reject no-bid, stale, crossed, and wide-spread contracts before ranking. |
| Signal gate | Trades, volume, open interest, Greeks | Score volume/OI pressure, delta bucket, IV, gamma exposure, and price response. |
| Handoff | Contract snapshot | Refresh the exact OCC ticker before alerting or opening a detail panel. |
API example
scanner request sequence
# List expirations first so the scanner does not guess valid dates.
curl -H "Authorization: Bearer YOUR_API_KEY" "https://api.cutemarkets.com/v1/tickers/expirations/SPY/"
# Pull one expiration at a time and paginate until the response has no next cursor.
curl -H "Authorization: Bearer YOUR_API_KEY" "https://api.cutemarkets.com/v1/options/chain/SPY/?expiration_date=2026-05-15&limit=250"
# Inspect a candidate contract before alerting or showing a recommendation.
curl -H "Authorization: Bearer YOUR_API_KEY" "https://api.cutemarkets.com/v1/options/snapshot/SPY/O:SPY260515C00500000/"spread-aware TypeScript filter
type ChainContract = {
ticker: string;
contract_type: "call" | "put";
strike_price: string;
expiration_date: string;
open_interest?: number;
greeks?: { delta?: number; gamma?: number; theta?: number; vega?: number };
last_quote?: { bid_price?: string; ask_price?: string; sip_timestamp?: string };
day?: { volume?: number };
};
const API = "https://api.cutemarkets.com/v1";
async function cute(path: string) {
const response = await fetch(API + path, {
headers: { Authorization: "Bearer " + process.env.CUTEMARKETS_API_KEY },
});
if (!response.ok) throw new Error(await response.text());
return response.json();
}
function scannerScore(contract: ChainContract, underlyingPrice: number, nowMs: number) {
const quote = contract.last_quote;
const bid = Number(quote?.bid_price ?? 0);
const ask = Number(quote?.ask_price ?? 0);
const mid = bid > 0 && ask >= bid ? (bid + ask) / 2 : 0;
const spreadPct = mid > 0 ? (ask - bid) / mid : Infinity;
const quoteAgeSeconds = quote?.sip_timestamp ? (nowMs - Date.parse(quote.sip_timestamp)) / 1000 : Infinity;
const moneyness = Number(contract.strike_price) / underlyingPrice;
const volume = contract.day?.volume ?? 0;
const openInterest = contract.open_interest ?? 0;
const delta = Math.abs(contract.greeks?.delta ?? 0);
if (spreadPct > 0.12) return null;
if (quoteAgeSeconds > 20) return null;
if (openInterest < 100) return null;
if (delta < 0.15 || delta > 0.75) return null;
return {
ticker: contract.ticker,
moneyness,
spreadPct,
quoteAgeSeconds,
liquidityScore: volume + openInterest,
};
}
async function scanExpiration(underlying: string, expiration: string, underlyingPrice: number) {
const chain = await cute("/options/chain/" + underlying + "/?expiration_date=" + expiration + "&limit=250");
const nowMs = Date.now();
return chain.results
.map((contract: ChainContract) => scannerScore(contract, underlyingPrice, nowMs))
.filter(Boolean)
.sort((a, b) => b.liquidityScore - a.liquidityScore);
}How to implement
Request expirations for each underlying and build the scanner queue from listed dates instead of guessed calendar rules.
Pull chain pages for each expiration, persist cursors, and stop only after the full chain is loaded.
Create one row schema with OCC ticker, strike, side, expiration, DTE, moneyness, open interest, Greeks, quote fields, and day stats.
Apply no-bid, stale quote, wide spread, low open-interest, and delta-range rejects before ranking by opportunity.
Refresh the exact contract snapshot before sending an alert or opening a dashboard detail view.
Last verified
This guide was last reviewed on June 4, 2026. Date-sensitive market calendars, provider docs, and listed contracts can change, so production workflows should verify the live source before trading or publishing an automated answer.
No. Request listed expirations first, then scan one expiration at a time so pagination, rate limits, DTE labels, and failures stay explicit.
Bid/ask spread, quote age, open interest, day volume, moneyness, delta bucket, and reject reason matter before any ranking score.
The snapshot refreshes latest quote, latest trade, Greeks, IV, day stats, and open interest for the exact OCC ticker before you alert a user.
Operational usage
Treat this page as a decision boundary for the surrounding API workflow. Before a value from How to Build an Options Chain Scannerenters a scanner, dashboard, calendar, backtest, or support answer, store the source route, request parameters, relevant timestamp, freshness label, and the reason the value is suitable for the next step.
The important implementation habit is to keep display labels separate from stable identifiers. Dates should remain tied to the calendar rule or listed-expiration source that produced them. Option rows should keep the OCC symbol, expiration, strike, side, quote state, and pagination context that created the row. Provider or product answers should keep the entitlement, licensing, and support assumptions visible.
When the workflow changes, rerun the page against one concrete example instead of trusting a general claim. Pick the ticker, date window, endpoint family, and expected output artifact. Then verify that the same terminology appears in the API request, UI label, log entry, and review checklist.
Related pages
Options chain scanner architecture
Read the system-design doc for scanner queues, scoring, and rejects.
Options liquidity scanner
See how liquidity, open interest, spread, and quote age shape scanner output.
Option quote and trade conditions
Use quote and trade conditions to avoid false scanner positives.
Unusual options activity scanner
Compare volume-only signals with quote-aware scanner checks.