feat: saxo

This commit is contained in:
OpenSquared
2026-07-18 22:20:33 +02:00
parent b0b7f9bfda
commit 0d3a6a6fad
5 changed files with 93 additions and 1 deletions

View File

@@ -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),

View File

@@ -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() {
<Plus className="w-3.5 h-3.5" /> Ajouter
</button>
</div>
<div className="flex items-center gap-2 mb-2">
<input
value={expandKeyword}
onChange={e => setExpandKeyword(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleExpandWatchlist()}
placeholder="Mot-clé sous-jacent (ex. EURUSD, Gold, Brent)"
className="bg-dark-800 border border-slate-700/40 rounded px-3 py-1.5 text-xs text-white w-72 focus:outline-none focus:border-blue-500/50"
/>
<button
onClick={handleExpandWatchlist}
disabled={!expandKeyword.trim() || expandWatchlist.isPending}
title="Ajoute toutes les maturités/racines du catalogue correspondant à ce mot-clé (pas besoin de connaître chaque ticker exact)"
className="flex items-center gap-1 px-3 py-1.5 rounded text-xs bg-dark-700 hover:bg-dark-600 border border-slate-700/50 text-slate-300 disabled:opacity-40"
>
<Plus className="w-3.5 h-3.5" /> Ajouter toutes les maturités
</button>
</div>
{expandMsg && <div className="text-[10px] text-slate-500 mb-2">{expandMsg}</div>}
<div className="flex flex-wrap gap-1.5">
{symbols.map(sym => {
const check = validation?.find(v => v.symbol === sym.toUpperCase())