mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
修复股票与ETF切换误清策略池
This commit is contained in:
@@ -1402,9 +1402,9 @@ export const api = {
|
||||
: '/api/watchlist/enriched',
|
||||
),
|
||||
|
||||
screenerStrategies: async (assetType: 'stock' | 'etf' = 'stock') => {
|
||||
screenerStrategies: async (assetType?: 'stock' | 'etf') => {
|
||||
const data = await request<{ strategies: StrategyDetail[]; load_errors?: StrategyLoadError[] }>(
|
||||
`/api/strategies?asset_type=${assetType}&timeframe=1d`,
|
||||
`/api/strategies?${assetType ? `asset_type=${assetType}&` : ''}timeframe=1d`,
|
||||
)
|
||||
return { presets: data.strategies, load_errors: data.load_errors }
|
||||
},
|
||||
|
||||
@@ -120,8 +120,8 @@ export function Screener() {
|
||||
const screenerAutoRun = prefs?.screener_auto_run ?? true
|
||||
|
||||
const strategies = useQuery({
|
||||
queryKey: QK.screenerStrategies(assetType),
|
||||
queryFn: () => api.screenerStrategies(assetType),
|
||||
queryKey: QK.screenerStrategies('all'),
|
||||
queryFn: () => api.screenerStrategies(),
|
||||
})
|
||||
|
||||
// 卡片首屏只读取轻量摘要;明细在点击策略或“全部”时按需加载。
|
||||
@@ -154,25 +154,37 @@ export function Screener() {
|
||||
if (latest) setAsOf(latest)
|
||||
}, [dataStatus.data?.enriched?.latest_date])
|
||||
|
||||
const strategyPresets = useMemo(
|
||||
() => (strategies.data?.presets ?? []).filter(s => s.asset_types.includes(assetType)),
|
||||
[strategies.data, assetType],
|
||||
)
|
||||
|
||||
// 策略 ID → 名称映射
|
||||
const strategyIdToName = useMemo(() => {
|
||||
const map: Record<string, string> = {}
|
||||
for (const p of strategies.data?.presets ?? []) {
|
||||
for (const p of strategyPresets) {
|
||||
map[p.id] = p.name
|
||||
}
|
||||
return map
|
||||
}, [strategies.data])
|
||||
}, [strategyPresets])
|
||||
|
||||
// 策略 ID → 完整对象映射(避免每张卡片 find 遍历)
|
||||
const strategyMap = useMemo(() => {
|
||||
const map = new Map<string, ScreenerStrategy>()
|
||||
for (const p of strategies.data?.presets ?? []) {
|
||||
for (const p of strategyPresets) {
|
||||
map.set(p.id, p)
|
||||
}
|
||||
return map
|
||||
}, [strategies.data])
|
||||
}, [strategyPresets])
|
||||
|
||||
const availableStrategyIds = useMemo(() => new Set((strategies.data?.presets ?? []).map(s => s.id)), [strategies.data])
|
||||
const allStrategyIds = useMemo(
|
||||
() => new Set((strategies.data?.presets ?? []).map(s => s.id)),
|
||||
[strategies.data],
|
||||
)
|
||||
const availableStrategyIds = useMemo(
|
||||
() => new Set(strategyPresets.map(s => s.id)),
|
||||
[strategyPresets],
|
||||
)
|
||||
const visiblePool = useMemo(() => pool.filter(id => availableStrategyIds.has(id)), [pool, availableStrategyIds])
|
||||
|
||||
// 策略列表加载后,自动清除池中失效的自定义策略(如本地开发残留的、
|
||||
@@ -183,9 +195,9 @@ export function Screener() {
|
||||
useEffect(() => {
|
||||
if (strategies.isError) return // 拉取失败: 不 prune
|
||||
if (!strategies.isSuccess) return // 加载中: 不 prune
|
||||
if (availableStrategyIds.size === 0) return // 空列表: 不 prune
|
||||
prune(availableStrategyIds)
|
||||
}, [availableStrategyIds, prune, strategies.isError, strategies.isSuccess])
|
||||
if (allStrategyIds.size === 0) return // 空列表: 不 prune
|
||||
prune(allStrategyIds)
|
||||
}, [allStrategyIds, prune, strategies.isError, strategies.isSuccess])
|
||||
|
||||
// 策略文件加载失败时提示用户(避免"策略静默消失"被误判为正常)
|
||||
const loadErrors = strategies.data?.load_errors ?? []
|
||||
@@ -400,7 +412,7 @@ export function Screener() {
|
||||
useEffect(() => {
|
||||
// ETF 模式无股票盘后缓存/ runAll, 单策略走实时单跑, 不触发 runAll
|
||||
if (assetType !== 'stock') return
|
||||
if (!asOf || !strategies.data?.presets?.length || !summaryQuery.isSuccess || runAll.isPending || visiblePool.length === 0) return
|
||||
if (!asOf || strategyPresets.length === 0 || !summaryQuery.isSuccess || runAll.isPending || visiblePool.length === 0) return
|
||||
const runKey = `${asOf}|${visiblePool.join(',')}`
|
||||
if (runAllDateRef.current === runKey) return
|
||||
// 缓存已覆盖当前策略池 → 秒加载, 不触发 runAll
|
||||
@@ -412,7 +424,7 @@ export function Screener() {
|
||||
if (!screenerAutoRun) return
|
||||
runAllDateRef.current = runKey
|
||||
runAll.mutate({ date: asOf, strategyIds: missingStrategyIds })
|
||||
}, [asOf, strategies.data, summaryQuery.isSuccess, visiblePool, cacheCoversPool, missingStrategyIds, screenerAutoRun, assetType, runAll.isPending])
|
||||
}, [asOf, strategyPresets.length, summaryQuery.isSuccess, visiblePool, cacheCoversPool, missingStrategyIds, screenerAutoRun, assetType, runAll.isPending])
|
||||
|
||||
const run = useMutation({
|
||||
mutationFn: ({ id, date }: { id: string; date: string }) =>
|
||||
@@ -621,7 +633,7 @@ export function Screener() {
|
||||
<Layers className="h-3.5 w-3.5" />
|
||||
策略池
|
||||
<span className="ml-0.5 min-w-[28px] h-4 flex items-center justify-center rounded-full bg-accent/15 text-accent text-[10px] font-bold">
|
||||
{visiblePool.length}/{strategies.data?.presets?.length ?? 0}
|
||||
{visiblePool.length}/{strategyPresets.length}
|
||||
</span>
|
||||
</button>
|
||||
{/* 创建策略 */}
|
||||
@@ -943,9 +955,9 @@ export function Screener() {
|
||||
open={showBuilder}
|
||||
onClose={() => setShowBuilder(false)}
|
||||
mode={builderMode}
|
||||
existingStrategyIds={availableStrategyIds}
|
||||
existingStrategyIds={allStrategyIds}
|
||||
onSavedId={async id => {
|
||||
const data = await qc.fetchQuery({ queryKey: QK.screenerStrategies('stock'), queryFn: () => api.screenerStrategies('stock'), staleTime: 0 })
|
||||
const data = await qc.fetchQuery({ queryKey: QK.screenerStrategies('all'), queryFn: () => api.screenerStrategies(), staleTime: 0 })
|
||||
if (!data.presets.some(s => s.id === id)) {
|
||||
throw new Error(`策略 ${id} 已保存但未加载,请检查策略代码`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user