/* chart_tv_finalize.js — time sync / bindSync / view restore / tooltip */ function chartTvFinalize(ctx) { var symbol = ctx.symbol; var timeframe = ctx.timeframe; var symbolConfig = ctx.symbolConfig; var useSubSubPeriod = ctx.useSubSubPeriod; var useElementPeriod = ctx.useElementPeriod; var klinePeriodLabel = ctx.klinePeriodLabel; var candles = ctx.candles; var klineDataSource = ctx.klineDataSource; var container = ctx.container; var showMacd = ctx.showMacd; var showOriginalKline = ctx.showOriginalKline; var mainChartContainer = ctx.mainChartContainer; var volumeChartContainer = ctx.volumeChartContainer; var atrChartContainer = ctx.atrChartContainer; var macdChartContainer = ctx.macdChartContainer; var chanMacdChartContainer = ctx.chanMacdChartContainer; var mainChart = ctx.mainChart; var volumeChart = ctx.volumeChart; var atrChart = ctx.atrChart; var macdChart = ctx.macdChart; var chanMacdChart = ctx.chanMacdChart; var createChartOptions = ctx.createChartOptions; // 同步所有图表的时间轴配置 const hasPendingRestoreView = !!window._pendingRestoreView; const pendingView = window._pendingRestoreView; const syncTimeScaleSettings = () => { const baseOptions = { timeVisible: true, secondsVisible: false, borderColor: '#ddd', lockVisibleTimeRangeOnResize: true, // 关键:确保所有图表边缘行为完全一致 fixLeftEdge: false, fixRightEdge: false, // 确保时间刻度行为一致 ticksVisible: true, minimumHeight: 0, }; // 有待恢复视图时不要先写 barSpacing/rightOffset(会钉右缘导致图往右偏), // 交给后面 setVisibleRange 一次锁定位置+缩放。 if (!pendingView) { baseOptions.barSpacing = symbolConfig.type === 'a_stock' ? 6 : 10; baseOptions.rightOffset = 12; } console.log('🔧 同步时间轴设置:', baseOptions); // 应用相同的设置到所有图表 mainChart.timeScale().applyOptions(baseOptions); volumeChart.timeScale().applyOptions(baseOptions); atrChart.timeScale().applyOptions(baseOptions); if (showMacd && macdChart) { macdChart.timeScale().applyOptions(baseOptions); } }; // 首先同步时间轴设置 syncTimeScaleSettings(); // 仅在没有待恢复视图时,设置默认可见范围 const totalBars = candles ? candles.length : 0; const visibleBarsCount = 200; const allChartsNow = [mainChart, volumeChart, atrChart] .concat(showMacd && macdChart ? [macdChart] : []) .concat(showMacd && chanMacdChart ? [chanMacdChart] : []); const restoreOpts = function () { const firstT = candles && candles.length ? candles[0].time : null; const lastT = candles && candles.length ? candles[candles.length - 1].time : null; return { firstBarTime: firstT, lastBarTime: lastT, oldBarCount: window._preserveViewBarCount || 0, newBarCount: totalBars }; }; if (hasPendingRestoreView && pendingView) { restoreChartViewState(allChartsNow, pendingView, restoreOpts()); } else { // 显示最近 200 根K线而非全部挤压(避免K线过多时重叠) if (totalBars > visibleBarsCount) { const rangeFrom = totalBars - visibleBarsCount; const rangeTo = totalBars + 12; mainChart.timeScale().setVisibleLogicalRange({ from: rangeFrom, to: rangeTo }); } else { mainChart.timeScale().fitContent(); } } // 立即同步其他图表到主图表的范围 setTimeout(() => { if (pendingView) { restoreChartViewState(allChartsNow, pendingView, restoreOpts()); const logRange = mainChart.timeScale().getVisibleLogicalRange(); if (logRange) { volumeChart.timeScale().setVisibleLogicalRange(logRange); atrChart.timeScale().setVisibleLogicalRange(logRange); if (showMacd && macdChart) { macdChart.timeScale().setVisibleLogicalRange(logRange); } if (showMacd && chanMacdChart) { chanMacdChart.timeScale().setVisibleLogicalRange(logRange); } } return; } const logRange = mainChart.timeScale().getVisibleLogicalRange(); if (logRange) { console.log('🔧 同步可见范围:', logRange); volumeChart.timeScale().setVisibleLogicalRange(logRange); atrChart.timeScale().setVisibleLogicalRange(logRange); if (showMacd && macdChart) { macdChart.timeScale().setVisibleLogicalRange(logRange); } if (showMacd && chanMacdChart) { chanMacdChart.timeScale().setVisibleLogicalRange(logRange); } console.log('🔧 时间轴同步完成'); } }, 50); // 保存图表对象 tvWidget.mainChart = mainChart; tvWidget.volumeChart = volumeChart; tvWidget.atrChart = atrChart; tvWidget.macdChart = macdChart; tvWidget.chanMacdChart = chanMacdChart; tvWidget.state.isInitialized = true; // 注册窗口卸载时释放资源,避免GPU内存泄漏 window.onbeforeunload = function() { try { if (tvWidget && tvWidget.state && tvWidget.state.isInitialized) { if (tvWidget.mainChart && typeof tvWidget.mainChart.remove === 'function') tvWidget.mainChart.remove(); if (tvWidget.volumeChart && typeof tvWidget.volumeChart.remove === 'function') tvWidget.volumeChart.remove(); if (tvWidget.macdChart && typeof tvWidget.macdChart.remove === 'function') tvWidget.macdChart.remove(); if (tvWidget.chanMacdChart && typeof tvWidget.chanMacdChart.remove === 'function') tvWidget.chanMacdChart.remove(); if (tvWidget.atrChart && typeof tvWidget.atrChart.remove === 'function') tvWidget.atrChart.remove(); } } catch (e) {} }; // 初始化默认均线/布林带配置(仅在首次初始化时) if (!hasInitializedDefaultMAs && movingAverages.length === 0) { console.log('初始化默认均线与布林带指标'); if (typeof maIdCounter !== 'number' || !Number.isFinite(maIdCounter)) { maIdCounter = 0; } if (typeof bbIdCounter !== 'number' || !Number.isFinite(bbIdCounter)) { bbIdCounter = 0; } const defaultMAs = [ { type: 'EMA', length: 26, color: '#FF8C00', name: 'EMA26', visible: false }, // 橙色 { type: 'EMA', length: 52, color: '#000000', name: 'EMA52', visible: true }, // 黑色 · 默认开 { type: 'SMA', length: 30, color: '#1E90FF', name: 'MA30', visible: true }, // 蓝色 · 默认开 { type: 'SMA', length: 250, color: '#800080', name: 'MA250', visible: true } // 紫色 · 默认开 ]; defaultMAs.forEach(ma => { const config = { id: ++maIdCounter, type: ma.type, length: ma.length, source: 'close', smoothType: 'none', smoothLength: 3, lineWidth: 1, // 1px线宽 lineStyle: 0, // 实线 color: ma.color, visible: ma.visible }; movingAverages.push(config); console.log(`添加默认${ma.name}:`, ma.color); }); if (bollingerBands.length === 0) { const defaultBB = { id: ++bbIdCounter, type: 'Bollinger Bands', length: 20, upperMultiplier: 2, lowerMultiplier: 2, source: 'close', lineWidth: 1, lineStyle: 0, upperColor: '#ff6b6b', middleColor: '#ffffff', lowerColor: '#ff6b6b', visible: false }; bollingerBands.push(defaultBB); console.log('添加默认布林带: BB(20, 2, 2)'); } console.log('默认指标配置完成,当前均线数量', movingAverages.length, '布林带数量', bollingerBands.length); hasInitializedDefaultMAs = true; } // 添加均线到图表 addMovingAveragesToChart(candles); // 添加布林带到图表 addBollingerBandsToChart(candles); // 更新技术指标面板显示 updateIndicatorPanel(); // 绑定同步事件 if (hasPendingRestoreView && pendingView) { window._preserveViewDuringUpdate = true; } bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, mainChart, volumeChart, atrChart, macdChart, chanMacdChart, showMacd); // 最终确保所有图表时间轴对齐(同时恢复刷新前保存的缩放/位置) setTimeout(() => { const allCharts = [mainChart, volumeChart, atrChart]; if (showMacd && macdChart) allCharts.push(macdChart); if (showMacd && chanMacdChart) allCharts.push(chanMacdChart); // 检查是否有待恢复的视图(缩放 + 位置) const pending = window._pendingRestoreView || pendingView; window._pendingRestoreView = null; if (pending) { const firstT = candles && candles.length ? candles[0].time : null; const lastT = candles && candles.length ? candles[candles.length - 1].time : null; console.log('📌 恢复图表视图:', JSON.stringify(pending)); restoreChartViewState(allCharts, pending, { firstBarTime: firstT, lastBarTime: lastT, oldBarCount: window._preserveViewBarCount || 0, newBarCount: totalBars }); window._preserveViewBarCount = 0; } else { // 无保存视图,正常同步主图到子图 const visibleRange = mainChart.timeScale().getVisibleRange(); if (visibleRange) { console.log('🔧 最终同步可见范围:', visibleRange); [volumeChart, atrChart].concat( showMacd && macdChart ? [macdChart] : [], showMacd && chanMacdChart ? [chanMacdChart] : [] ).forEach(c => { try { c.timeScale().setVisibleRange(visibleRange); } catch(e) {} }); } } window._preserveViewDuringUpdate = false; console.log('🔧 最终时间轴对齐完成'); }, 150); // 只有在时间输入框都为空时才设置图表默认时间范围 if (!$('#start_time').val() && !$('#end_time').val()) { setDefaultTimeRange(); } // 添加买卖点提示 // 初始化 tooltip 与 U 显示状态 window.showUOnMain = $('#toggleUOnMain').is(':checked'); window.showUOnElement = $('#toggleUOnElement').is(':checked'); setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd); // 更新EMA52显示 if (currentData) { updateEMA52Display(currentData); } }