fix(web): poll klines API every 5s so new on-chain candles render live
This commit is contained in:
@@ -26,6 +26,8 @@ export const TradingViewChart: React.FC<TradingViewChartProps> = ({
|
|||||||
// 用 ref 保活最新真实价格,避免 5s 定时器闭包捕获到过期值
|
// 用 ref 保活最新真实价格,避免 5s 定时器闭包捕获到过期值
|
||||||
const priceRef = useRef<number>(currentPrice);
|
const priceRef = useRef<number>(currentPrice);
|
||||||
const lastTickRef = useRef<{ time: number; price: number } | null>(null);
|
const lastTickRef = useRef<{ time: number; price: number } | null>(null);
|
||||||
|
// 已绘制到哪根柱(防止轮询/心跳重复写入同一根柱)
|
||||||
|
const lastBarTimeRef = useRef<number>(0);
|
||||||
const [timeframe, setTimeframe] = useState<string>('5s');
|
const [timeframe, setTimeframe] = useState<string>('5s');
|
||||||
|
|
||||||
// 同步最新真实价格到 ref(同时立即打点一次,保证首根柱出现)
|
// 同步最新真实价格到 ref(同时立即打点一次,保证首根柱出现)
|
||||||
@@ -91,35 +93,61 @@ export const TradingViewChart: React.FC<TradingViewChartProps> = ({
|
|||||||
seriesRef.current = candlestickSeries;
|
seriesRef.current = candlestickSeries;
|
||||||
|
|
||||||
// 2. 从后端 API 异步获取真实 K 线(Indexer 聚合链上成交)
|
// 2. 从后端 API 异步获取真实 K 线(Indexer 聚合链上成交)
|
||||||
const loadRealKlines = async () => {
|
const fetchKlines = async (): Promise<any[] | null> => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${API_BASE}/api/v1/charts/klines?market_address=${marketAddress}&outcome_index=${outcomeIndex}&timeframe=${timeframe}`
|
`${API_BASE}/api/v1/charts/klines?market_address=${marketAddress}&outcome_index=${outcomeIndex}&timeframe=${timeframe}`
|
||||||
);
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.bars && data.bars.length > 0) {
|
if (data.bars && data.bars.length > 0) return data.bars;
|
||||||
candlestickSeries.setData(data.bars);
|
|
||||||
chart.timeScale().fitContent();
|
|
||||||
const last = data.bars[data.bars.length - 1];
|
|
||||||
lastTickRef.current = { time: last.time, price: Number(last.close) };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Load klines failed:', e);
|
console.warn('Load klines failed:', e);
|
||||||
}
|
}
|
||||||
// 无历史成交:不画任何假柱,等 5s 心跳用真实边际价打点
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadRealKlines = async () => {
|
||||||
|
const bars = await fetchKlines();
|
||||||
|
if (bars && bars.length > 0) {
|
||||||
|
candlestickSeries.setData(bars as any);
|
||||||
|
chart.timeScale().fitContent();
|
||||||
|
const last = bars[bars.length - 1];
|
||||||
|
lastBarTimeRef.current = last.time;
|
||||||
|
lastTickRef.current = { time: last.time, price: Number(last.close) };
|
||||||
|
}
|
||||||
|
// 无历史成交:不画任何假柱,等心跳用真实边际价打点
|
||||||
};
|
};
|
||||||
|
|
||||||
loadRealKlines();
|
loadRealKlines();
|
||||||
|
|
||||||
// 3. 每 5 秒用最新真实边际价打点(价格未变化则不重复写入,绝不伪造曲线)
|
// 3. 每 5 秒轮询后端:把 indexer 最新聚合出的成交柱增量追加/更新到图表
|
||||||
const interval = setInterval(() => {
|
const refreshInterval = setInterval(async () => {
|
||||||
|
const bars = await fetchKlines();
|
||||||
|
if (!bars || bars.length === 0) return;
|
||||||
|
const series = seriesRef.current;
|
||||||
|
if (!series) return;
|
||||||
|
const lastBarTime = lastBarTimeRef.current;
|
||||||
|
// 仅处理「当前已绘制柱」与「其后新增柱」,避免重复写历史
|
||||||
|
for (const b of bars) {
|
||||||
|
if (b.time < lastBarTime) continue;
|
||||||
|
series.update({ time: b.time, open: b.open, high: b.high, low: b.low, close: b.close });
|
||||||
|
}
|
||||||
|
const newest = bars[bars.length - 1];
|
||||||
|
if (newest.time >= lastBarTime) {
|
||||||
|
lastBarTimeRef.current = newest.time;
|
||||||
|
lastTickRef.current = { time: newest.time, price: Number(newest.close) };
|
||||||
|
}
|
||||||
|
}, BAR_SECONDS * 1000);
|
||||||
|
|
||||||
|
// 4. 每 5 秒用最新真实边际价打点(仅在最新成交柱之后,价格未变化则不重复写入,绝不伪造曲线)
|
||||||
|
const heartbeatInterval = setInterval(() => {
|
||||||
const p = priceRef.current;
|
const p = priceRef.current;
|
||||||
const series = seriesRef.current;
|
const series = seriesRef.current;
|
||||||
if (p <= 0 || !series) return;
|
if (p <= 0 || !series) return;
|
||||||
const barTime = barTimeOfNow();
|
const barTime = barTimeOfNow();
|
||||||
|
if (barTime < lastBarTimeRef.current) return;
|
||||||
const last = lastTickRef.current;
|
const last = lastTickRef.current;
|
||||||
if (last && last.time === barTime && last.price === p) return;
|
if (last && last.time === barTime && last.price === p) return;
|
||||||
series.update({
|
series.update({
|
||||||
@@ -129,6 +157,9 @@ export const TradingViewChart: React.FC<TradingViewChartProps> = ({
|
|||||||
low: p,
|
low: p,
|
||||||
close: p,
|
close: p,
|
||||||
});
|
});
|
||||||
|
if (barTime > lastBarTimeRef.current) {
|
||||||
|
lastBarTimeRef.current = barTime;
|
||||||
|
}
|
||||||
lastTickRef.current = { time: barTime, price: p };
|
lastTickRef.current = { time: barTime, price: p };
|
||||||
}, BAR_SECONDS * 1000);
|
}, BAR_SECONDS * 1000);
|
||||||
|
|
||||||
@@ -140,11 +171,13 @@ export const TradingViewChart: React.FC<TradingViewChartProps> = ({
|
|||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(refreshInterval);
|
||||||
|
clearInterval(heartbeatInterval);
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
chart.remove();
|
chart.remove();
|
||||||
chartRef.current = null;
|
chartRef.current = null;
|
||||||
seriesRef.current = null;
|
seriesRef.current = null;
|
||||||
|
lastBarTimeRef.current = 0;
|
||||||
};
|
};
|
||||||
}, [marketAddress, outcomeIndex, timeframe]);
|
}, [marketAddress, outcomeIndex, timeframe]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user