fix: 修复主站自动刷新内存泄漏,并完善 chan_tv 图表体验

主站重建前完整 dispose、去掉重复 sync 监听,自动刷新默认增量更新;顺带消除首屏重复 analyze、复用 ChanMACD,以及全版 TV 指标/未完成中枢/布局本地缓存。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-06 16:09:48 +08:00
co-authored by Cursor
parent 6b0f3b5837
commit 9f1e7361b6
14 changed files with 457 additions and 669 deletions
+37 -24
View File
@@ -1,20 +1,27 @@
/**
* TradingView Datafeed — 对接 Data Provider 微服务
*
* 数据源: http://103.179.242.166
* - GET /timeframes → 可用周期
* - GET /api/candles → 历史 OHLCV
* - WS /ws → 实时 K 线推送
* REST: https://provider.jackyu66.com
* WS: wss://jackyu66.com/ws (可通过 window.DATA_SERVICE_* 或 URL 参数覆盖)
*
* 实现 IDatafeedChartApi 核心接口:
* onReady, resolveSymbol, getBars, subscribeBars, unsubscribeBars
* 实时:subscribeBars → 收到 kline 后调用 onTick(由 Charting Library 增量更新,不重置缩放)
*/
var ChanTVDatafeed = (function () {
'use strict'
// 默认 data_provider 地址,可通过 URL param 覆盖
var DATA_HOST = 'http://103.179.242.166'
function getParam(name) {
try {
var m = (new RegExp('[?&]' + name + '=([^&]*)')).exec(location.search)
return m ? decodeURIComponent(m[1]) : ''
} catch (e) {
return ''
}
}
// REST 与 WS 可分离(nginx 反代)
var DATA_HOST = (window.DATA_SERVICE_URL || getParam('data_host') || 'https://provider.jackyu66.com').replace(/\/$/, '')
var WS_URL = (window.DATA_SERVICE_WS_URL || getParam('ws_url') || 'wss://jackyu66.com/ws').replace(/\/$/, '')
// ---- resolution <-> timeframe 转换 ----
var RES_TO_TF = {
@@ -36,13 +43,12 @@ var ChanTVDatafeed = (function () {
var ws = null
var wsReconnectTimer = null
var wsSubs = {} // listenerGuid -> { symbol, tf, onTick, lastTickTime }
var wsUrl = DATA_HOST.replace(/^http/, 'ws') + '/ws'
function wsConnect() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return
try {
ws = new WebSocket(wsUrl)
ws = new WebSocket(WS_URL)
} catch (e) {
console.warn('[TV Datafeed] WS 连接失败', e)
scheduleReconnect()
@@ -50,7 +56,7 @@ var ChanTVDatafeed = (function () {
}
ws.onopen = function () {
console.log('[TV Datafeed] WS 已连接')
console.log('[TV Datafeed] WS 已连接', WS_URL)
// 重新订阅
Object.keys(wsSubs).forEach(function (guid) {
var sub = wsSubs[guid]
@@ -62,28 +68,35 @@ var ChanTVDatafeed = (function () {
try {
var msg = JSON.parse(evt.data)
var bars = msg.data || msg.bars // data_provider 用 'data' 字段
// 历史快照交给 getBars;实时只走 kline → onTick,避免冲掉缩放
if (msg.type === 'snapshot' || msg.type === 'subscribed') return
if ((msg.type === 'kline' || msg.type === 'candles') && bars && bars.length > 0) {
// 只推送最新一根 bar,避免历史快照造成时间顺序冲突
// 按时间升序排列取最后一个
var sorted = bars.slice().sort(function (a, b) { return (a.timestamp || 0) - (b.timestamp || 0) })
var latest = sorted[sorted.length - 1]
// 广播给所有匹配的 subscriber
if (!latest || latest.timestamp == null) return
Object.keys(wsSubs).forEach(function (guid) {
var sub = wsSubs[guid]
if (sub.symbol === msg.symbol && sub.tf === msg.timeframe) {
// 跳过已处理过的时间戳
if (sub.lastTickTime && latest.timestamp <= sub.lastTickTime) return
// 允许同 timestamp 更新未收盘棒(用 < 而不是 <=)
if (sub.lastTickTime != null && latest.timestamp < sub.lastTickTime) return
var tick = {
time: latest.timestamp,
open: latest.open,
high: latest.high,
low: latest.low,
close: latest.close,
volume: latest.volume,
}
try {
sub.onTick({
time: latest.timestamp,
open: latest.open,
high: latest.high,
low: latest.low,
close: latest.close,
volume: latest.volume,
})
sub.onTick(tick)
sub.lastTickTime = latest.timestamp
} catch (e) { /* ignore */ }
// 通知页面:更新缠论缓存(K 线由 TV onTick 处理,不重置缩放)
try {
if (window.ChanTvRealtime && typeof window.ChanTvRealtime.onBar === 'function') {
window.ChanTvRealtime.onBar(msg.symbol, msg.timeframe, latest)
}
} catch (e2) { /* ignore */ }
}
})
}