diff --git a/backend/data/geooptions.db-journal b/backend/data/geooptions.db-journal
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/routers/saxo.py b/backend/routers/saxo.py
index 6e6c5a4..f97803b 100644
--- a/backend/routers/saxo.py
+++ b/backend/routers/saxo.py
@@ -39,6 +39,45 @@ def update_watchlist(req: WatchlistRequest):
return {"symbols": get_watchlist()}
+class ExpandWatchlistRequest(BaseModel):
+ keyword: str
+
+
+@router.post("/watchlist/expand")
+def expand_watchlist(req: ExpandWatchlistRequest):
+ """Adds every catalog entry matching a keyword (ex. 'EURUSD' -> every weekly/monthly
+ FuturesOption root, not just one) — avoids hand-picking each maturity's exact ticker."""
+ matches = get_saxo_catalog(None, req.keyword, limit=200)
+ if not matches:
+ raise HTTPException(status_code=404, detail=f"Aucune entrée du catalogue ne correspond à '{req.keyword}' — rafraîchissez le catalogue d'abord si besoin.")
+ current = get_watchlist()
+ added = [m["symbol"] for m in matches if m["symbol"] not in current]
+ set_watchlist(current + added)
+ return {"symbols": get_watchlist(), "added": added}
+
+
+@router.get("/debug-chain/{symbol}")
+def debug_chain(symbol: str):
+ """Raw, unparsed options-chain snapshot for one symbol — diagnostic endpoint only.
+ Also written to System Logs (details field) so it can be read from the Logs page
+ instead of requiring direct URL/API access."""
+ from services.saxo_client import debug_chain_raw, SaxoNotConnected, SaxoApiError
+ from services.database import log_system_event
+ try:
+ result = debug_chain_raw(symbol)
+ log_system_event(
+ level="INFO", source="saxo_debug",
+ message=f"Raw options chain snapshot for {symbol.upper()} (voir details)",
+ ticker=symbol, details=result,
+ )
+ return result
+ except SaxoNotConnected as e:
+ raise HTTPException(status_code=401, detail=str(e))
+ except SaxoApiError as e:
+ log_system_event(level="ERROR", source="saxo_debug", message=f"debug-chain failed for {symbol.upper()}: {e}", ticker=symbol)
+ raise HTTPException(status_code=502, detail=str(e))
+
+
def _validate_symbol(symbol: str) -> dict:
from services.saxo_client import resolve_instrument, SaxoNotConnected
try:
diff --git a/backend/services/saxo_client.py b/backend/services/saxo_client.py
index c001860..516eb93 100644
--- a/backend/services/saxo_client.py
+++ b/backend/services/saxo_client.py
@@ -242,6 +242,15 @@ def _snapshot_via_subscription(uic: int, asset_type: str = "StockOption") -> Dic
return snapshot
+def debug_chain_raw(symbol: str) -> Dict[str, Any]:
+ """Returns the raw (unparsed) options-chain subscription response for one symbol —
+ diagnostic only, used to pin down exactly where Mid/Greeks/Spot live in Saxo's real
+ payload instead of guessing field names blind."""
+ instrument = resolve_instrument(symbol)
+ subscription = _snapshot_via_subscription(instrument["uic"], asset_type=instrument["asset_type"] or "StockOption")
+ return {"instrument": instrument, "raw": subscription}
+
+
def snapshot_options_chain(symbol: str, target_days: int = 30) -> List[Dict[str, Any]]:
"""
Returns normalized rows ready for services/database.save_saxo_snapshot_rows:
diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts
index e9b5dd6..e43ce5c 100644
--- a/frontend/src/hooks/useApi.ts
+++ b/frontend/src/hooks/useApi.ts
@@ -1701,6 +1701,14 @@ export const useUpdateSaxoWatchlist = () => {
})
}
+export const useExpandSaxoWatchlist = () => {
+ const qc = useQueryClient()
+ return useMutation({
+ mutationFn: (keyword: string) => api.post('/saxo/watchlist/expand', { keyword }).then(r => r.data as { symbols: string[]; added: string[] }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ['saxo-watchlist'] }),
+ })
+}
+
export const useSnapshotSaxoNow = () =>
useMutation({
mutationFn: (symbol: string) => api.post(`/saxo/snapshot-now/${encodeURIComponent(symbol)}`).then(r => r.data),
diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx
index c32051d..6298868 100644
--- a/frontend/src/pages/Config.tsx
+++ b/frontend/src/pages/Config.tsx
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useCycleStepCatalog, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig, useInstrumentsWatchlist, useAddWatchlistInstrument, useRemoveWatchlistInstrument, validateTicker, useSaxoStatus, useDisconnectSaxo, useSaxoWatchlist, useUpdateSaxoWatchlist, useSnapshotSaxoNow, useValidateSaxoWatchlist, useSaxoCatalog, useSaxoCatalogSummary, useRefreshSaxoCatalog, useTestSaxoQuote, useSaxoSettings, useUpdateSaxoSettings, useSnapshotAllSaxoNow, type CycleStepDef } from '../hooks/useApi'
+import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useCycleStepCatalog, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig, useInstrumentsWatchlist, useAddWatchlistInstrument, useRemoveWatchlistInstrument, validateTicker, useSaxoStatus, useDisconnectSaxo, useSaxoWatchlist, useUpdateSaxoWatchlist, useSnapshotSaxoNow, useValidateSaxoWatchlist, useSaxoCatalog, useSaxoCatalogSummary, useRefreshSaxoCatalog, useTestSaxoQuote, useSaxoSettings, useUpdateSaxoSettings, useSnapshotAllSaxoNow, useExpandSaxoWatchlist, type CycleStepDef } from '../hooks/useApi'
import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup, Radar, Link2, Unlink, Camera, ShieldCheck, ExternalLink } from 'lucide-react'
import clsx from 'clsx'
@@ -439,6 +439,9 @@ function SaxoConnectionCard() {
const testQuote = useTestSaxoQuote()
const [quoteSymbol, setQuoteSymbol] = useState('EURUSD')
const [quoteAssetType, setQuoteAssetType] = useState('FxSpot')
+ const expandWatchlist = useExpandSaxoWatchlist()
+ const [expandKeyword, setExpandKeyword] = useState('')
+ const [expandMsg, setExpandMsg] = useState('')
useEffect(() => {
const params = new URLSearchParams(window.location.search)
@@ -463,6 +466,21 @@ function SaxoConnectionCard() {
const removeSymbol = (sym: string) => updateWatchlist.mutate(symbols.filter(s => s !== sym))
+ const handleExpandWatchlist = async () => {
+ const kw = expandKeyword.trim()
+ if (!kw) return
+ setExpandMsg('')
+ try {
+ const r = await expandWatchlist.mutateAsync(kw)
+ setExpandMsg(r.added.length
+ ? `✓ ${r.added.length} ticker(s) ajouté(s) : ${r.added.join(', ')}`
+ : `○ Aucun nouveau ticker pour "${kw}" (déjà présents, ou rafraîchissez le catalogue)`)
+ setExpandKeyword('')
+ } catch (e: any) {
+ setExpandMsg(`✗ ${e?.response?.data?.detail ?? 'échec'}`)
+ }
+ }
+
const handleSnapshotNow = async (sym: string) => {
setSnapMsg('')
try {
@@ -595,6 +613,24 @@ function SaxoConnectionCard() {