fix(crypto-tail): 周期切换时按周期拉取 tokenIds,修复手动下单 orderbook 不存在

- 后端 initMonitor 支持可选 periodStartUnix,按指定周期取市场与 tokenIds
- 前端周期切换时调用 monitorInit 传入 pushPeriod,确保 tokenIds 与当前周期一致
- 修复切换周期无变化及手动下单报 orderbook does not exist

Made-with: Cursor
This commit is contained in:
WrBug
2026-02-26 20:43:27 +08:00
parent a2be5b7f52
commit cb1f43871a
4 changed files with 47 additions and 19 deletions
@@ -5,7 +5,9 @@ package com.wrbug.polymarketbot.dto
*/
data class CryptoTailMonitorInitRequest(
/** 策略ID */
val strategyId: Long = 0L
val strategyId: Long = 0L,
/** 指定周期开始时间 (Unix 秒),不传则用服务器当前周期 */
val periodStartUnix: Long? = null
)
/**
@@ -147,7 +147,8 @@ class CryptoTailMonitorService(
val account = accountRepository.findById(strategy.accountId).orElse(null)
val nowSeconds = System.currentTimeMillis() / 1000
val periodStartUnix = (nowSeconds / strategy.intervalSeconds) * strategy.intervalSeconds
val periodStartUnix = request.periodStartUnix
?: ((nowSeconds / strategy.intervalSeconds) * strategy.intervalSeconds)
// 获取市场信息
val slug = "${strategy.marketSlugPrefix}-$periodStartUnix"
+40 -15
View File
@@ -64,6 +64,10 @@ const CryptoTailMonitor: React.FC = () => {
const marketChartRef = useRef<HTMLDivElement>(null)
const marketChartInstance = useRef<echarts.ECharts | null>(null)
const lastPeriodStartRef = useRef<number | null>(null)
const selectedStrategyIdRef = useRef<number | null>(null)
useEffect(() => {
selectedStrategyIdRef.current = selectedStrategyId
}, [selectedStrategyId])
// localStorage key for period switch mode
const PERIOD_SWITCH_MODE_KEY = 'cryptoTailMonitor_periodSwitchMode'
@@ -148,7 +152,7 @@ const CryptoTailMonitor: React.FC = () => {
setPendingPeriodData(null)
setIsViewingOldPeriod(false)
try {
const res = await apiService.cryptoTailStrategy.monitorInit(selectedStrategyId)
const res = await apiService.cryptoTailStrategy.monitorInit({ strategyId: selectedStrategyId })
if (res.data.code === 0 && res.data.data) {
setInitData(res.data.data)
} else {
@@ -193,11 +197,23 @@ const CryptoTailMonitor: React.FC = () => {
const lastPeriod = lastPeriodStartRef.current
if (pushPeriod != null && pushPeriod !== lastPeriod) {
// 新周期到来
// 新周期到来:重新拉取 init(含 tokenIds),再更新状态
lastPeriodStartRef.current = pushPeriod
const marketTitle = (data as { marketTitle?: string }).marketTitle
const applyFreshInit = (fresh: CryptoTailMonitorInitResponse) => {
if (selectedStrategyIdRef.current !== fresh.strategyId) return
const merged: CryptoTailMonitorInitResponse = {
...fresh,
periodStartUnix: pushPeriod,
marketTitle: marketTitle ?? fresh.marketTitle ?? ''
}
if (periodSwitchMode === 'manual' && lastPeriod != null) {
setPendingPeriodData(p => p ? { ...p, initData: merged } : null)
} else {
setInitData(merged)
}
}
if (periodSwitchMode === 'manual' && lastPeriod != null) {
// 手动模式:保存新周期数据到 pending,保留当前显示
setPendingPeriodData({
periodStartUnix: pushPeriod,
priceHistory: [newPoint],
@@ -205,26 +221,35 @@ const CryptoTailMonitor: React.FC = () => {
pushData: data
})
setIsViewingOldPeriod(true)
// 更新 pending 数据的 initData
setInitData(prev => {
if (prev) {
setPendingPeriodData(p => p ? { ...p, initData: { ...prev, periodStartUnix: pushPeriod, marketTitle: (data as { marketTitle?: string }).marketTitle ?? prev.marketTitle } } : null)
apiService.cryptoTailStrategy.monitorInit({ strategyId: selectedStrategyId!, periodStartUnix: pushPeriod }).then(res => {
if (res.data?.code === 0 && res.data?.data) applyFreshInit(res.data.data)
else {
setInitData(prev => {
if (prev) setPendingPeriodData(p => p ? { ...p, initData: { ...prev, periodStartUnix: pushPeriod, marketTitle: marketTitle ?? prev.marketTitle ?? '' } } : null)
return prev ?? null
})
}
return prev
}).catch(() => {
setInitData(prev => {
if (prev) setPendingPeriodData(p => p ? { ...p, initData: { ...prev, periodStartUnix: pushPeriod, marketTitle: marketTitle ?? prev.marketTitle ?? '' } } : null)
return prev ?? null
})
})
} else {
// 自动模式或首次推送:直接切换
if (lastPeriod != null) {
setHasSwitchedPeriod(true)
}
if (lastPeriod != null) setHasSwitchedPeriod(true)
setFirstDataTime(newPoint.time)
setInitData(prev => prev ? { ...prev, periodStartUnix: pushPeriod, marketTitle: (data as { marketTitle?: string }).marketTitle ?? prev.marketTitle } : null)
setPriceHistory([newPoint])
setPushData(data)
setIsViewingOldPeriod(false)
setPendingPeriodData(null)
return
apiService.cryptoTailStrategy.monitorInit({ strategyId: selectedStrategyId!, periodStartUnix: pushPeriod }).then(res => {
if (res.data?.code === 0 && res.data?.data) applyFreshInit(res.data.data)
else setInitData(prev => prev ? { ...prev, periodStartUnix: pushPeriod, marketTitle: marketTitle ?? prev.marketTitle ?? '' } : null)
}).catch(() => {
setInitData(prev => prev ? { ...prev, periodStartUnix: pushPeriod, marketTitle: marketTitle ?? prev.marketTitle ?? '' } : null)
})
}
return
} else {
// 同周期:追加数据
if (periodSwitchMode === 'manual' && isViewingOldPeriod && pendingPeriodData) {
+2 -2
View File
@@ -498,8 +498,8 @@ export const apiService = {
apiClient.post<ApiResponse<import('../types').CryptoTailMarketOptionDto[]>>('/crypto-tail-strategy/market-options', {}),
autoMinSpread: (data: { intervalSeconds: number }) =>
apiClient.post<ApiResponse<import('../types').CryptoTailAutoMinSpreadResponse>>('/crypto-tail-strategy/auto-min-spread', data),
monitorInit: (strategyId: number) =>
apiClient.post<ApiResponse<import('../types').CryptoTailMonitorInitResponse>>('/crypto-tail-strategy/monitor/init', { strategyId }),
monitorInit: (data: { strategyId: number; periodStartUnix?: number }) =>
apiClient.post<ApiResponse<import('../types').CryptoTailMonitorInitResponse>>('/crypto-tail-strategy/monitor/init', data),
manualOrder: (data: {
strategyId: number
periodStartUnix: number