from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List router = APIRouter(prefix="/api/watchlist", tags=["watchlist"]) # Intentionally duplicated from market_data.py to keep this system fully decoupled # from the other instrument-list mechanisms (market_watchlist, INSTRUMENT_MODELS, # instruments.json, options_vol watchlist-tickers). _QUOTE_TYPE_TO_ASSET_CLASS = { "CURRENCY": "forex", "FUTURE": "energy", "INDEX": "indices", "ETF": "etfs", "EQUITY": "equities", "MUTUALFUND": "etfs", } @router.get("/") def list_watchlist(): from services.database import get_instruments_watchlist return get_instruments_watchlist() @router.get("/quotes") def watchlist_quotes(): from services.database import get_instruments_watchlist from services.data_fetcher import get_quote_with_volatility items = [] for row in get_instruments_watchlist(): q = get_quote_with_volatility(row["ticker"]) or {} items.append({ **row, "price": q.get("price"), "change_pct": q.get("change_pct"), "volatility_pct": q.get("volatility_pct"), "volatility_change_pct": q.get("volatility_change_pct"), }) return {"items": items} @router.post("/{ticker}") def add_ticker(ticker: str): from services.data_fetcher import get_quote from services.database import get_instruments_watchlist, add_instrument_watchlist import yfinance as yf ticker = ticker.strip().upper() if any(row["ticker"] == ticker for row in get_instruments_watchlist()): raise HTTPException(409, f"'{ticker}' is already in the watchlist") q = get_quote(ticker) if not q or not q.get("price"): raise HTTPException(400, f"Ticker '{ticker}' not found on yfinance") name = ticker asset_class = "unknown" try: info = yf.Ticker(ticker).fast_info name = getattr(info, "long_name", None) or getattr(info, "short_name", None) or ticker quote_type = getattr(info, "quote_type", "") or "" asset_class = _QUOTE_TYPE_TO_ASSET_CLASS.get(quote_type.upper(), "unknown") except Exception: pass add_instrument_watchlist(ticker, name, asset_class) return {"ticker": ticker, "name": name, "asset_class": asset_class, "price": q["price"]} @router.delete("/{ticker}") def remove_ticker(ticker: str): from services.database import remove_instrument_watchlist remove_instrument_watchlist(ticker.strip().upper()) return {"removed": ticker.strip().upper()} class ReorderBody(BaseModel): tickers: List[str] @router.put("/reorder") def reorder(body: ReorderBody): from services.database import reorder_instruments_watchlist reorder_instruments_watchlist(body.tickers) return {"ok": True} class SaxoLinkBody(BaseModel): saxo_symbol: str | None = None @router.put("/{ticker}/saxo-link") def set_saxo_link(ticker: str, body: SaxoLinkBody): """Link this tracked instrument to a Saxo watchlist symbol (or pass null to unlink) so Options Lab's Saxo section shows broker data for it. Adds the symbol to services.saxo_scheduler's watchlist automatically if it wasn't already there.""" from services.database import set_instrument_watchlist_saxo_symbol ok = set_instrument_watchlist_saxo_symbol(ticker, body.saxo_symbol) if not ok: raise HTTPException(404, f"'{ticker}' is not in the instruments watchlist") return {"ticker": ticker.strip().upper(), "saxo_symbol": (body.saxo_symbol or "").strip().upper() or None}