From 212bdf6678a4b7bbf66025c9802b9077e3097798 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Sat, 18 Jul 2026 18:54:12 +0200 Subject: [PATCH] feat: sexo history --- backend/routers/saxo.py | 10 +++++++--- backend/services/saxo_client.py | 27 ++++++++++++++++++++------- frontend/src/hooks/useApi.ts | 5 ++++- frontend/src/pages/Config.tsx | 4 +++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/backend/routers/saxo.py b/backend/routers/saxo.py index 6ea576a..e477535 100644 --- a/backend/routers/saxo.py +++ b/backend/routers/saxo.py @@ -37,10 +37,14 @@ def update_watchlist(req: WatchlistRequest): def _validate_symbol(symbol: str) -> dict: - from services.saxo_client import resolve_option_root_uic, SaxoNotConnected + from services.saxo_client import resolve_instrument, SaxoNotConnected try: - uic = resolve_option_root_uic(symbol) - return {"symbol": symbol.upper(), "valid": True, "uic": uic, "error": None} + 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: diff --git a/backend/services/saxo_client.py b/backend/services/saxo_client.py index 6022bf1..33b7785 100644 --- a/backend/services/saxo_client.py +++ b/backend/services/saxo_client.py @@ -26,8 +26,8 @@ logger = logging.getLogger(__name__) _OPTION_ASSET_TYPES = "StockOption,StockIndexOption,FuturesOption,ContractFutures" -# symbol -> option root Uic, cheap in-process cache (roots don't change within a session) -_root_uic_cache: Dict[str, int] = {} +# symbol -> resolved instrument details, cheap in-process cache (roots don't change within a session) +_root_uic_cache: Dict[str, Dict[str, Any]] = {} class SaxoNotConnected(Exception): @@ -91,7 +91,9 @@ def _first(d: Dict[str, Any], *keys: str) -> Any: return None -def resolve_option_root_uic(symbol: str) -> int: +def resolve_instrument(symbol: str) -> Dict[str, Any]: + """Full matched instrument (Uic + Symbol/Description/AssetType) — surfaced by /validate so + a wrong-ticker guess is visibly distinguishable from an account-entitlement error.""" if symbol in _root_uic_cache: return _root_uic_cache[symbol] @@ -100,12 +102,23 @@ def resolve_option_root_uic(symbol: str) -> int: if not items: raise ValueError(f"Aucun option root Saxo trouvé pour '{symbol}' (réponse: {list(data.keys())})") - uic = _first(items[0], "Uic", "uic", "Identifier") + item = items[0] + uic = _first(item, "Uic", "uic", "Identifier") if uic is None: - raise ValueError(f"Champ Uic introuvable dans la réponse instruments pour '{symbol}': {items[0]}") + raise ValueError(f"Champ Uic introuvable dans la réponse instruments pour '{symbol}': {item}") - _root_uic_cache[symbol] = int(uic) - return int(uic) + result = { + "uic": int(uic), + "symbol": _first(item, "Symbol", "symbol"), + "description": _first(item, "Description", "description"), + "asset_type": _first(item, "AssetType", "assetType"), + } + _root_uic_cache[symbol] = result + return result + + +def resolve_option_root_uic(symbol: str) -> int: + return resolve_instrument(symbol)["uic"] def get_option_space(root_uic: int) -> Dict[str, Any]: diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index a98d5be..9dc32a4 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -1730,7 +1730,10 @@ export const useSaxoSymbols = () => queryFn: () => api.get('/saxo/symbols').then(r => r.data), }) -export type SaxoValidation = { symbol: string; valid: boolean; uic: number | null; error: string | null } +export type SaxoValidation = { + symbol: string; valid: boolean; uic: number | null; error: string | null + matched_symbol?: string; description?: string; asset_type?: string +} export const useValidateSaxoWatchlist = () => useQuery({ diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index 34e2c95..6746116 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -547,7 +547,9 @@ function SaxoConnectionCard() { return ( {sym} {check && (check.valid ? : )}