diff --git a/backend/services/database.py b/backend/services/database.py index bf02305..692fb23 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -6169,6 +6169,15 @@ def get_saxo_catalog(asset_type: Optional[str] = None, q: Optional[str] = None, return [dict(r) for r in rows] +def get_saxo_catalog_by_symbol(symbol: str) -> Optional[Dict[str, Any]]: + conn = get_conn() + row = conn.execute( + "SELECT * FROM saxo_instrument_catalog WHERE symbol = ? COLLATE NOCASE LIMIT 1", (symbol,) + ).fetchone() + conn.close() + return dict(row) if row else None + + def get_saxo_catalog_summary() -> List[Dict[str, Any]]: conn = get_conn() rows = conn.execute(""" diff --git a/backend/services/saxo_client.py b/backend/services/saxo_client.py index 1eaf4bb..4fdb720 100644 --- a/backend/services/saxo_client.py +++ b/backend/services/saxo_client.py @@ -117,6 +117,20 @@ def resolve_instrument(symbol: str, asset_types: str = _OPTION_ASSET_TYPES) -> D if cache_key in _root_uic_cache: return _root_uic_cache[cache_key] + # Fast path: exchange-suffixed catalog symbols (e.g. "EUU:XCME", "OG:xcme") are Saxo + # identifiers, not natural-language search terms — Saxo's free-text Keywords search + # isn't reliable for them. If we already cached this exact symbol via a catalog + # refresh, use its known Uic directly instead of re-searching live. + from services.database import get_saxo_catalog_by_symbol + cached_entry = get_saxo_catalog_by_symbol(symbol) + if cached_entry: + result = { + "uic": cached_entry["uic"], "symbol": cached_entry["symbol"], + "description": cached_entry["description"], "asset_type": cached_entry["asset_type"], + } + _root_uic_cache[cache_key] = result + return result + data = _get("/ref/v1/instruments", {"Keywords": symbol, "AssetTypes": asset_types}) items = data.get("Data") or data.get("data") or [] if not items: diff --git a/frontend/src/pages/StrategyBuilder.tsx b/frontend/src/pages/StrategyBuilder.tsx index 7af64c5..d43520b 100644 --- a/frontend/src/pages/StrategyBuilder.tsx +++ b/frontend/src/pages/StrategyBuilder.tsx @@ -584,7 +584,7 @@ export default function StrategyBuilder() { ...(saxoCatalog ?? []).map(c => c.symbol), ])).sort() - const { data: chain, isLoading: chainLoading, isError: chainError, refetch: refetchChain, isFetching } = + const { data: chain, isLoading: chainLoading, isError: chainError, error: chainErrorObj, refetch: refetchChain, isFetching } = useOptionChainSlice(symbol, horizonDays, 3) useEffect(() => { @@ -680,7 +680,8 @@ export default function StrategyBuilder() { {chainError && (