142 lines
5.0 KiB
Python
142 lines
5.0 KiB
Python
"""
|
||
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 (GEX1–GEXn), 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 |