Files
QuantumTerminal/backend/options_routes.py
T

142 lines
5.0 KiB
Python
Raw Normal View History

"""
options_routes.py — Options Quant REST Endpoints
=================================================
Quantum Terminal | Phase 4F — Options Quant Engine
Wire into data_server.py:
from options_routes import create_options_router
app.include_router(create_options_router())
Endpoints:
GET /api/options/symbols — supported canonical assets + Yahoo mapping
GET /api/options/{ticker}/snapshot — full metrics (GEX + levels + IV divergence)
GET /api/options/{ticker}/chain — raw calls/puts chain data
GET /api/options/{ticker}/expiries — available expiry dates from Yahoo
"""
import logging
from pathlib import Path
from fastapi import APIRouter, HTTPException, Query
from typing import Optional
log = logging.getLogger("mk.options_routes")
PROJECT_ROOT = Path(__file__).resolve().parent
def create_options_router() -> APIRouter:
router = APIRouter(tags=["options"])
from options_data_manager import get_options_dm, OPTIONS_SYMBOL_MAP
from options_engine import compute_all, compute_all_multi
@router.get("/api/options/symbols")
async def get_options_symbols():
"""
Return list of assets with options support and their Yahoo mappings.
"""
return {
"supported": [
{"canonical": k, "cboe_ticker": v}
for k, v in OPTIONS_SYMBOL_MAP.items()
]
}
@router.get("/api/options/{ticker}/snapshot")
async def get_options_snapshot(
ticker: str,
expiry: Optional[str] = Query(None, description="ISO date e.g. 2025-03-21"),
):
"""
Full options analytics snapshot for one asset.
Includes GEX, flip level, max pain, walls, P/C ratio, IV divergence.
Response is cached 15 minutes — stale data served outside market hours.
"""
canonical = ticker.upper()
if canonical not in OPTIONS_SYMBOL_MAP:
raise HTTPException(
404,
f"{canonical} not supported. Supported: {list(OPTIONS_SYMBOL_MAP.keys())}"
)
odm = get_options_dm()
snap = odm.get_snapshot(canonical, expiry=expiry)
if snap is None:
raise HTTPException(503, f"Could not fetch options data for {canonical}. "
f"Check yfinance install and network.")
try:
metrics = compute_all(snap)
except Exception as e:
log.error(f"Options engine error for {canonical}: {e}")
raise HTTPException(500, f"Options calculation failed: {e}")
return metrics
@router.get("/api/options/{ticker}/chain")
async def get_options_chain(
ticker: str,
expiry: Optional[str] = Query(None),
):
"""
Raw options chain (calls + puts) for one asset and expiry.
Useful for frontend to render custom strike tables.
"""
canonical = ticker.upper()
if canonical not in OPTIONS_SYMBOL_MAP:
raise HTTPException(404, f"{canonical} not supported")
odm = get_options_dm()
snap = odm.get_snapshot(canonical, expiry=expiry)
if snap is None:
raise HTTPException(503, f"Could not fetch chain for {canonical}")
return {
"canonical": snap["canonical"],
"cboe_ticker": snap.get("cboe_ticker"),
"expiry": snap.get("expiry"),
"spot": snap.get("spot"),
"calls": snap.get("calls", []),
"puts": snap.get("puts", []),
"cached_at": snap.get("cached_at"),
"is_stale": snap.get("is_stale", False),
}
@router.get("/api/options/{ticker}/expiries")
async def get_options_expiries(ticker: str):
"""Available expiry dates. Returns list of ISO date strings."""
canonical = ticker.upper()
if canonical not in OPTIONS_SYMBOL_MAP:
raise HTTPException(404, f"{canonical} not supported")
odm = get_options_dm()
return {"canonical": canonical, "expiries": odm.get_available_expiries(canonical)}
@router.get("/api/options/{ticker}/full")
async def get_options_full(
ticker: str,
n: int = Query(8, description="Number of expiries for GEX stack (1-12)"),
):
"""
Full multi-expiry options analytics.
Includes GEX stack (GEX1GEXn), 0DTE GEX, HVL, expected move.
This is the primary endpoint for the enhanced OPTIONS QUANT UI.
"""
canonical = ticker.upper()
if canonical not in OPTIONS_SYMBOL_MAP:
raise HTTPException(404, f"{canonical} not supported")
odm = get_options_dm()
n = max(1, min(n, 12))
snap = odm.get_full_snapshot(canonical, n_expiries=n)
if snap is None:
raise HTTPException(503, f"Could not fetch options data for {canonical}")
try:
return compute_all_multi(snap)
except Exception as e:
log.error(f"Options multi-expiry calc error {canonical}: {e}")
raise HTTPException(500, f"Calculation failed: {e}")
return router