diff --git a/backend/services/saxo_client.py b/backend/services/saxo_client.py index 5c80236..09a39a7 100644 --- a/backend/services/saxo_client.py +++ b/backend/services/saxo_client.py @@ -60,17 +60,23 @@ def _synthesize_quote( # Bounded, stable catalogs worth fully caching in our own DB (StockOption/StockIndexOption # are far too large to bulk-fetch — those stay resolved on demand via Keywords search). -# ContractFutures (outright futures — Brent, WTI, Gold, Copper, index e-minis) and FxSpot -# (FX pairs) are the underlying-side equivalents of the option types above — added so the -# Instruments Watchlist "Quote" link (services.database.set_instrument_watchlist_saxo_quote_symbol) -# has a real catalog to search instead of only ever finding option instruments. -CATALOG_ASSET_TYPES = ["FuturesOption", "FxVanillaOption", "ContractFutures", "FxSpot"] +# ContractFutures/CfdOnFutures/FxSpot are the underlying-side equivalents of the option +# types above — added so the Instruments Watchlist "Quote" link +# (services.database.set_instrument_watchlist_saxo_quote_symbol) has a real catalog to +# search instead of only ever finding option instruments. Two flavors of "futures" on +# purpose: ContractFutures lists one instrument PER expiry month (e.g. Brent LCOU6 = +# Sep 2026, LCOZ6 = Dec 2026 — needs manual rolling as contracts expire), while +# CfdOnFutures is Saxo's CFD product tracking the same underlying continuously, closer +# to what a "spot price" ticker like yfinance's BZ=F implies. Confirmed 2026-07-23 via +# the Config -> Saxo "Tester" tool that ContractFutures/LCOU6 resolves live for Brent; +# CfdOnFutures availability not yet confirmed for this account. +CATALOG_ASSET_TYPES = ["FuturesOption", "FxVanillaOption", "ContractFutures", "CfdOnFutures", "FxSpot"] # Which of CATALOG_ASSET_TYPES are options (Instruments Watchlist "Option" picker) vs. # underlyings (the "Quote" picker) — see routers/instruments_watchlist.py's saxo-*-link # endpoints and frontend/src/pages/Config.tsx's SaxoLinkPicker. OPTION_ASSET_TYPES = ["FuturesOption", "FxVanillaOption", "StockOption", "StockIndexOption"] -UNDERLYING_ASSET_TYPES = ["ContractFutures", "FxSpot", "StockIndex"] +UNDERLYING_ASSET_TYPES = ["ContractFutures", "CfdOnFutures", "FxSpot", "StockIndex"] # symbol -> resolved instrument details, cheap in-process cache (roots don't change within a session) _root_uic_cache: Dict[str, Dict[str, Any]] = {} diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index a31c47f..8664e0b 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -341,6 +341,25 @@ function RiskProfilesCard() { // Asset-type filters matching backend services.saxo_client.OPTION_ASSET_TYPES / // UNDERLYING_ASSET_TYPES — keeps each picker's search scoped to the catalog rows that // are actually relevant, instead of both searching the same undifferentiated list. +// Saxo has no shared naming convention with yfinance — its catalog search matches on +// Saxo's own instrument description text ("Brent Crude"), not the yfinance ticker code +// ("BZ=F"). This maps the common yfinance futures-root/index tickers to the plain-English +// name worth searching for, so opening a picker pre-fills something useful instead of an +// empty box the user has to guess into. Not exhaustive — anything not listed here (most +// FX pairs, where the Saxo symbol IS the yfinance root, e.g. "EURUSD=X" -> "EURUSD") falls +// back to a best-effort strip of yfinance's own suffix/prefix decoration. +const YFINANCE_SEARCH_HINTS: Record = { + 'BZ=F': 'Brent', 'CL=F': 'WTI Crude', 'NG=F': 'Natural Gas', + 'GC=F': 'Gold', 'SI=F': 'Silver', 'HG=F': 'Copper', 'PL=F': 'Platinum', + '^GSPC': 'S&P 500', '^NDX': 'Nasdaq 100', '^DJI': 'Dow Jones', '^RUT': 'Russell 2000', + '^VIX': 'VIX', +} + +function guessSaxoSearchHint(ticker: string): string { + if (YFINANCE_SEARCH_HINTS[ticker]) return YFINANCE_SEARCH_HINTS[ticker] + return ticker.replace(/=F$|=X$|^\^/g, '') +} + const SAXO_LINK_KIND_META = { option: { label: 'Option', placeholder: 'Search Saxo option symbol…', @@ -350,7 +369,7 @@ const SAXO_LINK_KIND_META = { quote: { label: 'Quote', placeholder: 'Search Saxo spot/futures symbol…', activeCls: 'text-sky-400 hover:text-sky-300 bg-sky-900/20 border-sky-700/30', - assetTypes: 'ContractFutures,FxSpot,StockIndex', + assetTypes: 'ContractFutures,CfdOnFutures,FxSpot,StockIndex', }, } as const @@ -375,7 +394,7 @@ function SaxoLinkPicker({ ticker, kind, saxoSymbol }: { ticker: string; kind: ke {meta.label}: {saxoSymbol} ) : ( - ) @@ -780,6 +799,7 @@ function SaxoConnectionCard() { +