/* overlays.js */ function clearEMA52Series() { // 清理所有EMA52相关系列(包括线系列和标记系列) if (tvWidget && tvWidget.series && tvWidget.series.ema52Series) { tvWidget.series.ema52Series.forEach(series => { try { if (tvWidget.mainChart) { tvWidget.mainChart.removeSeries(series); } } catch (e) { console.warn('移除EMA52系列失败:', e); } }); tvWidget.series.ema52Series = []; } console.log('✅ EMA52系列已清理'); } // 存储上次的EMA52数据,用于比较 let lastEMA52Data = null; // 更新EMA52显示 function updateEMA52Display(data) { console.log('更新EMA52显示', data.ema52_dict); // 检查是否有EMA52数据 if (!data.ema52_dict || Object.keys(data.ema52_dict).length === 0) { // 即使没有数据也要清理之前的系列 clearEMA52Series(); lastEMA52Data = null; return; } // 检查数据是否与上次相同,如果相同则跳过更新 const currentDataStr = JSON.stringify(data.ema52_dict); if (lastEMA52Data === currentDataStr) { console.log('EMA52数据未变化,跳过更新'); return; } // 清除之前的EMA52线系列 clearEMA52Series(); // 保存当前数据 lastEMA52Data = currentDataStr; // 定义时间周期的显示顺序和颜色 const timeframeColors = { '1m': '#FF0000', // 红色 '3m': '#FF6600', // 橙红色 '5m': '#FF9900', // 橙色 '10m': '#DDAA00', // 黄色 '15m': '#FFCC00', // 黄色 '30m': '#99FF00', // 黄绿色 '1h': '#00FF00', // 绿色 '2h': '#00FF99', // 青绿色 '4h': '#00FFFF', // 青色 '6h': '#0099FF', // 蓝青色 '8h': '#0066FF', // 蓝色 '12h': '#3300FF', // 蓝紫色 '16h': '#6600FF', // 紫色 '1d': '#9900FF', // 紫红色 '2d': '#CC00FF', // 品红色 '3d': '#FF00CC', // 粉红色 }; // 按照预定义顺序排列时间周期 const orderedTimeframes = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d']; // 获取主图表容器 const chartContainer = document.getElementById('tradingview_chart'); if (!chartContainer || !tvWidget.mainChart) { return; } orderedTimeframes.forEach(timeframe => { if (data.ema52_dict[timeframe] !== undefined && data.ema52_dict[timeframe] !== null) { const value = data.ema52_dict[timeframe]; const color = timeframeColors[timeframe] || '#800080'; // 添加虚线到主图表 if (tvWidget.mainChart) { try { // 使用LightweightCharts的addLineSeries创建虚线 const lineSeries = tvWidget.mainChart.addLineSeries({ color: color, lineWidth: 1, lineStyle: 2, // 虚线样式 title: ``, lastValueVisible: false, // 不在价格标尺显示数值 priceLineVisible: false, // 不显示默认价格线 crosshairMarkerVisible: false, priceFormat: { type: 'price', precision: 2, minMove: 0.01, }, priceScaleId: 'right', }); // 创建横线数据(使用K线数据的时间范围) if (data.kline_data && data.kline_data.length > 0) { const firstKline = data.kline_data[0]; const lastKline = data.kline_data[data.kline_data.length - 1]; const startTime = Math.floor(new Date(firstKline.date).getTime() / 1000); const endTime = Math.floor(new Date(lastKline.date).getTime() / 1000); const lineData = [ { time: startTime, value: value }, { time: endTime, value: value } ]; lineSeries.setData(lineData); // 使用标记在K线右侧显示文字 if (data.kline_data && data.kline_data.length > 0) { const lastKline = data.kline_data[data.kline_data.length - 1]; const lastTime = Math.floor(new Date(lastKline.date).getTime() / 1000); // 计算时间间隔(用于右偏移) let timeInterval = 60; // 默认1分钟 if (data.kline_data.length > 1) { const secondLastKline = data.kline_data[data.kline_data.length - 2]; const secondLastTime = Math.floor(new Date(secondLastKline.date).getTime() / 1000); timeInterval = lastTime - secondLastTime; } // 创建右偏移的时间点(在最后一根K线右边) const rightOffsetTime = lastTime + timeInterval*100; // 创建一个新的线系列用于显示文字标记 const markerSeries = tvWidget.mainChart.addLineSeries({ color: 'transparent', lineWidth: 0, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false, priceScaleId: 'right', }); // 添加一个透明的数据点在右偏移位置 markerSeries.setData([{ time: rightOffsetTime, value: value }]); // 在右偏移位置添加文字标记 markerSeries.setMarkers([{ time: rightOffsetTime, position: 'inBar', color: color, shape: 'square', text: ` ${timeframe} ${value.toFixed(2)}`, size: 1, }]); // 保存标记系列引用 if (!tvWidget.series.ema52Series) { tvWidget.series.ema52Series = []; } tvWidget.series.ema52Series.push(markerSeries); } } // 保存系列引用以便后续清理 if (!tvWidget.series.ema52Series) { tvWidget.series.ema52Series = []; } tvWidget.series.ema52Series.push(lineSeries); } catch (e) { console.warn('添加EMA52虚线失败:', timeframe, e); } } } }); } // 获取随机颜色 function getRandomColor() { const colors = ['#2962FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#48CAE4', '#F38BA8', '#A8DADC', '#F1C0E8']; return colors[Math.floor(Math.random() * colors.length)]; } // 显示布林带配置弹窗 function showBBConfig() { console.log('📂 显示布林带配置弹窗,设置为添加模式'); $('#bbConfigModal').css('display', 'flex'); // 重置表单 $('#bbType').val('Bollinger Bands'); $('#bbLength').val(20); $('#bbStdDev').val(2); $('#bbSource').val('close'); $('#bbLineWidth').val(2); $('#bbLineStyle').val(0); $('#bbColor').val(getRandomColor()); // 移除所有现有的点击事件,避免事件冲突 $('#bbConfigSubmitBtn').off('click'); // 设置按钮为添加模式 $('#bbConfigSubmitBtn').text('添加').on('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log('🖱️ 点击了添加按钮(来自showBBConfig),准备添加新布林带'); addBollingerBand(); }); console.log('🔄 按钮已设置为添加模式'); // 更新预览 setTimeout(updateBBLinePreview, 50); } // 显示均线配置弹窗 function showMAConfig() { console.log('📂 显示均线配置弹窗,设置为添加模式'); $('#maConfigModal').css('display', 'flex'); // 重置表单 $('#maType').val('SMA'); $('#maLength').val(20); $('#maSource').val('close'); $('#maSmoothType').val('none'); $('#maSmoothLength').val(3).prop('disabled', true); $('#maLineWidth').val(2); $('#maLineStyle').val(0); $('#maColor').val(getRandomColor()); // 移除所有现有的点击事件,避免事件冲突 $('#maConfigSubmitBtn').off('click'); // 设置按钮为添加模式 $('#maConfigSubmitBtn').text('添加').on('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log('🖱️ 点击了添加按钮(来自showMAConfig),准备添加新均线'); addMovingAverage(); }); console.log('🔄 按钮已设置为添加模式'); // 更新预览 setTimeout(updateLinePreview, 50); } // 隐藏均线配置弹窗 function hideMAConfig() { console.log('🔒 关闭均线配置弹窗'); $('#maConfigModal').css('display', 'none'); // 移除所有现有的点击事件 $('#maConfigSubmitBtn').off('click'); // 恢复按钮为添加模式 - 但不立即绑定事件,防止意外触发 $('#maConfigSubmitBtn').text('添加'); console.log('🔄 按钮已重置为添加模式,等待用户主动点击"添加均线"按钮'); } // 点击弹窗外部关闭(MA) $(document).on('click', '#maConfigModal', function(e) { if (e.target === this) { hideMAConfig(); } }); // 添加均线 function addMovingAverage() { console.log('🚨🚨🚨 ➕ 警告:addMovingAverage函数被调用了!!!'); console.log('📊 添加前均线配置数量:', movingAverages.length); console.log('🔍 调用堆栈:', new Error().stack); const config = { id: ++maIdCounter, type: $('#maType').val(), length: parseInt($('#maLength').val()), source: $('#maSource').val(), smoothType: $('#maSmoothType').val(), smoothLength: parseInt($('#maSmoothLength').val()), lineWidth: parseInt($('#maLineWidth').val()), lineStyle: parseInt($('#maLineStyle').val()), color: $('#maColor').val(), visible: true }; console.log('📝 新均线配置:', { id: config.id, type: config.type, length: config.length, source: config.source, color: config.color }); // 验证输入 if (config.length < 1) { alert('均线长度必须大于0'); return; } if (config.smoothType !== 'none' && (config.smoothLength < 1 || config.smoothLength > 50)) { alert('平滑长度必须在1-50之间'); return; } movingAverages.push(config); console.log('📊 添加后均线配置数量:', movingAverages.length); hideMAConfig(); // 统一刷新(带重试,确保图表就绪) refreshIndicatorsNowOrLater(5); } // 计算均线数据 function calculateMA() { if (window.App && window.App.Indicators && typeof window.App.Indicators.calculateMA === 'function') { return window.App.Indicators.calculateMA.apply(null, arguments); } console.warn('calculateMA 未就绪,返回空数组'); return []; } // 应用平滑处理 function applySmoothToMA(maData, smoothType, smoothLength) { if (smoothType === 'none' || maData.length < smoothLength) { return maData; } const result = []; for (let i = smoothLength - 1; i < maData.length; i++) { let value; switch(smoothType) { case 'SMA': value = maData.slice(i - smoothLength + 1, i + 1) .reduce((sum, item) => sum + item.value, 0) / smoothLength; break; case 'EMA': const multiplier = 2 / (smoothLength + 1); if (result.length === 0) { // 第一个EMA值使用SMA作为种子值 value = maData.slice(i - smoothLength + 1, i + 1) .reduce((sum, item) => sum + item.value, 0) / smoothLength; } else { // 后续EMA值使用标准公式 value = maData[i].value * multiplier + result[result.length - 1].value * (1 - multiplier); } break; case 'WMA': let weightSum = 0; let valueSum = 0; for (let j = 0; j < smoothLength; j++) { const weight = j + 1; weightSum += weight; valueSum += maData[i - smoothLength + 1 + j].value * weight; } value = valueSum / weightSum; break; default: value = maData[i].value; } result.push({ time: maData[i].time, value: value }); } return result; } // 更新技术指标面板(统一面板) function updateIndicatorPanel() { const panel = $('#indicatorPanel'); if (movingAverages.length === 0 && bollingerBands.length === 0) { panel.hide(); return; } let html = ''; let hasVisibleIndicator = false; // 添加均线指标 movingAverages.forEach(ma => { // 获取最新价格 const latestValue = ma.data && ma.data.length > 0 ? ma.data[ma.data.length - 1].value.toFixed(2) : '--'; const smoothText = ma.smoothType !== 'none' ? `, ${ma.smoothType}(${ma.smoothLength})` : ''; // 获取线条样式描述 const lineStyleNames = ['实线', '点线', '虚线', '大虚线']; const styleText = ma.lineWidth && ma.lineStyle !== undefined ? ` ${ma.lineWidth}px ${lineStyleNames[ma.lineStyle] || '实线'}` : ''; // 显示所有均线(包括隐藏的),但用不同样式区分 const itemStyle = ma.visible ? '' : 'opacity: 0.5;'; const eyeIcon = ma.visible ? 'bi-eye' : 'bi-eye-slash'; html += `
${ma.type}(${ma.length})${smoothText}${styleText} ${ma.visible ? latestValue : '--'}
`; if (ma.visible) { hasVisibleIndicator = true; } }); // 添加布林带指标 bollingerBands.forEach(bb => { // 获取最新价格 const latestValue = bb.data && bb.data.length > 0 ? bb.data[bb.data.length - 1].middle.toFixed(2) : '--'; // 获取线条样式描述 const lineStyleNames = ['实线', '点线', '虚线', '大虚线']; const styleText = bb.lineWidth && bb.lineStyle !== undefined ? ` ${bb.lineWidth}px ${lineStyleNames[bb.lineStyle] || '实线'}` : ''; // 显示所有布林带(包括隐藏的),但用不同样式区分 const itemStyle = bb.visible ? '' : 'opacity: 0.5;'; const eyeIcon = bb.visible ? 'bi-eye' : 'bi-eye-slash'; // 创建渐变颜色指示器显示三种颜色 const colorIndicatorStyle = ` background: linear-gradient(90deg, ${bb.upperColor || '#ff6b6b'} 0%, ${bb.middleColor || '#667eea'} 50%, ${bb.lowerColor || '#4ecdc4'} 100%); border-radius: 2px; `; html += `
BB(${bb.length}, ${bb.upperMultiplier || 2}, ${bb.lowerMultiplier || 2})${styleText} ${bb.visible ? latestValue : '--'}
`; if (bb.visible) { hasVisibleIndicator = true; } }); panel.html(html); if (hasVisibleIndicator || movingAverages.length > 0 || bollingerBands.length > 0) { panel.show(); } else { panel.hide(); } } // 更新均线面板(兼容旧代码) function updateMAPanel() { updateIndicatorPanel(); } // 切换均线可见性 function toggleMAVisibility(maId) { console.log('👁️ 切换均线可见性,ID:', maId, 'Type:', typeof maId); const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换 if (ma) { console.log('📝 找到均线,当前可见性:', ma.visible); ma.visible = !ma.visible; console.log('✅ 切换后可见性:', ma.visible); // 直接使用前端数据重新计算均线 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addMovingAveragesToChart(candles); } // 刷新面板图标与数值 try { updateIndicatorPanel(); } catch(e) {} } else { console.error('❌ 未找到要切换可见性的均线,ID:', maId); } } // 配置均线 function configMA(maId) { console.log('⚙️ 开始配置均线,ID:', maId, 'Type:', typeof maId); console.log('📊 当前所有均线ID:', movingAverages.map(m => ({id: m.id, type: typeof m.id}))); const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换 if (!ma) { console.error('❌ 未找到要配置的均线,ID:', maId); return; } console.log('📝 找到要配置的均线:', ma.type, ma.length, ma.color); // 填充表单 $('#maType').val(ma.type); $('#maLength').val(ma.length); $('#maSource').val(ma.source); $('#maSmoothType').val(ma.smoothType); $('#maSmoothLength').val(ma.smoothLength); $('#maLineWidth').val(ma.lineWidth || 2); $('#maLineStyle').val(ma.lineStyle || 0); $('#maColor').val(ma.color); // 启用/禁用平滑长度输入 $('#maSmoothLength').prop('disabled', ma.smoothType === 'none'); // 移除所有现有的点击事件,避免事件冲突 $('#maConfigSubmitBtn').off('click'); // 修改按钮为更新模式 $('#maConfigSubmitBtn').text('更新').on('click', (function(capturedMaId) { return function(e) { e.preventDefault(); e.stopPropagation(); console.log('🖱️ 点击了更新按钮,准备更新ID:', capturedMaId); console.log('🔍 捕获的ID类型:', typeof capturedMaId); updateMovingAverage(capturedMaId); }; })(maId)); console.log('🔄 按钮已切换为更新模式'); $('#maConfigModal').css('display', 'flex'); // 更新预览 setTimeout(updateLinePreview, 50); } // 更新均线 function updateMovingAverage(maId) { console.log('🔄 开始更新均线,ID:', maId, 'Type:', typeof maId); console.log('📊 更新前均线配置数量:', movingAverages.length); console.log('📊 当前所有均线:', movingAverages.map(m => ({id: m.id, type: typeof m.id, maType: m.type, length: m.length, color: m.color}))); const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换 if (!ma) { console.error('❌ 未找到要更新的均线配置,ID:', maId); console.error('❌ 可用的均线ID:', movingAverages.map(m => m.id)); alert('错误:未找到要更新的均线配置!'); return; } console.log('📝 找到要更新的均线:', ma.type, ma.length, ma.color); // 验证输入 const newLength = parseInt($('#maLength').val()); const newSmoothLength = parseInt($('#maSmoothLength').val()); const newSmoothType = $('#maSmoothType').val(); if (newLength < 1) { alert('均线长度必须大于0'); return; } if (newSmoothType !== 'none' && (newSmoothLength < 1 || newSmoothLength > 50)) { alert('平滑长度必须在1-50之间'); return; } // 记录更新前的配置 console.log('🔧 更新前配置:', { type: ma.type, length: ma.length, source: ma.source, color: ma.color }); // 更新配置 ma.type = $('#maType').val(); ma.length = newLength; ma.source = $('#maSource').val(); ma.smoothType = newSmoothType; ma.smoothLength = newSmoothLength; ma.lineWidth = parseInt($('#maLineWidth').val()); ma.lineStyle = parseInt($('#maLineStyle').val()); ma.color = $('#maColor').val(); // 记录更新后的配置 console.log('✅ 更新后配置:', { type: ma.type, length: ma.length, source: ma.source, color: ma.color }); console.log('📊 更新后均线配置数量:', movingAverages.length); hideMAConfig(); // 直接使用前端数据重新计算均线 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addMovingAveragesToChart(candles); } // 刷新面板显示 try { updateIndicatorPanel(); } catch(e) {} } // 删除均线 function deleteMA(maId) { console.log('🗑️ 删除均线,ID:', maId, 'Type:', typeof maId); console.log('📊 删除前均线配置数量:', movingAverages.length); const beforeCount = movingAverages.length; movingAverages = movingAverages.filter(m => m.id != maId); // 使用!=而不是!==来兼容类型转换 console.log('📊 删除后均线配置数量:', movingAverages.length); console.log('✅ 是否成功删除:', beforeCount > movingAverages.length); // 直接使用前端数据重新计算均线 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addMovingAveragesToChart(candles); } // 刷新技术指标面板并关闭可能残留的配置弹窗 try { updateIndicatorPanel(); } catch(e) {} try { if ($('#maConfigModal').is(':visible')) { hideMAConfig(); } } catch(e) {} } // 获取当前K线数据的辅助函数(与基础显示的主/小/次次周期保持一致) function getCurrentCandleData() { if (!currentData || !currentData.kline_data) { return []; } const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') && currentData.sub_sub_kline_data && Array.isArray(currentData.sub_sub_kline_data); const useElementPeriod = !useSubSubPeriod && $('#elementPeriodKline').is(':checked') && currentData.element_kline_data && Array.isArray(currentData.element_kline_data); let source = currentData.kline_data; if (useSubSubPeriod) { source = currentData.sub_sub_kline_data; } else if (useElementPeriod) { source = currentData.element_kline_data; } const candles = source.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), }; }); return candles; } // 从蜡烛数据生成 Heikin-Ashi(平均K) function buildHeikinFromCandles(candles) { if (!Array.isArray(candles) || candles.length === 0) return []; const result = []; let prevHaClose = (candles[0].open + candles[0].high + candles[0].low + candles[0].close) / 4; let prevHaOpen = (candles[0].open + candles[0].close) / 2; const firstHaHigh = Math.max(candles[0].high, prevHaOpen, prevHaClose); const firstHaLow = Math.min(candles[0].low, prevHaOpen, prevHaClose); result.push({ time: candles[0].time, open: prevHaOpen, high: firstHaHigh, low: firstHaLow, close: prevHaClose }); for (let i = 1; i < candles.length; i++) { const c = candles[i]; const haClose = (c.open + c.high + c.low + c.close) / 4; const haOpen = (prevHaOpen + prevHaClose) / 2; const haHigh = Math.max(c.high, haOpen, haClose); const haLow = Math.min(c.low, haOpen, haClose); result.push({ time: c.time, open: haOpen, high: haHigh, low: haLow, close: haClose }); prevHaOpen = haOpen; prevHaClose = haClose; } return result; } // 从分析数据生成KLC蜡烛数据 function buildKLCFromAnalysis(data) { if (!data) return []; // 根据基础显示的K线周期选择:次次周期 / 小周期 / 主周期 const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') && data.sub_sub_klc_list && Array.isArray(data.sub_sub_klc_list); const useElementPeriod = !useSubSubPeriod && $('#elementPeriodKline').is(':checked') && data.element_klc_list && Array.isArray(data.element_klc_list); const klcList = useSubSubPeriod ? data.sub_sub_klc_list : (useElementPeriod ? data.element_klc_list : data.klc_list); if (!klcList || !Array.isArray(klcList)) return []; const klcCandles = []; // 遍历KLC列表,转换为蜡烛数据格式 klcList.forEach(klc => { if (!klc || !klc.date) return; const date = new Date(klc.date); const timestamp = date.getTime() / 1000; const candle = { time: timestamp, open: klc.open || 0, high: klc.high || 0, low: klc.low || 0, close: klc.close || 0 }; klcCandles.push(candle); }); return klcCandles; } // 计算默认砖大小(优先使用ATR的最新非零值,否则按收盘价的0.5%) function computeDefaultBrickSize(candles) { try { const useElementPeriod = $('#elementPeriodKline').is(':checked') && currentData.element_kline_data && Array.isArray(currentData.element_kline_data); const atrArr = useElementPeriod ? (currentData.element_atr || currentData.atr) : currentData.atr; if (Array.isArray(atrArr) && atrArr.length > 0) { for (let i = atrArr.length - 1; i >= 0; i--) { const v = parseFloat(atrArr[i]); if (!isNaN(v) && v > 0) { return v; } } } } catch(e) {} const last = candles && candles.length ? candles[candles.length - 1].close : 0; const size = last * 0.005; // 默认0.5% return size > 0 ? size : 1; } // 从蜡烛数据生成Renko砖(返回伪K线数据用于CandlestickSeries显示) function buildRenkoFromCandles(candles) { if (!Array.isArray(candles) || candles.length === 0) return []; const tryBuild = (size) => { const bricks = []; let lastClose = candles[0].close; for (let i = 1; i < candles.length; i++) { // 确保同一根K内多砖的时间唯一且递增 let nextTime = candles[i].time; const price = candles[i].close; let diff = price - lastClose; // 向上生成砖 while (diff >= size) { const open = lastClose; const close = lastClose + size; bricks.push({ time: nextTime, open: open, high: close, low: open, close: close }); lastClose = close; diff = price - lastClose; nextTime += 1; // 递增1秒,保证唯一 } // 向下生成砖 while (diff <= -size) { const open = lastClose; const close = lastClose - size; bricks.push({ time: nextTime, open: open, high: open, low: close, close: close }); lastClose = close; diff = price - lastClose; nextTime += 1; // 递增1秒,保证唯一 } } return bricks; }; // 计算初始砖大小,若无砖则逐步缩小后重试,避免不显示 let brickSize = computeDefaultBrickSize(candles); let result = tryBuild(brickSize); let attempts = 0; while (result.length === 0 && attempts < 4) { brickSize *= 0.5; // 缩小一半后重试 result = tryBuild(brickSize); attempts += 1; } return result; } // 指标统一刷新(若图表未就绪则自动重试) function refreshIndicatorsNowOrLater(retries) { try { if (tvWidget && tvWidget.mainChart && currentData && (currentData.kline_data || currentData.element_kline_data)) { const candles = getCurrentCandleData(); if (candles && candles.length > 0) { addMovingAveragesToChart(candles); addBollingerBandsToChart(candles); try { updateIndicatorPanel(); } catch(e) {} return; } } } catch(e) {} if (retries && retries > 0) { setTimeout(function(){ refreshIndicatorsNowOrLater(retries - 1); }, 200); } } // 添加均线到图表 function addMovingAveragesToChart() { if (window.App && window.App.Charts && typeof window.App.Charts.addMovingAveragesToChart === 'function') { return window.App.Charts.addMovingAveragesToChart.apply(null, arguments); } console.warn('addMovingAveragesToChart 未就绪'); } // 监听平滑类型变化(保留,UI 逻辑) $(document).on('change', '#maSmoothType', function() { const isNone = $(this).val() === 'none'; $('#maSmoothLength').prop('disabled', isNone); }); // 重复定义的 showBBConfig 已移除(合并到上方唯一实现) // 隐藏布林带配置弹窗 function hideBBConfig() { console.log('🔒 关闭布林带配置弹窗'); $('#bbConfigModal').css('display', 'none'); // 移除所有现有的点击事件 $('#bbConfigSubmitBtn').off('click'); // 恢复按钮为添加模式 - 但不立即绑定事件,防止意外触发 $('#bbConfigSubmitBtn').text('添加'); console.log('🔄 按钮已重置为添加模式,等待用户主动点击"添加布林带"按钮'); } // 添加布林带 function addBollingerBand() { console.log('🚨🚨🚨 ➕ 警告:addBollingerBand函数被调用了!!!'); console.log('📊 添加前布林带配置数量:', bollingerBands.length); console.log('🔍 调用堆栈:', new Error().stack); const config = { id: ++bbIdCounter, type: $('#bbType').val(), length: parseInt($('#bbLength').val()), upperMultiplier: parseFloat($('#bbUpperMultiplier').val()), lowerMultiplier: parseFloat($('#bbLowerMultiplier').val()), source: $('#bbSource').val(), lineWidth: parseInt($('#bbLineWidth').val()), lineStyle: parseInt($('#bbLineStyle').val()), upperColor: $('#bbUpperColor').val(), middleColor: $('#bbMiddleColor').val(), lowerColor: $('#bbLowerColor').val(), visible: true }; console.log('📝 新布林带配置:', { id: config.id, type: config.type, length: config.length, upperMultiplier: config.upperMultiplier, lowerMultiplier: config.lowerMultiplier, source: config.source, colors: { upper: config.upperColor, middle: config.middleColor, lower: config.lowerColor } }); // 验证输入 if (config.length < 1) { alert('布林带长度必须大于0'); return; } if (config.upperMultiplier < 0.1) { alert('上轨倍数必须大于0.1'); return; } if (config.lowerMultiplier < 0.1) { alert('下轨倍数必须大于0.1'); return; } bollingerBands.push(config); console.log('📊 添加后布林带配置数量:', bollingerBands.length); hideBBConfig(); // 统一刷新(带重试,确保图表就绪) refreshIndicatorsNowOrLater(5); } // 计算布林带数据 function calculateBB() { if (window.App && window.App.Indicators && typeof window.App.Indicators.calculateBB === 'function') { return window.App.Indicators.calculateBB.apply(null, arguments); } console.warn('calculateBB 未就绪,返回空数组'); return []; } // 更新布林带面板(兼容旧代码) function updateBBPanel() { updateIndicatorPanel(); } // 切换布林带可见性 function toggleBBVisibility(bbId) { console.log('👁️ 切换布林带可见性,ID:', bbId, 'Type:', typeof bbId); const bb = bollingerBands.find(b => b.id == bbId); // 使用==而不是===来兼容类型转换 if (bb) { console.log('📝 找到布林带,当前可见性:', bb.visible); bb.visible = !bb.visible; console.log('✅ 切换后可见性:', bb.visible); // 直接使用前端数据重新计算布林带 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addBollingerBandsToChart(candles); addMovingAveragesToChart(candles); // 同时更新均线 } // 刷新面板图标与数值 try { updateIndicatorPanel(); } catch(e) {} } else { console.error('❌ 未找到要切换可见性的布林带,ID:', bbId); } } // 配置布林带 function configBB(bbId) { console.log('⚙️ 开始配置布林带,ID:', bbId, 'Type:', typeof bbId); console.log('📊 当前所有布林带ID:', bollingerBands.map(b => ({id: b.id, type: typeof b.id}))); const bb = bollingerBands.find(b => b.id == bbId); // 使用==而不是===来兼容类型转换 if (!bb) { console.error('❌ 未找到要配置的布林带,ID:', bbId); return; } console.log('📝 找到要配置的布林带:', bb.type, bb.length, bb.upperMultiplier, bb.lowerMultiplier, bb.upperColor, bb.middleColor, bb.lowerColor); // 填充表单 $('#bbType').val(bb.type); $('#bbLength').val(bb.length); $('#bbUpperMultiplier').val(bb.upperMultiplier || 2); $('#bbLowerMultiplier').val(bb.lowerMultiplier || 2); $('#bbSource').val(bb.source); $('#bbLineWidth').val(bb.lineWidth || 2); $('#bbLineStyle').val(bb.lineStyle || 0); $('#bbUpperColor').val(bb.upperColor || '#ff6b6b'); $('#bbMiddleColor').val(bb.middleColor || '#667eea'); $('#bbLowerColor').val(bb.lowerColor || '#4ecdc4'); // 移除所有现有的点击事件,避免事件冲突 $('#bbConfigSubmitBtn').off('click'); // 修改按钮为更新模式 $('#bbConfigSubmitBtn').text('更新').on('click', (function(capturedBbId) { return function(e) { e.preventDefault(); e.stopPropagation(); console.log('🖱️ 点击了更新按钮,准备更新ID:', capturedBbId); console.log('🔍 捕获的ID类型:', typeof capturedBbId); updateBollingerBand(capturedBbId); }; })(bbId)); console.log('🔄 按钮已切换为更新模式'); $('#bbConfigModal').css('display', 'flex'); // 更新预览 setTimeout(updateBBLinePreview, 50); } // 更新布林带 function updateBollingerBand(bbId) { console.log('🔄 开始更新布林带,ID:', bbId, 'Type:', typeof bbId); console.log('📊 更新前布林带配置数量:', bollingerBands.length); console.log('📊 当前所有布林带:', bollingerBands.map(b => ({id: b.id, type: typeof b.id, bbType: b.type, length: b.length, upperMultiplier: b.upperMultiplier, lowerMultiplier: b.lowerMultiplier, upperColor: b.upperColor, middleColor: b.middleColor, lowerColor: b.lowerColor}))); const bb = bollingerBands.find(b => b.id == bbId); // 使用==而不是===来兼容类型转换 if (!bb) { console.error('❌ 未找到要更新的布林带配置,ID:', bbId); console.error('❌ 可用的布林带ID:', bollingerBands.map(b => b.id)); alert('错误:未找到要更新的布林带配置!'); return; } console.log('📝 找到要更新的布林带:', bb.type, bb.length, bb.upperMultiplier, bb.lowerMultiplier, bb.upperColor, bb.middleColor, bb.lowerColor); // 验证输入 const newLength = parseInt($('#bbLength').val()); const newUpperMultiplier = parseFloat($('#bbUpperMultiplier').val()); const newLowerMultiplier = parseFloat($('#bbLowerMultiplier').val()); if (newLength < 1) { alert('布林带长度必须大于0'); return; } if (newUpperMultiplier < 0.1) { alert('上轨倍数必须大于0.1'); return; } if (newLowerMultiplier < 0.1) { alert('下轨倍数必须大于0.1'); return; } // 记录更新前的配置 console.log('🔧 更新前配置:', { type: bb.type, length: bb.length, upperMultiplier: bb.upperMultiplier, lowerMultiplier: bb.lowerMultiplier, source: bb.source, colors: { upper: bb.upperColor, middle: bb.middleColor, lower: bb.lowerColor } }); // 更新配置 bb.type = $('#bbType').val(); bb.length = newLength; bb.upperMultiplier = newUpperMultiplier; bb.lowerMultiplier = newLowerMultiplier; bb.source = $('#bbSource').val(); bb.lineWidth = parseInt($('#bbLineWidth').val()); bb.lineStyle = parseInt($('#bbLineStyle').val()); bb.upperColor = $('#bbUpperColor').val(); bb.middleColor = $('#bbMiddleColor').val(); bb.lowerColor = $('#bbLowerColor').val(); // 记录更新后的配置 console.log('✅ 更新后配置:', { type: bb.type, length: bb.length, upperMultiplier: bb.upperMultiplier, lowerMultiplier: bb.lowerMultiplier, source: bb.source, colors: { upper: bb.upperColor, middle: bb.middleColor, lower: bb.lowerColor } }); console.log('📊 更新后布林带配置数量:', bollingerBands.length); hideBBConfig(); // 直接使用前端数据重新计算布林带 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addBollingerBandsToChart(candles); } // 刷新面板显示 try { updateIndicatorPanel(); } catch(e) {} // 刷新面板显示 try { updateIndicatorPanel(); } catch(e) {} } // 删除布林带 function deleteBB(bbId) { console.log('🗑️ 删除布林带,ID:', bbId, 'Type:', typeof bbId); console.log('📊 删除前布林带配置数量:', bollingerBands.length); const beforeCount = bollingerBands.length; bollingerBands = bollingerBands.filter(b => b.id != bbId); // 使用!=而不是!==来兼容类型转换 console.log('📊 删除后布林带配置数量:', bollingerBands.length); console.log('✅ 是否成功删除:', beforeCount > bollingerBands.length); // 直接使用前端数据重新计算布林带 if (tvWidget.mainChart && currentData && currentData.kline_data) { const candles = getCurrentCandleData(); addBollingerBandsToChart(candles); } // 刷新技术指标面板并关闭可能残留的配置弹窗 try { updateIndicatorPanel(); } catch(e) {} try { if ($('#bbConfigModal').is(':visible')) { hideBBConfig(); } } catch(e) {} } // 添加布林带到图表 function addBollingerBandsToChart() { if (window.App && window.App.Charts && typeof window.App.Charts.addBollingerBandsToChart === 'function') { return window.App.Charts.addBollingerBandsToChart.apply(null, arguments); } console.warn('addBollingerBandsToChart 未就绪'); } // 更新均线预览 function updateLinePreview() { // 检查是否在均线配置窗口 if ($('#maConfigModal').is(':visible')) { const color = $('#maColor').val(); const width = $('#maLineWidth').val(); const style = $('#maLineStyle').val(); const line = $('#previewLine'); line.attr('stroke', color); line.attr('stroke-width', width); // 设置线条样式 switch(parseInt(style)) { case 0: // 实线 line.attr('stroke-dasharray', 'none'); break; case 1: // 点线 line.attr('stroke-dasharray', '2,3'); break; case 2: // 虚线 line.attr('stroke-dasharray', '5,5'); break; case 3: // 大虚线 line.attr('stroke-dasharray', '10,5'); break; } } } // 更新布林带预览 function updateBBLinePreview() { const upperColor = $('#bbUpperColor').val(); const middleColor = $('#bbMiddleColor').val(); const lowerColor = $('#bbLowerColor').val(); const width = $('#bbLineWidth').val(); const style = $('#bbLineStyle').val(); const upperLine = $('#bbPreviewUpper'); const middleLine = $('#bbPreviewMiddle'); const lowerLine = $('#bbPreviewLower'); // 设置各条线的颜色 upperLine.attr('stroke', upperColor); middleLine.attr('stroke', middleColor); lowerLine.attr('stroke', lowerColor); // 设置线条宽度和样式 [upperLine, middleLine, lowerLine].forEach(line => { line.attr('stroke-width', width); // 设置线条样式 switch(parseInt(style)) { case 0: // 实线 line.attr('stroke-dasharray', 'none'); break; case 1: // 点线 line.attr('stroke-dasharray', '2,3'); break; case 2: // 虚线 line.attr('stroke-dasharray', '5,5'); break; case 3: // 大虚线 line.attr('stroke-dasharray', '10,5'); break; } }); } // 监听配置变化以更新预览 $(document).on('change', '#maColor, #maLineWidth, #maLineStyle', updateLinePreview); $(document).on('change', '#bbUpperColor, #bbMiddleColor, #bbLowerColor, #bbLineWidth, #bbLineStyle', updateBBLinePreview); // 点击弹窗外部关闭 $(document).on('click', '#bbConfigModal', function(e) { if (e.target === this) { hideBBConfig(); } }); // 初始化技术指标下拉菜单 function initIndicatorDropdown() { // 确保Bootstrap下拉菜单正常工作 try { // 如果Bootstrap没有正确加载,添加手动下拉菜单功能 $('#indicatorDropdown').off('click').on('click', function(e) { e.preventDefault(); const $menu = $(this).next('.dropdown-menu'); // 切换菜单显示状态 if ($menu.hasClass('show')) { $menu.removeClass('show'); } else { // 隐藏其他所有下拉菜单 $('.dropdown-menu').removeClass('show'); $menu.addClass('show'); } }); // 点击菜单外部关闭下拉菜单 $(document).on('click', function(e) { if (!$(e.target).closest('.indicator-dropdown').length) { $('.dropdown-menu').removeClass('show'); } }); console.log('✅ 技术指标下拉菜单初始化完成'); } catch (error) { console.error('❌ 技术指标下拉菜单初始化失败:', error); } } // 页面加载完成后初始化