/* chart_tv_chan.js — BI / SEG / ZS */ /** 按界面结束时间裁掉被 recent 合并进来的更晚 K 线 */ function klinesUpToSelectedEnd(klineData) { if (!Array.isArray(klineData) || !klineData.length) return []; const endVal = $('#end_time').val(); if (!endVal) return klineData; const endMs = new Date(endVal).getTime(); if (isNaN(endMs)) return klineData; const out = []; for (let i = 0; i < klineData.length; i++) { const k = klineData[i]; let t; if (k.timestamp != null && k.timestamp !== '') { t = Number(k.timestamp); } else { t = new Date(k.date).getTime(); } if (!isNaN(t) && t <= endMs) out.push(k); } return out; } function alignTimeToChartCandles(targetSec, chartCandles) { if (targetSec == null || isNaN(targetSec)) return targetSec; if (!Array.isArray(chartCandles) || !chartCandles.length) return targetSec; const t = Math.floor(Number(targetSec)); let best = Math.floor(Number(chartCandles[0].time)); let bestDiff = Math.abs(best - t); for (let i = 1; i < chartCandles.length; i++) { const ct = Math.floor(Number(chartCandles[i].time)); if (!isFinite(ct)) continue; const d = Math.abs(ct - t); if (d < bestDiff) { best = ct; bestDiff = d; } } return best; } function getLastBarFromChartCandles(chartCandles) { if (!Array.isArray(chartCandles) || !chartCandles.length) return null; const c = chartCandles[chartCandles.length - 1]; if (!c || c.time == null) return null; return { time: Math.floor(Number(c.time)), high: parseFloat(c.high), low: parseFloat(c.low), close: parseFloat(c.close) }; } /** * 未完成笔终点:优先分析 end_price;时间接到该周期末根并 snap 到图表 candles。 */ function resolveUncompletedBiEndpoint(bi, klineData, chartCandles) { const clipped = klinesUpToSelectedEnd(klineData); const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); const startPrice = parseFloat(bi.start_price); let endPrice = parseFloat(bi.end_price); let endTime = bi.end_time ? Math.floor(new Date(bi.end_time).getTime() / 1000) : NaN; const lastFromChart = getLastBarFromChartCandles(chartCandles); const lastK = clipped.length ? clipped[clipped.length - 1] : null; if (lastFromChart) { endTime = lastFromChart.time; if (isNaN(endPrice)) { endPrice = bi.direction === 1 ? lastFromChart.high : lastFromChart.low; } } else if (lastK) { const lastTime = Math.floor(new Date(lastK.date).getTime() / 1000); if (!isNaN(lastTime)) endTime = lastTime; if (isNaN(endPrice)) { endPrice = bi.direction === 1 ? parseFloat(lastK.high) : parseFloat(lastK.low); } } endTime = alignTimeToChartCandles(endTime, chartCandles); return { startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice }; } function resolveUncompletedSegEndpoint(seg, klineData, chartCandles) { const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); const startPrice = parseFloat(seg.start_price); let endTime; let endPrice; if (seg.end_time != null && seg.end_price != null && seg.end_price !== '' && !isNaN(parseFloat(seg.end_price))) { endTime = Math.floor(new Date(seg.end_time).getTime() / 1000); endPrice = parseFloat(seg.end_price); endTime = alignTimeToChartCandles(endTime, chartCandles); } else { const ep = resolveUncompletedBiEndpoint({ start_time: seg.start_time, start_price: seg.start_price, end_price: null, end_time: null, direction: seg.direction }, klineData, chartCandles); endTime = ep.endTime; endPrice = ep.endPrice; } return { startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice }; } function chartTvRenderChan(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; // 图表同步事件统一由文末 bindSyncEvents 注册(带 cleanup),此处不再重复 addEventListener, // 否则每次自动刷新/重建都会在 document/window 上堆积监听导致内存泄漏。 // 显示笔的绘制 - 分别处理主周期、次周期和次次周期 if ($('#showMainBi').is(':checked') || $('#showElementBi').is(':checked') || $('#showSubSubBi').is(':checked')) { console.log('绘制笔 - 已启用'); let biLines = []; // 主周期笔 if ($('#showMainBi').is(':checked') && currentData.bi_list && currentData.bi_list.length > 0) { console.log(`绘制主周期笔数据,共${currentData.bi_list.length}条`); // 清空已有的主周期笔系列 tvWidget.series.mainBiSeries = []; tvWidget.series.mainUncompletedBiSeries = []; // 遍历处理每个笔 currentData.bi_list.forEach(function(bi) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); const endTime = Math.floor(new Date(bi.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('主周期笔时间转换错误:', bi.start_time, bi.end_time); return; } const startPrice = parseFloat(bi.start_price); const endPrice = parseFloat(bi.end_price); if (isNaN(startPrice) || isNaN(endPrice)) { console.error('主周期笔价格转换错误:', bi.start_price, bi.end_price); return; } // 添加线段 biLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: bi.direction === 1 ? '#dc3545' : '#28a745', // 主周期笔颜色 lineWidth: 1, }); // 添加到图表对象 tvWidget.series.mainBiSeries.push({ time: startTime, value: startPrice, color: bi.direction === 1 ? '#dc3545' : '#28a745', lineWidth: 1 }); } catch (e) { console.error('主周期笔处理出错:', e); } }); } // 次周期笔 if ($('#showElementBi').is(':checked') && currentData.element_bi_list && currentData.element_bi_list.length > 0) { console.log(`绘制次周期笔数据,共${currentData.element_bi_list.length}条`); // 清空已有的次周期笔系列 tvWidget.series.elementBiSeries = []; tvWidget.series.elementUncompletedBiSeries = []; currentData.element_bi_list.forEach(function(bi) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); const endTime = Math.floor(new Date(bi.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('次周期笔时间转换错误:', bi.start_time, bi.end_time); return; } const startPrice = parseFloat(bi.start_price); const endPrice = parseFloat(bi.end_price); if (isNaN(startPrice) || isNaN(endPrice)) { console.error('次周期笔价格转换错误:', bi.start_price, bi.end_price); return; } // 添加线段 biLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: bi.direction === 1 ? '#9c27b0' : '#673ab7', // 次周期笔颜色 lineWidth: 1, }); // 添加到图表对象 tvWidget.series.elementBiSeries.push({ time: startTime, value: startPrice, color: bi.direction === 1 ? '#9c27b0' : '#673ab7', lineWidth: 1 }); } catch (e) { console.error('次周期笔处理出错:', e); } }); } // 绘制未完成笔 - 主周期 if ($('#showMainBi').is(':checked') && currentData.uncompleted_bi_list && currentData.uncompleted_bi_list.length > 0) { console.log(`绘制主周期未完成笔数据,共${currentData.uncompleted_bi_list.length}条`); currentData.uncompleted_bi_list.forEach(function(bi) { try { const ep = resolveUncompletedBiEndpoint(bi, currentData.kline_data, candles); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) { console.error('主周期未完成笔时间/价格转换错误:', bi.start_time, bi.end_price); return; } // 添加未完成笔(红色虚线) biLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#FF0000', // 红色 lineWidth: 1, lineStyle: 2 // 虚线 }); // 添加到图表对象 tvWidget.series.mainUncompletedBiSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#FF0000', lineWidth: 1, lineStyle: 2 }); } catch (e) { console.error('主周期未完成笔处理出错:', e); } }); } // 绘制未完成笔 - 次周期 if ($('#showElementBi').is(':checked') && currentData.element_uncompleted_bi_list && currentData.element_uncompleted_bi_list.length > 0) { console.log(`绘制次周期未完成笔数据,共${currentData.element_uncompleted_bi_list.length}条`); currentData.element_uncompleted_bi_list.forEach(function(bi) { try { const ep = resolveUncompletedBiEndpoint( bi, currentData.element_kline_data || currentData.kline_data, candles ); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) { console.error('次周期未完成笔时间/价格转换错误:', bi.start_time, bi.end_price); return; } // 添加未完成笔(红色虚线) biLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#FF0000', // 红色 lineWidth: 1, lineStyle: 2 // 虚线 }); // 添加到图表对象 tvWidget.series.elementUncompletedBiSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#FF0000', lineWidth: 1, lineStyle: 2 }); } catch (e) { console.error('次周期未完成笔处理出错:', e); } }); } // 次次周期笔 if ($('#showSubSubBi').is(':checked') && currentData.sub_sub_bi_list && currentData.sub_sub_bi_list.length > 0) { tvWidget.series.subSubBiSeries = []; tvWidget.series.subSubUncompletedBiSeries = []; currentData.sub_sub_bi_list.forEach(function(bi) { try { const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); const endTime = bi.end_time ? Math.floor(new Date(bi.end_time).getTime() / 1000) : 0; if (isNaN(startTime) || !endTime) return; const startPrice = parseFloat(bi.start_price); const endPrice = parseFloat(bi.end_price); if (isNaN(startPrice) || isNaN(endPrice)) return; biLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: bi.direction === 1 ? '#00897b' : '#26a69a', lineWidth: 1, lineStyle: 0 }); tvWidget.series.subSubBiSeries.push({ time: startTime, value: startPrice, color: '#00897b', lineWidth: 1 }); } catch (e) { console.error('次次周期笔处理出错:', e); } }); } // 次次周期未完成笔 if ($('#showSubSubBi').is(':checked') && currentData.sub_sub_uncompleted_bi_list && currentData.sub_sub_uncompleted_bi_list.length > 0) { if (!tvWidget.series.subSubBiSeries) tvWidget.series.subSubBiSeries = []; if (!tvWidget.series.subSubUncompletedBiSeries) tvWidget.series.subSubUncompletedBiSeries = []; currentData.sub_sub_uncompleted_bi_list.forEach(function(bi) { try { const ep = resolveUncompletedBiEndpoint( bi, currentData.sub_sub_kline_data || currentData.kline_data, candles ); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) return; biLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#00695c', lineWidth: 1, lineStyle: 2 }); tvWidget.series.subSubUncompletedBiSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#00695c', lineWidth: 1, lineStyle: 2 }); } catch (e) { console.error('次次周期未完成笔处理出错:', e); } }); } // 添加所有笔到图表 // 1m 等小周期叠加到大周期时,笔数量会非常大;每条笔创建一个 series 会导致主图渲染退化 // 这里限制绘制数量,优先保留最新笔,避免把主K线和其他元素“挤没” const MAX_BI_LINE_SERIES = 800; if (biLines.length > MAX_BI_LINE_SERIES) { console.warn(`BI线条过多(${biLines.length}),仅绘制最新 ${MAX_BI_LINE_SERIES} 条以保障主图稳定`); } const linesToDraw = biLines.length > MAX_BI_LINE_SERIES ? biLines.slice(-MAX_BI_LINE_SERIES) : biLines; linesToDraw.forEach(line => { try { if (!Number.isFinite(line.startTime) || !Number.isFinite(line.endTime)) return; if (!Number.isFinite(line.startPrice) || !Number.isFinite(line.endPrice)) return; if (line.endTime <= line.startTime) return; const lineSeries = mainChart.addLineSeries({ color: line.color, lineWidth: line.lineWidth, lineStyle: line.lineStyle || 0, // 支持虚线样式 lastValueVisible: false, priceLineVisible: false, }); lineSeries.setData([ { time: line.startTime, value: line.startPrice }, { time: line.endTime, value: line.endPrice } ]); } catch (e) { console.warn('绘制BI线段失败,已跳过单条异常数据:', e); } }); } else { console.log('绘制笔 - 已禁用'); } // 显示线段的绘制 - 分别处理主周期、次周期和次次周期 if ($('#showMainSeg').is(':checked') || $('#showElementSeg').is(':checked') || $('#showSubSubSeg').is(':checked')) { console.log('绘制线段 - 已启用'); let segLines = []; // 主周期线段 if ($('#showMainSeg').is(':checked') && currentData.seg_list && currentData.seg_list.length > 0) { console.log(`绘制主周期线段数据,共${currentData.seg_list.length}条`); // 清空已有的主周期线段系列 tvWidget.series.mainSegSeries = []; tvWidget.series.mainUncompletedSegSeries = []; currentData.seg_list.forEach(function(seg) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); const endTime = Math.floor(new Date(seg.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('主周期线段时间转换错误:', seg.start_time, seg.end_time); return; } const startPrice = parseFloat(seg.start_price); const endPrice = parseFloat(seg.end_price); if (isNaN(startPrice) || isNaN(endPrice)) { console.error('主周期线段价格转换错误:', seg.start_price, seg.end_price); return; } // 添加线段 segLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: seg.direction === 1 ? '#FF6B6B' : '#4CAF50', // 主周期线段颜色 lineWidth: 2, }); // 添加到图表对象 tvWidget.series.mainSegSeries.push({ time: startTime, value: startPrice, color: seg.direction === 1 ? '#FF6B6B' : '#4CAF50', lineWidth: 2 }); } catch (e) { console.error('主周期线段处理出错:', e); } }); } // 次周期线段 if ($('#showElementSeg').is(':checked') && currentData.element_seg_list && currentData.element_seg_list.length > 0) { console.log(`绘制次周期线段数据,共${currentData.element_seg_list.length}条`); // 清空已有的次周期线段系列 tvWidget.series.elementSegSeries = []; tvWidget.series.elementUncompletedSegSeries = []; currentData.element_seg_list.forEach(function(seg) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); const endTime = Math.floor(new Date(seg.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('次周期线段时间转换错误:', seg.start_time, seg.end_time); return; } const startPrice = parseFloat(seg.start_price); const endPrice = parseFloat(seg.end_price); if (isNaN(startPrice) || isNaN(endPrice)) { console.error('次周期线段价格转换错误:', seg.start_price, seg.end_price); return; } // 添加线段 segLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: seg.direction === 1 ? '#673ab7' : '#9c27b0', // 次周期线段颜色 lineWidth: 2, }); // 添加到图表对象 tvWidget.series.elementSegSeries.push({ time: startTime, value: startPrice, color: seg.direction === 1 ? '#673ab7' : '#9c27b0', lineWidth: 2 }); } catch (e) { console.error('次周期线段处理出错:', e); } }); } // 绘制未完成线段 - 主周期 if ($('#showMainSeg').is(':checked') && currentData.uncompleted_seg_list && currentData.uncompleted_seg_list.length > 0) { console.log(`绘制主周期未完成线段数据,共${currentData.uncompleted_seg_list.length}条`); currentData.uncompleted_seg_list.forEach(function(seg) { try { const ep = resolveUncompletedSegEndpoint(seg, currentData.kline_data, candles); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) { console.error('主周期未完成线段时间/价格转换错误:', seg.start_time, seg.end_price); return; } // 添加未完成线段(红色虚线) segLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#FF0000', // 红色 lineWidth: 2, lineStyle: 2 // 虚线 }); // 添加到图表对象 tvWidget.series.mainUncompletedSegSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#FF0000', lineWidth: 2, lineStyle: 2 }); } catch (e) { console.error('主周期未完成线段处理出错:', e); } }); } // 绘制未完成线段 - 次周期 if ($('#showElementSeg').is(':checked') && currentData.element_uncompleted_seg_list && currentData.element_uncompleted_seg_list.length > 0) { console.log(`绘制次周期未完成线段数据,共${currentData.element_uncompleted_seg_list.length}条`); currentData.element_uncompleted_seg_list.forEach(function(seg) { try { const ep = resolveUncompletedSegEndpoint( seg, currentData.element_kline_data || currentData.kline_data, candles ); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) { console.error('次周期未完成线段时间/价格转换错误:', seg.start_time, seg.end_price); return; } // 添加未完成线段(红色虚线) segLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#FF0000', // 红色 lineWidth: 2, lineStyle: 2 // 虚线 }); // 添加到图表对象 tvWidget.series.elementUncompletedSegSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#FF0000', lineWidth: 2, lineStyle: 2 }); } catch (e) { console.error('次周期未完成线段处理出错:', e); } }); } // 次次周期线段 if ($('#showSubSubSeg').is(':checked') && currentData.sub_sub_seg_list && currentData.sub_sub_seg_list.length > 0) { tvWidget.series.subSubSegSeries = []; tvWidget.series.subSubUncompletedSegSeries = []; currentData.sub_sub_seg_list.forEach(function(seg) { try { const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); const endTime = seg.end_time ? Math.floor(new Date(seg.end_time).getTime() / 1000) : 0; if (isNaN(startTime) || !endTime) return; const startPrice = parseFloat(seg.start_price); const endPrice = parseFloat(seg.end_price); if (isNaN(startPrice) || isNaN(endPrice)) return; segLines.push({ startTime: startTime, endTime: endTime, startPrice: startPrice, endPrice: endPrice, color: seg.direction === 1 ? '#00897b' : '#26a69a', lineWidth: 2, lineStyle: 0 }); tvWidget.series.subSubSegSeries.push({ time: startTime, value: startPrice, color: '#00897b', lineWidth: 2 }); } catch (e) { console.error('次次周期线段处理出错:', e); } }); } // 次次周期未完成线段 if ($('#showSubSubSeg').is(':checked') && currentData.sub_sub_uncompleted_seg_list && currentData.sub_sub_uncompleted_seg_list.length > 0) { if (!tvWidget.series.subSubUncompletedSegSeries) tvWidget.series.subSubUncompletedSegSeries = []; currentData.sub_sub_uncompleted_seg_list.forEach(function(seg) { try { const ep = resolveUncompletedSegEndpoint( seg, currentData.sub_sub_kline_data || currentData.kline_data, candles ); if (isNaN(ep.startTime) || isNaN(ep.endTime) || isNaN(ep.startPrice) || isNaN(ep.endPrice)) return; segLines.push({ startTime: ep.startTime, endTime: ep.endTime, startPrice: ep.startPrice, endPrice: ep.endPrice, color: '#00695c', lineWidth: 2, lineStyle: 2 }); tvWidget.series.subSubUncompletedSegSeries.push({ time: ep.startTime, value: ep.startPrice, color: '#00695c', lineWidth: 2, lineStyle: 2 }); } catch (e) { console.error('次次周期未完成线段处理出错:', e); } }); } // 添加所有线段到图表 segLines.forEach(line => { const lineSeries = mainChart.addLineSeries({ color: line.color, lineWidth: line.lineWidth, lineStyle: line.lineStyle || 0, // 支持虚线样式 lastValueVisible: false, priceLineVisible: false, }); lineSeries.setData([ { time: line.startTime, value: line.startPrice }, { time: line.endTime, value: line.endPrice } ]); }); } else { console.log('绘制线段 - 已禁用'); } // 显示中枢的绘制 - 分别处理主周期、次周期和次次周期(包含BI中枢,沿用同样样式与开关) if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked') || $('#showSubSubZs').is(':checked')) { console.log('绘制中枢 - 已启用'); // 主周期中枢 if ($('#showMainZs').is(':checked') && currentData.zs_list && currentData.zs_list.length > 0) { console.log(`绘制主周期中枢数据,共${currentData.zs_list.length}条`); currentData.zs_list.forEach(function(zs) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const endTime = Math.floor(new Date(zs.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('主周期中枢时间转换错误:', zs.start_time, zs.end_time); return; } const zg = parseFloat(zs.zg); // 中枢上沿 const zd = parseFloat(zs.zd); // 中枢下沿 const gg = parseFloat(zs.gg); // 中枢高高 const dd = parseFloat(zs.dd); // 中枢低低 if (isNaN(zg) || isNaN(zd)) { console.error('主周期中枢价格转换错误:', zs.zg, zs.zd); return; } // 创建中枢上边界 const topSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); topSeries.setData([ { time: startTime, value: zg }, { time: endTime, value: zg } ]); // 为下边界创建另一条线 const bottomSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); bottomSeries.setData([ { time: startTime, value: zd }, { time: endTime, value: zd } ]); // 添加左边界 const leftSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); leftSeries.setData([ { time: startTime, value: zd }, { time: startTime, value: zg } ]); // 添加右边界 const rightSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); rightSeries.setData([ { time: endTime, value: zd }, { time: endTime, value: zg } ]); // 绘制gg线(中枢高高) if (!isNaN(gg) && gg > 0) { const ggSeries = mainChart.addLineSeries({ color: '#F1C40F', // 使用中枢自己的颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); ggSeries.setData([ { time: startTime, value: gg }, { time: endTime, value: gg } ]); } // 绘制dd线(中枢低低) if (!isNaN(dd) && dd > 0) { const ddSeries = mainChart.addLineSeries({ color: '#F1C40F', // 使用中枢自己的颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); ddSeries.setData([ { time: startTime, value: dd }, { time: endTime, value: dd } ]); } // 添加到图表对象 tvWidget.series.mainZsSeries.push({ time: startTime, value: zg, color: '#F1C40F', lineWidth: 1 }); tvWidget.series.mainZsSeries.push({ time: endTime, value: zg, color: '#F1C40F', lineWidth: 1 }); tvWidget.series.mainZsSeries.push({ time: startTime, value: zd, color: '#F1C40F', lineWidth: 1 }); tvWidget.series.mainZsSeries.push({ time: endTime, value: zd, color: '#F1C40F', lineWidth: 1 }); } catch (e) { console.error('主周期中枢处理出错:', e); } }); } // 次周期中枢 if ($('#showElementZs').is(':checked') && currentData.element_zs_list && currentData.element_zs_list.length > 0) { console.log(`绘制次周期中枢数据,共${currentData.element_zs_list.length}条`); currentData.element_zs_list.forEach(function(zs) { try { // 直接使用UTC时间戳(秒) const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const endTime = Math.floor(new Date(zs.end_time).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { console.error('次周期中枢时间转换错误:', zs.start_time, zs.end_time); return; } const zg = parseFloat(zs.zg); // 中枢上沿 const zd = parseFloat(zs.zd); // 中枢下沿 const gg = parseFloat(zs.gg); // 中枢高高 const dd = parseFloat(zs.dd); // 中枢低低 if (isNaN(zg) || isNaN(zd)) { console.error('次周期中枢价格转换错误:', zs.zg, zs.zd); return; } // 创建中枢上边界 const topSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); topSeries.setData([ { time: startTime, value: zg }, { time: endTime, value: zg } ]); // 为下边界创建另一条线 const bottomSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); bottomSeries.setData([ { time: startTime, value: zd }, { time: endTime, value: zd } ]); // 添加左边界 const leftSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); leftSeries.setData([ { time: startTime, value: zd }, { time: startTime, value: zg } ]); // 添加右边界 const rightSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); rightSeries.setData([ { time: endTime, value: zd }, { time: endTime, value: zg } ]); // 绘制gg线(中枢高高) if (!isNaN(gg) && gg > 0) { const ggSeries = mainChart.addLineSeries({ color: '#3f51b5', // 使用次周期中枢自己的颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); ggSeries.setData([ { time: startTime, value: gg }, { time: endTime, value: gg } ]); } // 绘制dd线(中枢低低) if (!isNaN(dd) && dd > 0) { const ddSeries = mainChart.addLineSeries({ color: '#3f51b5', // 使用次周期中枢自己的颜色 lineWidth: 1, lastValueVisible: false, priceLineVisible: false, }); ddSeries.setData([ { time: startTime, value: dd }, { time: endTime, value: dd } ]); } // 添加到图表对象 tvWidget.series.elementZsSeries.push({ time: startTime, value: zg, color: '#3f51b5', lineWidth: 1 }); tvWidget.series.elementZsSeries.push({ time: endTime, value: zg, color: '#3f51b5', lineWidth: 1 }); tvWidget.series.elementZsSeries.push({ time: startTime, value: zd, color: '#3f51b5', lineWidth: 1 }); tvWidget.series.elementZsSeries.push({ time: endTime, value: zd, color: '#3f51b5', lineWidth: 1 }); } catch (e) { console.error('次周期中枢处理出错:', e); } }); } // 次次周期SEG中枢 if ($('#showSubSubZs').is(':checked') && currentData.sub_sub_zs_list && currentData.sub_sub_zs_list.length > 0) { const subSubZsColor = '#00897b'; currentData.sub_sub_zs_list.forEach(function(zs) { try { const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : 0; if (isNaN(startTime) || !endTime) return; const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd); if (isNaN(zg) || isNaN(zd)) return; mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]); mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]); mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]); mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]); if (!isNaN(gg) && gg > 0) mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]); if (!isNaN(dd) && dd > 0) mainChart.addLineSeries({ color: subSubZsColor, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]); } catch (e) { console.error('次次周期中枢处理出错:', e); } }); } } else { console.log('绘制中枢 - 已禁用'); } // BI中枢(已完成)- 使用独立的BI开关 if ($('#showMainBiZs').is(':checked') && currentData.bi_zs_list && currentData.bi_zs_list.length > 0) { try { console.log(`绘制主周期BI中枢数据,共${currentData.bi_zs_list.length}条`); } catch (e) {} currentData.bi_zs_list.forEach(function(zs) { try { const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { return; } const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd); if (isNaN(zg) || isNaN(zd)) { return; } const color = '#F1C40F'; const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]); const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]); const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]); const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]); if (!isNaN(gg) && gg > 0) { const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]); } if (!isNaN(dd) && dd > 0) { const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]); } } catch (e) { console.error('主周期BI中枢处理出错:', e); } }); } if ($('#showSubSubBiZs').is(':checked') && currentData.sub_sub_bi_zs_list && currentData.sub_sub_bi_zs_list.length > 0) { currentData.sub_sub_bi_zs_list.forEach(function(zs) { try { const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const kd = currentData.kline_data || []; const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : (kd.length ? Math.floor(new Date(kd[kd.length-1].date).getTime() / 1000) : 0); if (isNaN(startTime) || !endTime) return; const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd); if (isNaN(zg) || isNaN(zd)) return; const color = '#00897b'; mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]); mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]); mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]); mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]); if (!isNaN(gg) && gg > 0) mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]); if (!isNaN(dd) && dd > 0) mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }).setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]); } catch (e) { console.error('次次周期BI中枢处理出错:', e); } }); } if ($('#showElementBiZs').is(':checked') && currentData.element_bi_zs_list && currentData.element_bi_zs_list.length > 0) { try { console.log(`绘制次周期BI中枢数据,共${currentData.element_bi_zs_list.length}条`); } catch (e) {} currentData.element_bi_zs_list.forEach(function(zs) { try { const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000); const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); if (isNaN(startTime) || isNaN(endTime)) { return; } const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd); if (isNaN(zg) || isNaN(zd)) { return; } const color = '#3f51b5'; const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]); const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]); const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]); const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]); if (!isNaN(gg) && gg > 0) { const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]); } if (!isNaN(dd) && dd > 0) { const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }); ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]); } } catch (e) { console.error('次周期BI中枢处理出错:', e); } }); } }