Files
OpenFin/backend/routers/saxo.py
2026-07-18 19:44:32 +02:00

137 lines
4.3 KiB
Python

from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from services import saxo_auth
from services.saxo_scheduler import get_watchlist, set_watchlist
from services.database import (
get_saxo_snapshots, get_saxo_snapshot_symbols,
get_saxo_catalog, get_saxo_catalog_summary, upsert_saxo_catalog_rows,
)
router = APIRouter(prefix="/api/saxo", tags=["saxo"])
class WatchlistRequest(BaseModel):
symbols: List[str]
@router.get("/status")
def status():
return saxo_auth.get_status()
@router.post("/disconnect")
def disconnect():
saxo_auth.disconnect()
return {"disconnected": True}
@router.get("/watchlist")
def watchlist():
return {"symbols": get_watchlist()}
@router.put("/watchlist")
def update_watchlist(req: WatchlistRequest):
set_watchlist(req.symbols)
return {"symbols": get_watchlist()}
def _validate_symbol(symbol: str) -> dict:
from services.saxo_client import resolve_instrument, SaxoNotConnected
try:
info = resolve_instrument(symbol)
return {
"symbol": symbol.upper(), "valid": True, "error": None,
"uic": info["uic"], "matched_symbol": info["symbol"],
"description": info["description"], "asset_type": info["asset_type"],
}
except SaxoNotConnected as e:
return {"symbol": symbol.upper(), "valid": False, "uic": None, "error": str(e)}
except Exception as e:
return {"symbol": symbol.upper(), "valid": False, "uic": None, "error": str(e)}
@router.get("/validate")
def validate_watchlist():
"""Checks each watchlist symbol actually resolves to a real Saxo option-root instrument."""
return [_validate_symbol(s) for s in get_watchlist()]
@router.get("/validate/{symbol}")
def validate_symbol(symbol: str):
return _validate_symbol(symbol)
@router.get("/symbols")
def symbols_with_history():
"""Distinct symbols that already have recorded snapshot history (may differ from the
current watchlist — includes ad-hoc 'snapshot now' calls and previously-tracked symbols)."""
return get_saxo_snapshot_symbols()
@router.post("/snapshot-now/{symbol}")
def snapshot_now(symbol: str):
from services.saxo_client import snapshot_options_chain, SaxoNotConnected, SaxoApiError
from services.database import save_saxo_snapshot_rows
try:
rows = snapshot_options_chain(symbol)
except SaxoNotConnected as e:
raise HTTPException(status_code=401, detail=str(e))
except (ValueError, SaxoApiError) as e:
raise HTTPException(status_code=502, detail=str(e))
save_saxo_snapshot_rows(rows)
return {"symbol": symbol.upper(), "rows_saved": len(rows)}
@router.get("/history")
def history(
symbol: Optional[str] = Query(None),
date_from: Optional[str] = Query(None),
date_to: Optional[str] = Query(None),
):
return get_saxo_snapshots(symbol, date_from, date_to)
class CatalogRefreshRequest(BaseModel):
asset_types: Optional[List[str]] = None # default: CATALOG_ASSET_TYPES (FuturesOption, FxVanillaOption)
@router.post("/catalog/refresh")
def refresh_catalog(req: CatalogRefreshRequest):
from services.saxo_client import list_instruments, CATALOG_ASSET_TYPES, SaxoNotConnected, SaxoApiError
asset_types = req.asset_types or CATALOG_ASSET_TYPES
counts: Dict[str, int] = {}
try:
for asset_type in asset_types:
items = list_instruments(asset_type)
rows = [{
"uic": item.get("Identifier") or item.get("Uic"),
"symbol": item.get("Symbol"),
"asset_type": asset_type,
"description": item.get("Description"),
} for item in items if item.get("Identifier") or item.get("Uic")]
upsert_saxo_catalog_rows(rows)
counts[asset_type] = len(rows)
except SaxoNotConnected as e:
raise HTTPException(status_code=401, detail=str(e))
except SaxoApiError as e:
raise HTTPException(status_code=502, detail=str(e))
return {"refreshed": counts}
@router.get("/catalog")
def catalog(
asset_type: Optional[str] = Query(None),
q: Optional[str] = Query(None),
limit: int = Query(200),
):
return get_saxo_catalog(asset_type, q, limit)
@router.get("/catalog/summary")
def catalog_summary():
return get_saxo_catalog_summary()