TradingView图表实时K线数据无法更新问题求助
问题概述
TradingView图表组件历史数据加载正常,但实时K线数据无法更新到图表中,控制台能打印New kline data received:日志,说明数据已接收,但图表无新数据点追加。
核心问题与修复方案
1. DataFeed实例不一致,回调无法触发
组件每次渲染时都会重新创建datafeed对象,但tvWidget仅在首次渲染(依赖theme)时初始化,绑定的是第一次创建的datafeed实例。后续组件中调用datafeed.onRealtimeCallback(klineData)时,实际调用的是新创建的datafeed实例方法,而非widget绑定的那个,自然无法触发图表更新。
修复:
用useRef保存datafeed实例,避免每次渲染重新创建:
const TradingViewChart = () => { const tickersData = useSelector((state) => state.marketTickers.value) const selectedMarket = useSelector((state) => state.selectedMarket.value) const markets = useSelector((state) => state.marketList.value) const klineData = useSelector((state) => state.kline) const datafeedRef = useRef(null); // 用ref保存实例 useEffect(() => { // 在widget初始化时创建datafeed并保存 const datafeed = dataFeedObject(TradingViewChart, markets); datafeedRef.current = datafeed; const widgetOptions = { allow_symbol_change: false, autosize: true, calendar: true, hide_legend: false, save_image: true, client_id: "tradingview.com", custom_css_url: "/chartStyles.css", debug: true, details: true, library_path: "/charting_library/", symbol: selectedMarket.id ?? "btcusdt", datafeed, } window.tvWidget = new TradingView.widget(widgetOptions) }, [theme, markets]) useEffect(() => { if (klineData && klineData.last.time !== klineData.pre_last.time) { // 使用ref保存的实例调用回调 datafeedRef.current?.onRealtimeCallback(klineData); } }, [klineData]) }
2. 实时数据时间戳格式不匹配
TradingView要求所有时间戳(包括实时数据)必须是秒级,若WebSocket返回的是毫秒时间戳,未转换会导致图表无法识别数据。
修复:
在subscribeBars的回调中转换时间戳:
subscribeBars: ( symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback ) => { dataFeed.onRealtimeCallback = (kline) => { if (kline?.last) { // 将毫秒时间戳转为秒级 const realtimeData = { ...kline.last, time: Math.floor(kline.last.time / 1000) }; onRealtimeCallback(realtimeData); } } // ... 其他逻辑 }
3. 实时数据字段结构不符合要求
TradingView的onRealtimeCallback要求传入的对象必须包含以下必填字段:
time: 秒级时间戳open: 开盘价high: 最高价low: 最低价close: 收盘价volume: 成交量
若kline.last的字段名与上述不一致(比如后端返回o/h/l/c/v),需要映射为标准字段。
修复:
字段映射示例:
const realtimeData = { time: Math.floor(kline.last.time / 1000), open: parseFloat(kline.last.o), high: parseFloat(kline.last.h), low: parseFloat(kline.last.l), close: parseFloat(kline.last.c), volume: parseFloat(kline.last.v) }; onRealtimeCallback(realtimeData);
4. WebSocket订阅硬编码,未跟随市场切换
WebSocket Saga中硬编码了btcusdt.kline-1m,切换选中市场后不会自动订阅新市场的K线,导致接收的仍是旧市场数据。
修复:
根据当前选中市场动态生成订阅key:
// 假设subscribedMarket为当前选中市场id,如"ethusdt" const key = `${subscribedMarket}.kline-1m`; ws.onmessage = (event) => { const tickers = JSON.parse(event.data); if (tickers[key]) { if (subscribedMarket !== currentSubscribedMarket) { currentSubscribedMarket = subscribedMarket; emitter(clearKline()); } emitter(addKline(tickers[key])); } }
5. getBars方法冗余代码导致数据转换错误
getBars方法里重复定义bars变量,覆盖了转换后的时间戳数据,虽不影响实时更新,但需修复避免历史数据异常:
// 原代码冗余部分 const transformedData = data.map((item) => ([ Math.floor(item[0] / 1000), // 毫秒转秒 parseFloat(item[1]), parseFloat(item[2]), parseFloat(item[3]), parseFloat(item[4]), parseFloat(item[5]) ])) const bars = transformedData.map(klineArrayToObject) const bars = data.map(klineArrayToObject) // 这行覆盖了上面的bars,导致时间戳未转换
**修复:**删除重复的const bars = data.map(klineArrayToObject)行。
内容的提问来源于stack exchange,提问作者Vladimir Mawla

