80 lines
2.1 KiB
Python
80 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
|
|
from app.market.es_options import (
|
|
QUOTE_BATCH,
|
|
candidate_symbols,
|
|
search_from_quotes,
|
|
years_to_expiry,
|
|
)
|
|
|
|
|
|
def schwab_rest_client(settings):
|
|
from schwab.auth import client_from_token_file
|
|
|
|
if not settings.schwab_token_path.exists():
|
|
raise FileNotFoundError(f"No Schwab token at {settings.schwab_token_path}")
|
|
return client_from_token_file(
|
|
str(settings.schwab_token_path),
|
|
settings.schwab_api_key,
|
|
settings.schwab_app_secret,
|
|
)
|
|
|
|
|
|
def fetch_quotes(client, symbols: list[str]) -> dict[str, dict]:
|
|
quotes: dict[str, dict] = {}
|
|
for index in range(0, len(symbols), QUOTE_BATCH):
|
|
payload = client.get_quotes(symbols[index : index + QUOTE_BATCH]).json()
|
|
for symbol, body in payload.items():
|
|
if symbol != "errors":
|
|
quotes[symbol] = body
|
|
return quotes
|
|
|
|
|
|
def underlying_price(client, symbol: str = "/ES") -> tuple[str, float]:
|
|
payload = client.get_quotes([symbol]).json()
|
|
for returned, body in payload.items():
|
|
if returned == "errors":
|
|
continue
|
|
if body.get("assetMainType") != "FUTURE":
|
|
continue
|
|
fields = body.get("quote") or {}
|
|
price = fields.get("mark")
|
|
if price is None:
|
|
price = fields.get("lastPrice")
|
|
if price is None:
|
|
continue
|
|
return returned, float(price)
|
|
raise ValueError("No /ES futures quote")
|
|
|
|
|
|
def run_search(
|
|
settings,
|
|
*,
|
|
day: date,
|
|
root: str,
|
|
side: str,
|
|
mode: str,
|
|
low: float,
|
|
high: float,
|
|
now: datetime | None = None,
|
|
client=None,
|
|
) -> dict:
|
|
rest = client or schwab_rest_client(settings)
|
|
contract, forward = underlying_price(rest)
|
|
symbols = candidate_symbols(root, side, forward)
|
|
quotes = fetch_quotes(rest, symbols)
|
|
result = search_from_quotes(
|
|
day=day,
|
|
side=side,
|
|
mode=mode,
|
|
low=low,
|
|
high=high,
|
|
forward=forward,
|
|
quotes=quotes,
|
|
now=now,
|
|
)
|
|
result["underlying"] = contract
|
|
result["years"] = years_to_expiry(day, now)
|
|
return result
|