将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务; 前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。 Co-authored-by: Cursor <cursoragent@cursor.com>
1246 lines
46 KiB
JavaScript
1246 lines
46 KiB
JavaScript
/* 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 += `
|
||
<div class="indicator-item" data-indicator-type="ma" data-indicator-id="${ma.id}" style="${itemStyle}">
|
||
<div class="indicator-info">
|
||
<div class="indicator-color-indicator" style="background-color: ${ma.color}"></div>
|
||
<span class="indicator-label">${ma.type}(${ma.length})${smoothText}${styleText}</span>
|
||
<span class="indicator-value">${ma.visible ? latestValue : '--'}</span>
|
||
</div>
|
||
<div class="indicator-actions">
|
||
<button class="indicator-action-btn" onclick="toggleMAVisibility('${ma.id}')" title="${ma.visible ? '隐藏' : '显示'}">
|
||
<i class="bi ${eyeIcon}"></i>
|
||
</button>
|
||
<button class="indicator-action-btn" onclick="configMA('${ma.id}')" title="配置">
|
||
<i class="bi bi-gear"></i>
|
||
</button>
|
||
<button class="indicator-action-btn" onclick="deleteMA('${ma.id}')" title="删除">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 += `
|
||
<div class="indicator-item" data-indicator-type="bb" data-indicator-id="${bb.id}" style="${itemStyle}">
|
||
<div class="indicator-info">
|
||
<div class="indicator-color-indicator" style="${colorIndicatorStyle}"></div>
|
||
<span class="indicator-label">BB(${bb.length}, ${bb.upperMultiplier || 2}, ${bb.lowerMultiplier || 2})${styleText}</span>
|
||
<span class="indicator-value">${bb.visible ? latestValue : '--'}</span>
|
||
</div>
|
||
<div class="indicator-actions">
|
||
<button class="indicator-action-btn" onclick="toggleBBVisibility('${bb.id}')" title="${bb.visible ? '隐藏' : '显示'}">
|
||
<i class="bi ${eyeIcon}"></i>
|
||
</button>
|
||
<button class="indicator-action-btn" onclick="configBB('${bb.id}')" title="配置">
|
||
<i class="bi bi-gear"></i>
|
||
</button>
|
||
<button class="indicator-action-btn" onclick="deleteBB('${bb.id}')" title="删除">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后初始化
|