feat: saxo price
This commit is contained in:
@@ -60,17 +60,23 @@ def _synthesize_quote(
|
|||||||
|
|
||||||
# Bounded, stable catalogs worth fully caching in our own DB (StockOption/StockIndexOption
|
# 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).
|
# 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
|
# ContractFutures/CfdOnFutures/FxSpot are the underlying-side equivalents of the option
|
||||||
# (FX pairs) are the underlying-side equivalents of the option types above — added so the
|
# types above — added so the Instruments Watchlist "Quote" link
|
||||||
# Instruments Watchlist "Quote" link (services.database.set_instrument_watchlist_saxo_quote_symbol)
|
# (services.database.set_instrument_watchlist_saxo_quote_symbol) has a real catalog to
|
||||||
# has a real catalog to search instead of only ever finding option instruments.
|
# search instead of only ever finding option instruments. Two flavors of "futures" on
|
||||||
CATALOG_ASSET_TYPES = ["FuturesOption", "FxVanillaOption", "ContractFutures", "FxSpot"]
|
# 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.
|
# Which of CATALOG_ASSET_TYPES are options (Instruments Watchlist "Option" picker) vs.
|
||||||
# underlyings (the "Quote" picker) — see routers/instruments_watchlist.py's saxo-*-link
|
# underlyings (the "Quote" picker) — see routers/instruments_watchlist.py's saxo-*-link
|
||||||
# endpoints and frontend/src/pages/Config.tsx's SaxoLinkPicker.
|
# endpoints and frontend/src/pages/Config.tsx's SaxoLinkPicker.
|
||||||
OPTION_ASSET_TYPES = ["FuturesOption", "FxVanillaOption", "StockOption", "StockIndexOption"]
|
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)
|
# symbol -> resolved instrument details, cheap in-process cache (roots don't change within a session)
|
||||||
_root_uic_cache: Dict[str, Dict[str, Any]] = {}
|
_root_uic_cache: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|||||||
@@ -341,6 +341,25 @@ function RiskProfilesCard() {
|
|||||||
// Asset-type filters matching backend services.saxo_client.OPTION_ASSET_TYPES /
|
// 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
|
// 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.
|
// 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<string, string> = {
|
||||||
|
'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 = {
|
const SAXO_LINK_KIND_META = {
|
||||||
option: {
|
option: {
|
||||||
label: 'Option', placeholder: 'Search Saxo option symbol…',
|
label: 'Option', placeholder: 'Search Saxo option symbol…',
|
||||||
@@ -350,7 +369,7 @@ const SAXO_LINK_KIND_META = {
|
|||||||
quote: {
|
quote: {
|
||||||
label: 'Quote', placeholder: 'Search Saxo spot/futures symbol…',
|
label: 'Quote', placeholder: 'Search Saxo spot/futures symbol…',
|
||||||
activeCls: 'text-sky-400 hover:text-sky-300 bg-sky-900/20 border-sky-700/30',
|
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
|
} as const
|
||||||
|
|
||||||
@@ -375,7 +394,7 @@ function SaxoLinkPicker({ ticker, kind, saxoSymbol }: { ticker: string; kind: ke
|
|||||||
<Link2 className="w-2.5 h-2.5" /> {meta.label}: {saxoSymbol}
|
<Link2 className="w-2.5 h-2.5" /> {meta.label}: {saxoSymbol}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={() => setEditing(true)} className="flex items-center gap-1 text-[9px] text-slate-500 hover:text-slate-300 border border-slate-700/40 px-1.5 py-0.5 rounded">
|
<button onClick={() => { setEditing(true); setValue(guessSaxoSearchHint(ticker)); setShowDropdown(true) }} className="flex items-center gap-1 text-[9px] text-slate-500 hover:text-slate-300 border border-slate-700/40 px-1.5 py-0.5 rounded">
|
||||||
<Unlink className="w-2.5 h-2.5" /> {meta.label}
|
<Unlink className="w-2.5 h-2.5" /> {meta.label}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -780,6 +799,7 @@ function SaxoConnectionCard() {
|
|||||||
<option value="Stock">Stock</option>
|
<option value="Stock">Stock</option>
|
||||||
<option value="StockIndex">StockIndex</option>
|
<option value="StockIndex">StockIndex</option>
|
||||||
<option value="ContractFutures">ContractFutures</option>
|
<option value="ContractFutures">ContractFutures</option>
|
||||||
|
<option value="CfdOnFutures">CfdOnFutures</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
onClick={() => testQuote.mutate({ symbol: quoteSymbol, assetType: quoteAssetType })}
|
onClick={() => testQuote.mutate({ symbol: quoteSymbol, assetType: quoteAssetType })}
|
||||||
|
|||||||
Reference in New Issue
Block a user