/* chart_sync.js — split from chart.js */ function updateTradingViewData() { try { console.log('增量更新图表数据'); // 检查 currentData 是否存在 if (!currentData) { console.error('currentData为空,无法更新图表'); return; } // 优先用请求前冻结的视窗;否则现场拍(自动刷新短间隔 delta≈0,两种都稳) const frozen = window._preserveViewOnRefresh; const oldBarCount = window._preserveViewBarCount || 0; let savedScrollPosition = null; if (tvWidget.mainChart) { const ts = tvWidget.mainChart.timeScale(); if (frozen) { tvWidget.state.visibleRange = frozen.visibleRange; tvWidget.state.logicalRange = frozen.logicalRange; savedScrollPosition = (typeof frozen.scrollPosition === 'number') ? frozen.scrollPosition : null; } else { tvWidget.state.visibleRange = ts.getVisibleRange(); tvWidget.state.logicalRange = ts.getVisibleLogicalRange(); try { savedScrollPosition = ts.scrollPosition ? ts.scrollPosition() : null; } catch (e) {} } } window._preserveViewOnRefresh = null; window._preserveViewBarCount = 0; // 检查是否显示原始K线 const showOriginalKline = $('#showOriginalKline').is(':checked'); // 检查是否使用次次周期 / 小周期数据 const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') && currentData.sub_sub_timeframe && currentData.sub_sub_kline_data && Array.isArray(currentData.sub_sub_kline_data); const useElementPeriod = !useSubSubPeriod && $('#elementPeriodKline').is(':checked') && currentData.element_timeframe && currentData.element_kline_data && Array.isArray(currentData.element_kline_data); // 转换K线数据 let candles = []; if (useSubSubPeriod) { console.log('使用次次周期K线数据'); candles = currentData.sub_sub_kline_data.map((kline) => { const date = new Date(kline.date); const timestamp = date.getTime() / 1000; return { time: timestamp, open: parseFloat(kline.open), high: parseFloat(kline.high), low: parseFloat(kline.low), close: parseFloat(kline.close), }; }); } else if (useElementPeriod) { console.log('使用小周期K线数据'); candles = currentData.element_kline_data.map((kline) => { const date = new Date(kline.date); const timestamp = date.getTime() / 1000; return { time: timestamp, open: parseFloat(kline.open), high: parseFloat(kline.high), low: parseFloat(kline.low), close: parseFloat(kline.close), }; }); } else if (currentData.kline_data && Array.isArray(currentData.kline_data)) { console.log('使用主周期K线数据'); candles = currentData.kline_data.map((kline) => { const date = new Date(kline.date); const timestamp = date.getTime() / 1000; return { time: timestamp, open: parseFloat(kline.open), high: parseFloat(kline.high), low: parseFloat(kline.low), close: parseFloat(kline.close), }; }); } const newBarCount = candles.length; const barDelta = (oldBarCount > 0 && newBarCount > 0) ? (newBarCount - oldBarCount) : 0; // 更新主系列数据(根据klineType) const klineType = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line')); if (klineType === 'candlestick' && tvWidget.series.candleSeries) { tvWidget.series.candleSeries.setData(candles); } else if (klineType === 'renko' && tvWidget.series.renkoSeries) { const bricks = buildRenkoFromCandles(candles); tvWidget.series.renkoSeries.setData(bricks); } else if (klineType === 'heikin' && tvWidget.series.heikinSeries) { const hk = buildHeikinFromCandles(candles); tvWidget.series.heikinSeries.setData(hk); } else if (klineType === 'bar' && tvWidget.series.barSeries) { tvWidget.series.barSeries.setData(candles); } else if (klineType === 'line' && tvWidget.series.lineSeries) { const lineData = candles.map(c => ({ time: c.time, value: c.close })); tvWidget.series.lineSeries.setData(lineData); } else if (klineType === 'area' && tvWidget.series.areaSeries) { const areaData = candles.map(c => ({ time: c.time, value: c.close })); tvWidget.series.areaSeries.setData(areaData); } else if (klineType === 'baseline' && tvWidget.series.baselineSeries) { const baseData = candles.map(c => ({ time: c.time, value: c.close })); tvWidget.series.baselineSeries.setData(baseData); } else if (klineType === 'klc' && tvWidget.series.klcSeries) { const klcCandles = buildKLCFromAnalysis(currentData); tvWidget.series.klcSeries.setData(klcCandles); } // 更新均线数据 addMovingAveragesToChart(candles); // 更新布林带数据 addBollingerBandsToChart(candles); // 更新成交量数据 let volumes = []; if (useSubSubPeriod && currentData.sub_sub_kline_data && Array.isArray(currentData.sub_sub_kline_data)) { volumes = currentData.sub_sub_kline_data.map(kline => { const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); return { time: timestamp, value: parseFloat(kline.volume), color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)', }; }); } else if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) { volumes = currentData.element_kline_data.map(kline => { const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); return { time: timestamp, value: parseFloat(kline.volume), color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)', }; }); } else if (currentData.kline_data && Array.isArray(currentData.kline_data)) { volumes = currentData.kline_data.map(kline => { const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); return { time: timestamp, value: parseFloat(kline.volume), color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)', }; }); } if (tvWidget.series.volumeSeries) { tvWidget.series.volumeSeries.setData(volumes); } // 更新ATR数据 if (tvWidget.series.atrLineSeries) { const atrData = []; const atrDataSource = useSubSubPeriod ? (currentData.sub_sub_atr || currentData.atr) : (useElementPeriod ? (currentData.element_atr || currentData.atr) : currentData.atr); if (atrDataSource && Array.isArray(atrDataSource)) { const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data); // 修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据 for (let i = 0; i < klineDataSource.length; i++) { const kline = klineDataSource[i]; const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); // 为每个时间点都添加数据以保持时间轴对齐,但ATR为0时不显示 if (atrDataSource[i] !== undefined) { if (atrDataSource[i] > 0) { // ATR有效值,正常显示 atrData.push({ time: timestamp, value: atrDataSource[i] }); } else { // ATR为0,添加时间点但不显示线条(使用undefined作为value) atrData.push({ time: timestamp, value: undefined }); } } } console.log('🔄 增量更新ATR数据点数:', atrData.length); } tvWidget.series.atrLineSeries.setData(atrData); } // 更新MACD数据 if (tvWidget.series.macdLineSeries && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) { // 提取MACD数据 const macdData = []; const signalData = []; const histogramData = []; for (let i = 0; i < currentData.kline_data.length; i++) { const kline = currentData.kline_data[i]; const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); if (currentData.macd && currentData.macd.macd && currentData.macd.macd[i] !== undefined) { macdData.push({ time: timestamp, value: currentData.macd.macd[i] }); signalData.push({ time: timestamp, value: currentData.macd.signal[i] }); // 设置直方图颜色 const histValue = currentData.macd.histogram[i]; histogramData.push({ time: timestamp, value: histValue, color: histValue >= 0 ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)' }); } } tvWidget.series.macdLineSeries.setData(macdData); tvWidget.series.signalLineSeries.setData(signalData); tvWidget.series.histogramSeries.setData(histogramData); } // 更新 ChanMACD 数据与自定义标注 if (tvWidget.series.chanMacdLineSeries && ((useSubSubPeriod && currentData.sub_sub_macd) || (useElementPeriod && currentData.element_macd) || currentData.macd) && (useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data))) { const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data); const macdDataSource = useSubSubPeriod ? (currentData.sub_sub_macd || currentData.macd) : (useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd); if (macdDataSource && macdDataSource.macd && macdDataSource.signal && macdDataSource.histogram) { const chanMacdData = []; const chanSignalData = []; const chanHistData = []; for (let i = 0; i < klineDataSource.length; i++) { const kline = klineDataSource[i]; if (kline && kline.date && i < macdDataSource.macd.length && macdDataSource.macd[i] !== null && macdDataSource.macd[i] !== undefined) { const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); chanMacdData.push({ time: timestamp, value: macdDataSource.macd[i] }); chanSignalData.push({ time: timestamp, value: macdDataSource.signal[i] }); chanHistData.push({ time: timestamp, value: macdDataSource.histogram[i], color: macdDataSource.histogram[i] >= 0 ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)' }); } } if (chanMacdData.length > 0) { tvWidget.series.chanMacdLineSeries.setData(chanMacdData); tvWidget.series.chanMacdSignalSeries.setData(chanSignalData); tvWidget.series.chanMacdHistSeries.setData(chanHistData); } } // 重新应用自定义标注(段/UnitTF/HistSet/状态点) try { if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers(); const cm = useSubSubPeriod ? (currentData.sub_sub_chan_macd || currentData.chan_macd) : (useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd); const allowU = useSubSubPeriod ? !!window.showUOnSubSub : (useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain); if (cm && allowU) { addAllChanMacdMarkers( cm.seg_list || [], cm.unittf_list || [], cm.histset_list || [], { high_position_list: cm.high_position_list || [], high_empty_list: cm.high_empty_list || [], low_position_list: cm.low_position_list || [], low_empty_list: cm.low_empty_list || [], return_zero_list: cm.return_zero_list || [], cross0_up_list: cm.cross0_up_list || [], cross0_down_list: cm.cross0_down_list || [] } ); } } catch (e) { console.warn('更新ChanMACD标注失败:', e); } } // 不再调用 redrawFractalElements():它会全量 initTradingView, // 与增量更新叠加会导致图表反复重建、内存暴涨。 // 笔/段/中枢仍随「手动刷新 / 全量 refreshChart」重建;自动刷新走增量路径。 // 更新EMA52显示 updateEMA52Display(currentData); // 与自动刷新一致:增量更新绝不碰 barSpacing(缩放本来就留在图表实例上)。 // 一写 barSpacing,LWC 会按右边缘重锚 → 放大往右、缩小往左。 // 这里只在 setData 之后把位置扳回刷新前的 logical / time 窗口。 if (tvWidget.mainChart) { const charts = [ tvWidget.mainChart, tvWidget.volumeChart, tvWidget.atrChart, tvWidget.macdChart, tvWidget.chanMacdChart ].filter(Boolean); const vr = tvWidget.state.visibleRange; const lr = tvWidget.state.logicalRange; const savedScroll = savedScrollPosition; const applyPosition = function (tag) { let ok = false; if (lr && lr.from !== undefined && lr.to !== undefined) { charts.forEach(c => { try { c.timeScale().setVisibleLogicalRange({ from: lr.from, to: lr.to }); ok = true; } catch (e) {} }); if (ok) console.log('🔄 恢复位置 logical' + (tag || '') + ':', lr); } if (!ok && vr && vr.from !== undefined && vr.to !== undefined) { charts.forEach(c => { try { c.timeScale().setVisibleRange(vr); ok = true; } catch (e) {} }); if (ok) console.log('🔄 恢复位置 time' + (tag || '') + ':', vr); } if (!ok && typeof savedScroll === 'number') { const pos = savedScroll + (barDelta || 0); charts.forEach(c => { try { c.timeScale().scrollToPosition(pos, false); } catch (e) {} }); console.log('🔄 恢复位置 scroll' + (tag || '') + ':', pos); } }; applyPosition(''); setTimeout(function () { applyPosition('@0'); }, 0); setTimeout(function () { applyPosition('@50'); }, 50); } console.log('增量更新图表完成'); } catch (e) { console.error('增量更新图表错误,回退到完全重绘:', e); // 出错时回退到完全重绘 initTradingView($('#symbol').val(), $('#timeframe').val()); } } function bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, mainChart, volumeChart, atrChart, macdChart, chanMacdChart, showMacd) { // 清理上一轮绑定的事件监听器,防止累积 if (window._bindSyncCleanups) { window._bindSyncCleanups.forEach(fn => { try { fn(); } catch(e) {} }); } window._bindSyncCleanups = []; let syncInProgress = false; // 用于跟踪所有图表的拖动状态 - 在函数内部定义以确保作用域正确 let localDragStates = { main: false, volume: false, atr: false, macd: false, chanmacd: false }; // 同步图表的时间范围 function syncCharts(sourceChart, sourceContainer) { if (syncInProgress) return; syncInProgress = true; try { if (sourceChart && sourceChart.timeScale) { const logicalRange = sourceChart.timeScale().getVisibleLogicalRange(); if (logicalRange && logicalRange.from !== undefined && logicalRange.to !== undefined) { if (sourceChart !== mainChart && mainChart && mainChart.timeScale) { try { mainChart.timeScale().setVisibleLogicalRange(logicalRange); } catch (e) {} } if (sourceChart !== volumeChart && volumeChart && volumeChart.timeScale) { try { volumeChart.timeScale().setVisibleLogicalRange(logicalRange); } catch (e) {} } if (sourceChart !== atrChart && atrChart && atrChart.timeScale) { try { atrChart.timeScale().setVisibleLogicalRange(logicalRange); } catch (e) {} } if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) { try { macdChart.timeScale().setVisibleLogicalRange(logicalRange); } catch (e) {} } if (showMacd && chanMacdChart && sourceChart !== chanMacdChart && chanMacdChart.timeScale) { try { chanMacdChart.timeScale().setVisibleLogicalRange(logicalRange); } catch (e) {} } if (tvWidget && tvWidget.state) { tvWidget.state.logicalRange = logicalRange; try { tvWidget.state.visibleRange = sourceChart.timeScale().getVisibleRange(); } catch (e) {} } } } } catch (e) { console.error('同步图表出错:', e); } setTimeout(() => { syncInProgress = false; }, 1); } // 为每个图表添加事件监听 const addChartSyncEvents = (chartContainer, chart) => { const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : chart === atrChart ? 'atr' : chart === macdChart ? 'macd' : chart === chanMacdChart ? 'chanmacd' : 'unknown'; const timeRangeHandler = () => { if (!syncInProgress) { syncCharts(chart, chartContainer); } }; chart.timeScale().subscribeVisibleTimeRangeChange(timeRangeHandler); window._bindSyncCleanups.push(() => { try { chart.timeScale().unsubscribeVisibleTimeRangeChange(timeRangeHandler); } catch(e) {} }); let isScrolling = false; const mousedownHandler = () => { localDragStates[chartType] = true; }; const mouseupHandler = () => { localDragStates[chartType] = false; }; const mouseleaveHandler = () => { localDragStates[chartType] = false; }; const wheelHandler = () => { if (!isScrolling) { isScrolling = true; setTimeout(() => { if (!syncInProgress) { syncCharts(chart, chartContainer); } isScrolling = false; }, 50); } }; chartContainer.addEventListener('mousedown', mousedownHandler); chartContainer.addEventListener('mouseup', mouseupHandler); chartContainer.addEventListener('mouseleave', mouseleaveHandler); chartContainer.addEventListener('wheel', wheelHandler); window._bindSyncCleanups.push(() => { chartContainer.removeEventListener('mousedown', mousedownHandler); chartContainer.removeEventListener('mouseup', mouseupHandler); chartContainer.removeEventListener('mouseleave', mouseleaveHandler); chartContainer.removeEventListener('wheel', wheelHandler); }); }; // 添加事件监听 if (mainChartContainer && mainChart) { addChartSyncEvents(mainChartContainer, mainChart); } if (volumeChartContainer && volumeChart) { addChartSyncEvents(volumeChartContainer, volumeChart); } if (atrChartContainer && atrChart) { addChartSyncEvents(atrChartContainer, atrChart); } if (showMacd && macdChartContainer && macdChart) { addChartSyncEvents(macdChartContainer, macdChart); } if (showMacd && chanMacdChartContainer && chanMacdChart) { addChartSyncEvents(chanMacdChartContainer, chanMacdChart); } // 窗口大小变化时重绘图表 — 使用可清理的方式注册 const resizeHandler = () => { if (mainChart && mainChartContainer) { mainChart.applyOptions({ width: mainChartContainer.clientWidth, height: mainChartContainer.clientHeight }); } if (volumeChart && volumeChartContainer) { volumeChart.applyOptions({ width: volumeChartContainer.clientWidth, height: volumeChartContainer.clientHeight }); } if (atrChart && atrChartContainer) { atrChart.applyOptions({ width: atrChartContainer.clientWidth, height: atrChartContainer.clientHeight }); } if (showMacd && macdChart && macdChartContainer) { macdChart.applyOptions({ width: macdChartContainer.clientWidth, height: macdChartContainer.clientHeight }); } if (showMacd && chanMacdChart && chanMacdChartContainer) { chanMacdChart.applyOptions({ width: chanMacdChartContainer.clientWidth, height: chanMacdChartContainer.clientHeight }); } setTimeout(() => { if (mainChart) syncCharts(mainChart, mainChartContainer); }, 200); }; window.addEventListener('resize', resizeHandler); window._bindSyncCleanups.push(() => { window.removeEventListener('resize', resizeHandler); }); } function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd) { // 清理上一轮 tooltip 的事件订阅 if (window._tooltipCleanups) { window._tooltipCleanups.forEach(fn => { try { fn(); } catch(e) {} }); } window._tooltipCleanups = []; window.debugMode = true; // 初始化 U 显示状态(主/次周期分开控制) const isShowUMain = $('#toggleUOnMain').is(':checked'); const isShowUElement = $('#toggleUOnElement').is(':checked'); window.showUOnMain = isShowUMain; window.showUOnElement = isShowUElement; if (!isShowUMain && !isShowUElement) { // 隐藏时清空子图上的 U 标记 if (tvWidget.series && tvWidget.series.chanMacdLineSeries) { try { tvWidget.series.chanMacdLineSeries.setMarkers([]); } catch (e) {} } if (tvWidget.series && tvWidget.series.chanMacdSignalSeries) { try { tvWidget.series.chanMacdSignalSeries.setMarkers([]); } catch (e) {} } } // 添加买卖点悬浮提示元素 const tooltipElement = document.createElement('div'); tooltipElement.className = 'point-tooltip'; // document.body.appendChild(tooltipElement); // 添加自定义十字线信息显示 const crosshairTooltip = document.createElement('div'); crosshairTooltip.className = 'crosshair-tooltip'; crosshairTooltip.style.position = 'absolute'; crosshairTooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; crosshairTooltip.style.color = 'white'; crosshairTooltip.style.padding = '5px 10px'; crosshairTooltip.style.borderRadius = '4px'; crosshairTooltip.style.fontSize = '12px'; crosshairTooltip.style.zIndex = '1000'; crosshairTooltip.style.pointerEvents = 'none'; crosshairTooltip.style.display = 'none'; // document.body.appendChild(crosshairTooltip); // 添加鼠标悬停事件显示提示 if (mainChart) { const crosshairHandler = (param) => { // 十字线同步到其他图表 - 通过DOM元素绘制垂直线实现虚线延长效果 if (param.time && param.point && volumeChart) { try { // 清除之前的十字线标记 const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); existingVolumeLines.forEach(line => line.remove()); const existingAtrLines = document.querySelectorAll('.atr-crosshair-line'); existingAtrLines.forEach(line => line.remove()); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); existingMacdLines.forEach(line => line.remove()); const existingChanMacdLines = document.querySelectorAll('.chanmacd-crosshair-line'); existingChanMacdLines.forEach(line => line.remove()); // 获取时间对应的坐标位置 const mainTimeCoordinate = mainChart.timeScale().timeToCoordinate(param.time); if (mainTimeCoordinate !== null) { // 获取主图容器的位置 const mainChartRect = mainChartContainer.getBoundingClientRect(); // 在交易量图上绘制垂直线 const volumeTimeCoordinate = volumeChart.timeScale().timeToCoordinate(param.time); if (volumeTimeCoordinate !== null) { const volumeChartRect = volumeChartContainer.getBoundingClientRect(); const volumeLine = document.createElement('div'); volumeLine.className = 'volume-crosshair-line'; volumeLine.style.position = 'fixed'; // 改为fixed定位 volumeLine.style.left = (volumeChartRect.left + volumeTimeCoordinate) + 'px'; volumeLine.style.top = volumeChartRect.top + 'px'; volumeLine.style.width = '1px'; volumeLine.style.height = volumeChartRect.height + 'px'; volumeLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)'; volumeLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)'; volumeLine.style.pointerEvents = 'none'; volumeLine.style.zIndex = '1000'; document.body.appendChild(volumeLine); } // 在ATR图上绘制垂直线 if (atrChart && atrChartContainer) { const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time); if (atrTimeCoordinate !== null) { const atrChartRect = atrChartContainer.getBoundingClientRect(); const atrLine = document.createElement('div'); atrLine.className = 'atr-crosshair-line'; atrLine.style.position = 'fixed'; // 改为fixed定位 atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px'; atrLine.style.top = atrChartRect.top + 'px'; atrLine.style.width = '1px'; atrLine.style.height = atrChartRect.height + 'px'; atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)'; atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)'; atrLine.style.pointerEvents = 'none'; atrLine.style.zIndex = '1000'; document.body.appendChild(atrLine); } } // 如果有MACD图,也在MACD图上绘制垂直线 if (showMacd && macdChart && macdChartContainer) { const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time); if (macdTimeCoordinate !== null) { const macdChartRect = macdChartContainer.getBoundingClientRect(); const macdLine = document.createElement('div'); macdLine.className = 'macd-crosshair-line'; macdLine.style.position = 'fixed'; // 改为fixed定位 macdLine.style.left = (macdChartRect.left + macdTimeCoordinate) + 'px'; macdLine.style.top = macdChartRect.top + 'px'; macdLine.style.width = '1px'; macdLine.style.height = macdChartRect.height + 'px'; macdLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)'; macdLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)'; macdLine.style.pointerEvents = 'none'; macdLine.style.zIndex = '1000'; document.body.appendChild(macdLine); } } // 如果有ChanMACD图,也在ChanMACD图上绘制垂直线 if (showMacd && chanMacdChart && chanMacdChartContainer) { const chanMacdTimeCoordinate = chanMacdChart.timeScale().timeToCoordinate(param.time); if (chanMacdTimeCoordinate !== null) { const chanMacdChartRect = chanMacdChartContainer.getBoundingClientRect(); const chanMacdLine = document.createElement('div'); chanMacdLine.className = 'chanmacd-crosshair-line'; chanMacdLine.style.position = 'fixed'; chanMacdLine.style.left = (chanMacdChartRect.left + chanMacdTimeCoordinate) + 'px'; chanMacdLine.style.top = chanMacdChartRect.top + 'px'; chanMacdLine.style.width = '1px'; chanMacdLine.style.height = chanMacdChartRect.height + 'px'; chanMacdLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)'; chanMacdLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)'; chanMacdLine.style.pointerEvents = 'none'; chanMacdLine.style.zIndex = '1000'; document.body.appendChild(chanMacdLine); } } } } catch (e) { console.debug('十字线同步出错:', e); } } else { // 当十字线离开时,清除垂直线 try { const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); existingVolumeLines.forEach(line => line.remove()); const existingAtrLines = document.querySelectorAll('.atr-crosshair-line'); existingAtrLines.forEach(line => line.remove()); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); existingMacdLines.forEach(line => line.remove()); const existingChanMacdLines = document.querySelectorAll('.chanmacd-crosshair-line'); existingChanMacdLines.forEach(line => line.remove()); } catch (e) { console.debug('清除十字线时出错:', e); } } if (param.time && param.point) { const timeStr = param.time; const markers = [...buyMarkers, ...sellMarkers].filter(m => m.time === timeStr); // 同时检查分型标记 const fxMarkers = (window.fxMarkers || []).filter(m => m.time === timeStr); const allMarkers = [...markers, ...fxMarkers]; // 显示时区调试信息 if (window.debugMode) { const timezone = $('#timezone').val(); const formattedTime = formatTimeWithTimezone(timeStr * 1000, timezone); // 获取当前价格 - 通过param.seriesPrices获取 let priceInfo = ''; if (param.seriesPrices && param.seriesPrices.size > 0) { // 依次从当前可能的主系列中获取价格 if (tvWidget.series.candleSeries && param.seriesPrices.get(tvWidget.series.candleSeries)) { const price = param.seriesPrices.get(tvWidget.series.candleSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.renkoSeries && param.seriesPrices.get(tvWidget.series.renkoSeries)) { const price = param.seriesPrices.get(tvWidget.series.renkoSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.heikinSeries && param.seriesPrices.get(tvWidget.series.heikinSeries)) { const price = param.seriesPrices.get(tvWidget.series.heikinSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.barSeries && param.seriesPrices.get(tvWidget.series.barSeries)) { const price = param.seriesPrices.get(tvWidget.series.barSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) { const price = param.seriesPrices.get(tvWidget.series.lineSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) { const price = param.seriesPrices.get(tvWidget.series.areaSeries); priceInfo = `价格: ${price.toFixed(2)}`; } else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) { const price = param.seriesPrices.get(tvWidget.series.baselineSeries); priceInfo = `价格: ${price.toFixed(2)}`; } // 如果没有蜡烛图系列价格,尝试从区域图系列获取 else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) { const price = param.seriesPrices.get(tvWidget.series.areaSeries); priceInfo = `价格: ${price.toFixed(2)}`; } // 如果没有蜡烛图系列价格,尝试从基线图系列获取 else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) { const price = param.seriesPrices.get(tvWidget.series.baselineSeries); priceInfo = `价格: ${price.toFixed(2)}`; } } // 显示自定义时区工具提示,包含价格信息 crosshairTooltip.innerHTML = `