feat: saxo connector
This commit is contained in:
@@ -1768,3 +1768,14 @@ export const useRefreshSaxoCatalog = () => {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type SaxoQuote = {
|
||||
symbol: string; description: string | null; asset_type: string; uic: number
|
||||
bid: number | null; ask: number | null; last_updated: string | null; price_source: string | null
|
||||
}
|
||||
|
||||
export const useTestSaxoQuote = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ symbol, assetType }: { symbol: string; assetType?: string }) =>
|
||||
api.get<SaxoQuote>(`/saxo/quote/${encodeURIComponent(symbol)}`, { params: assetType ? { asset_type: assetType } : {} }).then(r => r.data),
|
||||
})
|
||||
|
||||
@@ -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, 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, 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'
|
||||
|
||||
@@ -433,6 +433,9 @@ function SaxoConnectionCard() {
|
||||
const [input, setInput] = useState('')
|
||||
const [snapMsg, setSnapMsg] = useState('')
|
||||
const { data: catalogMatches } = useSaxoCatalog(undefined, input.length >= 2 ? input : undefined)
|
||||
const testQuote = useTestSaxoQuote()
|
||||
const [quoteSymbol, setQuoteSymbol] = useState('EURUSD')
|
||||
const [quoteAssetType, setQuoteAssetType] = useState('FxSpot')
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -584,6 +587,48 @@ function SaxoConnectionCard() {
|
||||
})}
|
||||
</div>
|
||||
{snapMsg && <div className="text-[10px] text-slate-500 mt-2">{snapMsg}</div>}
|
||||
|
||||
<div className="mt-4 pt-3 border-t border-slate-700/30">
|
||||
<div className="text-xs text-slate-500 mb-2">
|
||||
Tester un prix hors options (isole si un blocage est spécifique aux options ou plus large) :
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
value={quoteSymbol}
|
||||
onChange={e => setQuoteSymbol(e.target.value.toUpperCase())}
|
||||
placeholder="EURUSD"
|
||||
className="bg-dark-800 border border-slate-700/40 rounded px-3 py-1.5 text-xs text-white w-32 focus:outline-none focus:border-blue-500/50"
|
||||
/>
|
||||
<select
|
||||
value={quoteAssetType}
|
||||
onChange={e => setQuoteAssetType(e.target.value)}
|
||||
className="bg-dark-800 border border-slate-700/40 rounded px-2 py-1.5 text-xs text-white"
|
||||
>
|
||||
<option value="FxSpot">FxSpot</option>
|
||||
<option value="Stock">Stock</option>
|
||||
<option value="StockIndex">StockIndex</option>
|
||||
<option value="ContractFutures">ContractFutures</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={() => testQuote.mutate({ symbol: quoteSymbol, assetType: quoteAssetType })}
|
||||
disabled={!status?.connected || !quoteSymbol.trim() || testQuote.isPending}
|
||||
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"
|
||||
>
|
||||
<RefreshCw className={clsx('w-3.5 h-3.5', testQuote.isPending && 'animate-spin')} /> Tester
|
||||
</button>
|
||||
</div>
|
||||
{testQuote.isSuccess && (
|
||||
<div className="text-xs text-emerald-400 mt-2">
|
||||
✓ {testQuote.data.symbol} ({testQuote.data.description}) — Bid {testQuote.data.bid} / Ask {testQuote.data.ask}
|
||||
<span className="text-slate-600"> · {testQuote.data.price_source}</span>
|
||||
</div>
|
||||
)}
|
||||
{testQuote.isError && (
|
||||
<div className="text-xs text-red-400 mt-2">
|
||||
✗ {(testQuote.error as any)?.response?.data?.detail ?? 'échec'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user