feat: saxo connector
This commit is contained in:
@@ -1741,3 +1741,30 @@ export const useValidateSaxoWatchlist = () =>
|
||||
queryFn: () => api.get('/saxo/validate').then(r => r.data),
|
||||
enabled: false,
|
||||
})
|
||||
|
||||
export type SaxoCatalogEntry = { uic: number; symbol: string; asset_type: string; description: string; updated_at: string }
|
||||
export type SaxoCatalogSummary = { asset_type: string; count: number; last_refreshed: string }
|
||||
|
||||
export const useSaxoCatalog = (assetType?: string, q?: string) =>
|
||||
useQuery<SaxoCatalogEntry[]>({
|
||||
queryKey: ['saxo-catalog', assetType, q],
|
||||
queryFn: () => api.get('/saxo/catalog', { params: { ...(assetType ? { asset_type: assetType } : {}), ...(q ? { q } : {}) } }).then(r => r.data),
|
||||
enabled: !!(assetType || q),
|
||||
})
|
||||
|
||||
export const useSaxoCatalogSummary = () =>
|
||||
useQuery<SaxoCatalogSummary[]>({
|
||||
queryKey: ['saxo-catalog-summary'],
|
||||
queryFn: () => api.get('/saxo/catalog/summary').then(r => r.data),
|
||||
})
|
||||
|
||||
export const useRefreshSaxoCatalog = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (assetTypes?: string[]) => api.post('/saxo/catalog/refresh', { asset_types: assetTypes ?? null }).then(r => r.data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['saxo-catalog'] })
|
||||
qc.invalidateQueries({ queryKey: ['saxo-catalog-summary'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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, 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, 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'
|
||||
|
||||
@@ -428,8 +428,11 @@ function SaxoConnectionCard() {
|
||||
const updateWatchlist = useUpdateSaxoWatchlist()
|
||||
const snapshotNow = useSnapshotSaxoNow()
|
||||
const { data: validation, refetch: runValidation, isFetching: validating } = useValidateSaxoWatchlist()
|
||||
const { data: catalogSummary } = useSaxoCatalogSummary()
|
||||
const refreshCatalog = useRefreshSaxoCatalog()
|
||||
const [input, setInput] = useState('')
|
||||
const [snapMsg, setSnapMsg] = useState('')
|
||||
const { data: catalogMatches } = useSaxoCatalog(undefined, input.length >= 2 ? input : undefined)
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -528,14 +531,33 @@ function SaxoConnectionCard() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-[10px] text-slate-600">
|
||||
Catalogue local (Futures/FX — tickers Saxo non-évidents, ex. Or = <code>OG:xcme</code>) :{' '}
|
||||
{(catalogSummary ?? []).map(s => `${s.asset_type} (${s.count})`).join(' · ') || 'vide'}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => refreshCatalog.mutate(undefined)}
|
||||
disabled={!status?.connected || refreshCatalog.isPending}
|
||||
className="flex items-center gap-1 text-[10px] text-slate-400 hover:text-slate-200 border border-slate-700/50 px-2 py-1 rounded disabled:opacity-40"
|
||||
>
|
||||
<RefreshCw className={clsx('w-3 h-3', refreshCatalog.isPending && 'animate-spin')} /> Rafraîchir le catalogue
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<input
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && addSymbol()}
|
||||
placeholder="Symbole (ex. SPY)"
|
||||
className="bg-dark-800 border border-slate-700/40 rounded px-3 py-1.5 text-xs text-white w-40 focus:outline-none focus:border-blue-500/50"
|
||||
placeholder="Symbole (ex. SPY, ou tape pour chercher dans le catalogue)"
|
||||
list="saxo-catalog-datalist"
|
||||
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"
|
||||
/>
|
||||
<datalist id="saxo-catalog-datalist">
|
||||
{(catalogMatches ?? []).map(m => (
|
||||
<option key={m.uic} value={m.symbol}>{m.description} ({m.asset_type})</option>
|
||||
))}
|
||||
</datalist>
|
||||
<button onClick={addSymbol} disabled={!input.trim()}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-500 disabled:opacity-40">
|
||||
<Plus className="w-3.5 h-3.5" /> Ajouter
|
||||
|
||||
Reference in New Issue
Block a user