from datetime import date, datetime from fastapi import FastAPI from fastapi.testclient import TestClient from app.api.routes import router as api_router from app.config import Settings from app.runtime import Runtime from app.market.es_options import ( api_symbol, attach_deltas, black76_delta, daily_root, filter_contracts, from_tos_symbol, monthly_root, nearby_expirations, parse_option_quote, search_from_quotes, tos_symbol, ) def test_weekday_roots_match_the_verified_august_week(): assert daily_root(date(2026, 8, 14)) == "EW2Q26" assert daily_root(date(2026, 8, 17)) == "E3AQ26" assert daily_root(date(2026, 8, 18)) == "E3BQ26" assert daily_root(date(2026, 8, 19)) == "E3CQ26" assert daily_root(date(2026, 8, 20)) == "E3DQ26" assert daily_root(date(2026, 8, 21)) == "EW3Q26" def test_monthly_uses_es_root_only_on_quarterlies(): assert monthly_root(date(2026, 8, 21)) == "EW3Q26" assert monthly_root(date(2026, 9, 18)) == "ESU26" def test_tos_mapping_strips_and_restores_exchange_suffix(): assert from_tos_symbol("./E3AQ26P7780:XCME") == "./E3AQ26P7780" assert tos_symbol("./E3AQ26P7780") == "./E3AQ26P7780:XCME" assert api_symbol("E3AQ26", "P", 7780) == "./E3AQ26P7780" def test_dropdown_for_friday_august_14(): rows = nearby_expirations(date(2026, 8, 14)) assert [(row.kind, row.date, row.root) for row in rows] == [ ("daily", date(2026, 8, 14), "EW2Q26"), ("daily", date(2026, 8, 17), "E3AQ26"), ("daily", date(2026, 8, 18), "E3BQ26"), ("weekly", date(2026, 8, 14), "EW2Q26"), ("monthly", date(2026, 8, 21), "EW3Q26"), ] def test_dropdown_skips_the_weekend(): rows = nearby_expirations(date(2026, 8, 15)) assert [row.date for row in rows if row.kind == "daily"] == [ date(2026, 8, 17), date(2026, 8, 18), date(2026, 8, 19), ] assert rows[-1].root == "EW3Q26" assert rows[-1].date == date(2026, 8, 21) def test_monthly_rolls_to_the_september_es_root_after_the_august_third_friday(): rows = nearby_expirations(date(2026, 8, 22)) monthly = rows[-1] assert monthly.kind == "monthly" assert monthly.date == date(2026, 9, 18) assert monthly.root == "ESU26" def test_abs_delta_filter_keeps_the_015_to_025_band(): rows = [ {"strike": 7700, "mark": 3.0, "abs_delta": 0.05}, {"strike": 7770, "mark": 6.8, "abs_delta": 0.16}, {"strike": 7780, "mark": 8.3, "abs_delta": 0.206}, {"strike": 7850, "mark": 22.0, "abs_delta": 0.40}, ] kept = filter_contracts(rows, "delta", 0.15, 0.25) assert [row["strike"] for row in kept] == [7770, 7780] def test_price_filter_uses_mark(): rows = [ {"strike": 7765, "mark": 6.2, "abs_delta": 0.14}, {"strike": 7780, "mark": 8.3, "abs_delta": 0.21}, {"strike": 7790, "mark": 10.1, "abs_delta": 0.26}, ] kept = filter_contracts(rows, "price", 6.0, 8.0) assert [row["strike"] for row in kept] == [7765] def test_black76_put_delta_is_negative_and_increases_toward_atm(): otm = black76_delta(7827, 7780, 3 / 365.25, 0.0855, "P") nearer = black76_delta(7827, 7800, 3 / 365.25, 0.0855, "P") assert otm is not None and nearer is not None assert otm < 0 and nearer < 0 assert abs(nearer) > abs(otm) def test_search_filters_parsed_quotes_by_mark(): quotes = { "./E3AQ26P7765": { "assetMainType": "FUTURE_OPTION", "quote": {"mark": 6.2, "bidPrice": 6.0, "askPrice": 6.2, "totalVolume": 43, "openInterest": 437}, "reference": {"strikePrice": 7765, "contractType": "P", "description": "./E3AQ26P7765:XCME"}, }, "./E3AQ26P7825": { "assetMainType": "FUTURE_OPTION", "quote": {"mark": 22.0, "bidPrice": 21.8, "askPrice": 22.0, "totalVolume": 10, "openInterest": 20}, "reference": {"strikePrice": 7825, "contractType": "P", "description": "./E3AQ26P7825:XCME"}, }, "errors": {"invalidSymbols": ["./E3AQ26P9999"]}, } result = search_from_quotes( day=date(2026, 8, 17), side="P", mode="price", low=6, high=8, forward=7827, quotes=quotes, now=datetime(2026, 8, 14, 10, 0), ) assert [row["tos"] for row in result["contracts"]] == ["./E3AQ26P7765:XCME"] assert result["contracts"][0]["mark"] == 6.2 def test_equity_payload_is_not_treated_as_a_futures_option(): assert parse_option_quote("ES", {"assetMainType": "EQUITY", "quote": {"mark": 72}, "reference": {}}) is None def test_attach_deltas_labels_the_015_band_from_live_shaped_marks(): contracts = [ {"strike": 7770.0, "mark": 6.8}, {"strike": 7780.0, "mark": 8.3}, {"strike": 7825.0, "mark": 22.0}, ] ranked = attach_deltas(contracts, 7827.0, 3 / 365.25, "P", 0.0855) band = filter_contracts(ranked, "delta", 0.15, 0.25) assert [row["strike"] for row in band] == [7770.0, 7780.0] def _client(tmp_path): app = FastAPI() app.include_router(api_router) app.state.runtime = Runtime(Settings(manual_lines_path=tmp_path / "lines.json")) return TestClient(app) def test_expirations_endpoint_needs_no_schwab(tmp_path): response = _client(tmp_path).get("/api/es-options/expirations") assert response.status_code == 200 rows = response.json()["expirations"] assert len(rows) == 5 assert [row["kind"] for row in rows] == ["daily", "daily", "daily", "weekly", "monthly"] assert all(row["root"] and row["date"] and row["label"] for row in rows) def test_search_endpoint_uses_the_injected_snapshot(tmp_path, monkeypatch): def fake_search(settings, **kwargs): assert kwargs["root"] == "E3AQ26" assert kwargs["side"] == "P" assert kwargs["mode"] == "price" return { "underlying": "/ESU26", "underlying_price": 7827.0, "iv": 0.085, "delta_approx": True, "contracts": [ { "symbol": "./E3AQ26P7765", "tos": "./E3AQ26P7765:XCME", "strike": 7765, "mark": 6.2, "abs_delta": 0.139, } ], } monkeypatch.setattr("app.api.routes.run_search", fake_search) response = _client(tmp_path).get( "/api/es-options/search", params={"date": "2026-08-17", "root": "E3AQ26", "side": "P", "mode": "price", "min": 6, "max": 8}, ) assert response.status_code == 200 assert response.json()["contracts"][0]["tos"] == "./E3AQ26P7765:XCME" def test_search_rejects_a_bad_date(tmp_path): response = _client(tmp_path).get( "/api/es-options/search", params={"date": "17-08-2026", "root": "E3AQ26", "min": 0.15, "max": 0.25}, ) assert response.status_code == 400