
Massive Options Data
- 67 installs
- 21 repo stars
- Updated August 3, 2026
- starchild-ai-agent/official-skills
Helps with ai & agent building tasks during AI-assisted development.
About
massive-options-data is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- massive-options-data
- AI & Agent Building
- AI-coding skill
Massive Options Data by the numbers
- 67 all-time installs (skills.sh)
- +8 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #5,935 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/starchild-ai-agent/official-skills --skill massive-options-dataAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 67 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 3, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Massive Options Data
Data supply layer for US options market data. Wraps the Massive (Polygon) options REST endpoints with a thin, predictable Python interface.
This skill does NOT generate strategies, signals, rankings, or trading advice — it only exposes options data.
Plan: Developer — REAL field availability
We are on Massive Options Developer ($79/mo). Build your callers against what's actually present in the API responses:
| Field | Developer returns? | Notes |
|---|---|---|
details.* (ticker, strike, expiration, type) | ✅ | Always present. |
implied_volatility | ✅ | Per contract, 15-min delayed. |
greeks (delta, gamma, theta, vega) | ✅ | Per contract, 15-min delayed. |
open_interest | ✅ | Per contract, previous session. |
day.{open,high,low,close,volume,vwap} | ✅ | Option prices (previous session OHLC), 15-min delayed. |
underlying_asset.ticker | ✅ | Always present. |
underlying_asset.price | ✅ | Current underlying price, 15-min delayed. |
last_trade (price, size, timestamp) | ✅ | Last trade, 15-min delayed. |
last_quote (bid/ask) | ❌ | Still not returned on Developer. Cannot calculate real-time spread. |
| Historical IV / IV Rank / IV Percentile | ❌ | Not exposed on any plan; build your own historical series. |
ATM filtering now uses real underlying price:
chain = massive_option_chain_snapshot("AAPL")
# No need for twelvedata — underlying price is now included!
spot_price = chain["results"][0]["underlying_asset"]["price"]
atm_low, atm_high = spot_price * 0.95, spot_price * 1.05
for contract in chain["results"]:
strike = contract["details"]["strike_price"]
if atm_low <= strike <= atm_high:
# This is an ATM contractIf you need real-time bid/ask quotes, upgrade to Advanced ($199/mo).
Pagination — required for any DTE-range scan
Chain snapshots paginate by ticker sort order. A 250-row first page often covers just one expiration. To get all contracts in a DTE window you MUST walk next_url (see massive_paginate in exports.py). Skipping this is the #1 reason a "0 results" scan looks broken.
Typical chain sizes for a single underlying with one expiration window can exceed 450 contracts. Allow at least 4 pages.
Script Usage
python3 - <<'EOF'
import sys, json
sys.path.insert(0, "/data/workspace/skills/massive-options-data")
from exports import (
massive_option_chain_snapshot,
massive_option_contract_snapshot,
massive_option_trades,
massive_option_quotes,
massive_option_aggregates,
massive_list_contracts,
massive_paginate,
)
snap = massive_option_chain_snapshot(underlying="SPY", limit=10)
print(json.dumps(snap.get("results", [])[:2], indent=2))
EOFFunctions (exports.py)
| Function | Endpoint | Purpose |
|---|---|---|
massive_option_chain_snapshot(underlying, **filters) | GET /v3/snapshot/options/{underlying} | Full chain snapshot (price/greeks/IV/OI; quote+trade missing on Starter). |
massive_option_contract_snapshot(underlying, option_ticker) | GET /v3/snapshot/options/{underlying}/{contract} | Single contract snapshot. |
massive_list_contracts(underlying_ticker=None, **filters) | GET /v3/reference/options/contracts | Reference list of option contracts (active or expired). |
massive_option_trades(option_ticker, **range) | GET /v3/trades/{option_ticker} | Historical trade ticks. Available on Developer+. |
massive_option_quotes(option_ticker, **range) | GET /v3/quotes/{option_ticker} | Historical NBBO quotes. Still returns 403 on Developer. Requires Advanced. |
massive_option_aggregates(option_ticker, multiplier, timespan, from_, to, **opts) | GET /v2/aggs/ticker/{ticker}/range/... | OHLCV bars. Minute + second bars on Developer, all bars on Advanced. |
massive_paginate(url, params=None, max_pages=20) | — | Walk next_url cursor pagination. |
All functions return the raw JSON from upstream. HTTP errors raise via Response.raise_for_status().
Hardening notes
- Probe first, code second. Before writing a filter pipeline against a
new endpoint, dump one full record and inspect actual fields. Saves hours of "why is everything filtered out?" debugging.
- Null handling.
greeks,last_quote,last_trademay be absent;
keep as None, never fabricate.
- Caller-id. Every call should include a
caller_idso transparent-proxy
can attribute usage.
Credentials
Set MASSIVE_API_KEY via the agent's secure input flow. The key is injected by sc-proxy when present; the local script also reads it from the environment so it works in BYOK setups.
Source of truth
- Massive options docs: https://massive.com/docs/rest/options/overview
- Follow official field names; when upstream changes the contract, update
this skill rather than papering over it in callers.
"""
Massive (formerly Polygon) Options data — script-mode skill exports.
Usage from a bash block:
python3 - <<'EOF'
import sys
sys.path.insert(0, "/data/workspace/skills/massive-options-data")
from exports import massive_option_chain_snapshot
print(massive_option_chain_snapshot("SPY", limit=5))
EOF
Imports from sidecar.proxy_client (NOT core.http_client) so this skill
stays runnable without the agent platform's core/* modules on PYTHONPATH.
"""
from __future__ import annotations
import os
from typing import Any, Dict, Iterable, List, Optional
try:
from sidecar.proxy_client import proxied_get
except ImportError:
# Local dev / outside the deployed image.
from core.http_client import proxied_get
BASE = "https://api.polygon.io"
API_KEY = os.environ.get("MASSIVE_API_KEY", "")
# ---------------------------------------------------------------------------
# Low-level
# ---------------------------------------------------------------------------
def _headers(extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
h = {"Accept": "application/json"}
if extra:
h.update(extra)
return h
def _get(path: str, params: Optional[Dict[str, Any]] = None,
caller_id: Optional[str] = None) -> Dict[str, Any]:
params = dict(params or {})
if API_KEY and "apikey" not in params and "apiKey" not in params:
params["apikey"] = API_KEY
headers = _headers({"SC-CALLER-ID": caller_id} if caller_id else None)
url = path if path.startswith("http") else f"{BASE}{path}"
r = proxied_get(url, params=params, headers=headers)
r.raise_for_status()
return r.json()
# ---------------------------------------------------------------------------
# Snapshots
# ---------------------------------------------------------------------------
def massive_option_chain_snapshot(
underlying: str,
*,
strike_price: Optional[float] = None,
strike_price_gte: Optional[float] = None,
strike_price_lte: Optional[float] = None,
expiration_date: Optional[str] = None,
expiration_date_gte: Optional[str] = None,
expiration_date_lte: Optional[str] = None,
contract_type: Optional[str] = None, # "call" | "put"
order: Optional[str] = None,
limit: int = 250,
sort: Optional[str] = None,
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""Full option chain snapshot for an underlying."""
params: Dict[str, Any] = {"limit": limit}
if strike_price is not None:
params["strike_price"] = strike_price
if strike_price_gte is not None:
params["strike_price.gte"] = strike_price_gte
if strike_price_lte is not None:
params["strike_price.lte"] = strike_price_lte
if expiration_date:
params["expiration_date"] = expiration_date
if expiration_date_gte:
params["expiration_date.gte"] = expiration_date_gte
if expiration_date_lte:
params["expiration_date.lte"] = expiration_date_lte
if contract_type:
params["contract_type"] = contract_type
if order:
params["order"] = order
if sort:
params["sort"] = sort
return _get(f"/v3/snapshot/options/{underlying.upper()}",
params=params, caller_id=caller_id)
def massive_option_contract_snapshot(
underlying: str,
option_ticker: str,
*,
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""Snapshot for a single option contract.
`option_ticker` is the OPRA-style ticker like `O:SPY260619C00500000`.
"""
return _get(
f"/v3/snapshot/options/{underlying.upper()}/{option_ticker}",
caller_id=caller_id,
)
# ---------------------------------------------------------------------------
# Reference
# ---------------------------------------------------------------------------
def massive_list_contracts(
*,
underlying_ticker: Optional[str] = None,
contract_type: Optional[str] = None, # "call" | "put"
expiration_date: Optional[str] = None,
expiration_date_gte: Optional[str] = None,
expiration_date_lte: Optional[str] = None,
strike_price: Optional[float] = None,
strike_price_gte: Optional[float] = None,
strike_price_lte: Optional[float] = None,
expired: Optional[bool] = None,
as_of: Optional[str] = None,
order: Optional[str] = None,
limit: int = 1000,
sort: Optional[str] = None,
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""List option contracts (active or expired)."""
params: Dict[str, Any] = {"limit": limit}
if underlying_ticker:
params["underlying_ticker"] = underlying_ticker.upper()
if contract_type:
params["contract_type"] = contract_type
if expiration_date:
params["expiration_date"] = expiration_date
if expiration_date_gte:
params["expiration_date.gte"] = expiration_date_gte
if expiration_date_lte:
params["expiration_date.lte"] = expiration_date_lte
if strike_price is not None:
params["strike_price"] = strike_price
if strike_price_gte is not None:
params["strike_price.gte"] = strike_price_gte
if strike_price_lte is not None:
params["strike_price.lte"] = strike_price_lte
if expired is not None:
params["expired"] = "true" if expired else "false"
if as_of:
params["as_of"] = as_of
if order:
params["order"] = order
if sort:
params["sort"] = sort
return _get("/v3/reference/options/contracts",
params=params, caller_id=caller_id)
# ---------------------------------------------------------------------------
# Ticks
# ---------------------------------------------------------------------------
def massive_option_trades(
option_ticker: str,
*,
timestamp_gte: Optional[str] = None,
timestamp_lte: Optional[str] = None,
order: str = "desc",
limit: int = 1000,
sort: str = "timestamp",
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""Historical trade ticks for an option contract."""
params: Dict[str, Any] = {"order": order, "limit": limit, "sort": sort}
if timestamp_gte:
params["timestamp.gte"] = timestamp_gte
if timestamp_lte:
params["timestamp.lte"] = timestamp_lte
return _get(f"/v3/trades/{option_ticker}",
params=params, caller_id=caller_id)
def massive_option_quotes(
option_ticker: str,
*,
timestamp_gte: Optional[str] = None,
timestamp_lte: Optional[str] = None,
order: str = "desc",
limit: int = 1000,
sort: str = "timestamp",
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""Historical NBBO quote ticks for an option contract."""
params: Dict[str, Any] = {"order": order, "limit": limit, "sort": sort}
if timestamp_gte:
params["timestamp.gte"] = timestamp_gte
if timestamp_lte:
params["timestamp.lte"] = timestamp_lte
return _get(f"/v3/quotes/{option_ticker}",
params=params, caller_id=caller_id)
# ---------------------------------------------------------------------------
# Aggregates
# ---------------------------------------------------------------------------
def massive_option_aggregates(
option_ticker: str,
multiplier: int,
timespan: str, # "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year" | "second"
from_: str, # "YYYY-MM-DD" or ms timestamp
to: str,
*,
adjusted: bool = True,
sort: str = "asc",
limit: int = 50000,
caller_id: Optional[str] = None,
) -> Dict[str, Any]:
"""OHLCV aggregate bars for an option contract."""
params: Dict[str, Any] = {
"adjusted": "true" if adjusted else "false",
"sort": sort,
"limit": limit,
}
path = f"/v2/aggs/ticker/{option_ticker}/range/{multiplier}/{timespan}/{from_}/{to}"
return _get(path, params=params, caller_id=caller_id)
# ---------------------------------------------------------------------------
# Pagination helper
# ---------------------------------------------------------------------------
def massive_paginate(
url: str,
params: Optional[Dict[str, Any]] = None,
*,
max_pages: int = 20,
caller_id: Optional[str] = None,
) -> List[Dict[str, Any]]:
"""Walk `next_url` until exhausted or `max_pages` reached.
Returns a flat list of `results` items.
"""
out: List[Dict[str, Any]] = []
next_url: Optional[str] = url
next_params: Optional[Dict[str, Any]] = dict(params or {})
pages = 0
while next_url and pages < max_pages:
pages += 1
data = _get(next_url, params=next_params, caller_id=caller_id)
out.extend(data.get("results") or [])
nu = data.get("next_url")
if not nu:
break
next_url = nu
next_params = None # next_url already contains query string
return out