+
@@ -891,6 +902,10 @@
let movingAverages = [];
let maCounter = 0;
+ // 布林带系统相关变量
+ let bollingerBands = [];
+ let bbCounter = 0;
+
// 均线计算函数
function calculateMovingAverage(data, period, type = 'SMA') {
if (!data || data.length < period) return [];
@@ -1007,6 +1022,39 @@
return calculateMovingAverage(data, smoothingPeriod, smoothingType);
}
+ // 布林带计算函数
+ function calculateBollingerBands(data, period, multiplier = 2) {
+ if (!data || data.length < period) return { middle: [], upper: [], lower: [] };
+
+ const middle = [];
+ const upper = [];
+ const lower = [];
+
+ for (let i = period - 1; i < data.length; i++) {
+ // 计算移动平均线(中线)
+ const sum = data.slice(i - period + 1, i + 1).reduce((acc, val) => acc + val.value, 0);
+ const ma = sum / period;
+
+ // 计算标准差
+ const variance = data.slice(i - period + 1, i + 1).reduce((acc, val) => {
+ return acc + Math.pow(val.value - ma, 2);
+ }, 0) / period;
+ const stdDev = Math.sqrt(variance);
+
+ // 计算上下轨
+ const upperValue = ma + (multiplier * stdDev);
+ const lowerValue = ma - (multiplier * stdDev);
+
+ const timestamp = data[i].time;
+
+ middle.push({ time: timestamp, value: ma });
+ upper.push({ time: timestamp, value: upperValue });
+ lower.push({ time: timestamp, value: lowerValue });
+ }
+
+ return { middle, upper, lower };
+ }
+
// 添加均线到图表
function addMovingAverageToChart(config) {
const ma = {
@@ -1108,19 +1156,20 @@
// 更新均线指标显示
function updateMovingAverageIndicators() {
- console.log('更新均线指标显示,当前均线数量:', movingAverages.length);
+ console.log('更新指标显示,当前均线数量:', movingAverages.length, '布林带数量:', bollingerBands.length);
try {
const container = $('#movingAverageIndicators');
const list = $('#maIndicatorsList');
- if (movingAverages.length === 0) {
+ if (movingAverages.length === 0 && bollingerBands.length === 0) {
container.hide();
- console.log('没有均线,隐藏指标容器');
+ console.log('没有指标,隐藏指标容器');
return;
}
list.empty();
+ // 显示均线
movingAverages.forEach((ma, index) => {
console.log(`创建均线指标 ${index + 1}:`, ma.id, ma.type, ma.period);
const latestValue = ma.data && ma.data.length > 0 ? ma.data[ma.data.length - 1].value : 0;
@@ -1148,10 +1197,47 @@
list.append(indicator);
});
+ // 显示布林带
+ bollingerBands.forEach((bb, index) => {
+ console.log(`创建布林带指标 ${index + 1}:`, bb.id, bb.period, bb.multiplier);
+ const upperValue = bb.data && bb.data.upper && bb.data.upper.length > 0 ? bb.data.upper[bb.data.upper.length - 1].value : 0;
+ const middleValue = bb.data && bb.data.middle && bb.data.middle.length > 0 ? bb.data.middle[bb.data.middle.length - 1].value : 0;
+ const lowerValue = bb.data && bb.data.lower && bb.data.lower.length > 0 ? bb.data.lower[bb.data.lower.length - 1].value : 0;
+
+ const indicator = $(`
+
+
+
BOLL(${bb.period},${bb.multiplier})
+
+ 上: ${upperValue.toFixed(2)}
+ 中: ${middleValue.toFixed(2)}
+ 下: ${lowerValue.toFixed(2)}
+
+
+
+
+
+
+
+ `);
+
+ list.append(indicator);
+ });
+
container.show();
- console.log('均线指标显示更新完成');
+ console.log('指标显示更新完成');
} catch (error) {
- console.error('更新均线指标显示时出错:', error);
+ console.error('更新指标显示时出错:', error);
}
}
@@ -1258,10 +1344,260 @@
updateMovingAverageIndicators();
}
+ // 添加布林带到图表
+ function addBollingerBandToChart(config) {
+ const bb = {
+ id: `bb_${++bbCounter}`,
+ ...config,
+ series: {
+ upper: null,
+ middle: null,
+ lower: null
+ },
+ visible: true
+ };
+
+ if (!currentData) {
+ console.error('没有数据,无法添加布林带');
+ return null;
+ }
+
+ // 获取价格数据
+ const priceData = getPriceData(null, config.source);
+
+ // 计算布林带
+ const bbData = calculateBollingerBands(priceData, config.period, config.multiplier);
+
+ // 添加到图表
+ const upperSeries = tvWidget.mainChart.addLineSeries({
+ color: config.upperColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL上轨(${config.period},${config.multiplier})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ const middleSeries = tvWidget.mainChart.addLineSeries({
+ color: config.middleColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL中轨(${config.period})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ const lowerSeries = tvWidget.mainChart.addLineSeries({
+ color: config.lowerColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL下轨(${config.period},${config.multiplier})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ upperSeries.setData(bbData.upper);
+ middleSeries.setData(bbData.middle);
+ lowerSeries.setData(bbData.lower);
+
+ bb.series.upper = upperSeries;
+ bb.series.middle = middleSeries;
+ bb.series.lower = lowerSeries;
+ bb.data = bbData;
+
+ bollingerBands.push(bb);
+ tvWidget.series.movingAverageSeries.push(upperSeries, middleSeries, lowerSeries);
+
+ // 更新显示
+ updateMovingAverageIndicators();
+
+ return bb;
+ }
+
+ // 删除布林带
+ function removeBollingerBand(bbId) {
+ console.log('删除布林带:', bbId);
+ try {
+ const index = bollingerBands.findIndex(bb => bb.id === bbId);
+ if (index !== -1) {
+ const bb = bollingerBands[index];
+ console.log('找到要删除的布林带:', bb);
+
+ if (bb.series && tvWidget.mainChart) {
+ // 删除三条线
+ tvWidget.mainChart.removeSeries(bb.series.upper);
+ tvWidget.mainChart.removeSeries(bb.series.middle);
+ tvWidget.mainChart.removeSeries(bb.series.lower);
+
+ // 从数组中移除
+ const removeFromArray = (arr, series) => {
+ const seriesIndex = arr.indexOf(series);
+ if (seriesIndex !== -1) {
+ arr.splice(seriesIndex, 1);
+ }
+ };
+
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.upper);
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.middle);
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.lower);
+ }
+ bollingerBands.splice(index, 1);
+ updateMovingAverageIndicators();
+ console.log('布林带删除成功');
+ } else {
+ console.warn('未找到要删除的布林带:', bbId);
+ }
+ } catch (error) {
+ console.error('删除布林带时出错:', error);
+ }
+ }
+
+ // 切换布林带显示/隐藏
+ function toggleBollingerBand(bbId) {
+ console.log('切换布林带显示状态:', bbId);
+ try {
+ const bb = bollingerBands.find(bb => bb.id === bbId);
+ if (bb && bb.series) {
+ bb.visible = !bb.visible;
+ console.log('布林带新状态:', bb.visible ? '显示' : '隐藏');
+ bb.series.upper.applyOptions({ visible: bb.visible });
+ bb.series.middle.applyOptions({ visible: bb.visible });
+ bb.series.lower.applyOptions({ visible: bb.visible });
+ updateMovingAverageIndicators();
+ console.log('布林带状态切换成功');
+ } else {
+ console.warn('未找到布林带或series不存在:', bbId, bb);
+ }
+ } catch (error) {
+ console.error('切换布林带显示状态时出错:', error);
+ }
+ }
+
+ // 编辑布林带配置
+ function editBollingerBand(bbId) {
+ console.log('编辑布林带配置:', bbId);
+ try {
+ const bb = bollingerBands.find(bb => bb.id === bbId);
+ if (!bb) {
+ console.warn('未找到要编辑的布林带:', bbId);
+ return;
+ }
+
+ console.log('找到布林带配置:', bb);
+
+ // 填充表单
+ $('#bbPeriod').val(bb.period);
+ $('#bbMultiplier').val(bb.multiplier);
+ $('#bbSource').val(bb.source);
+ $('#bbUpperColor').val(bb.upperColor);
+ $('#bbMiddleColor').val(bb.middleColor);
+ $('#bbLowerColor').val(bb.lowerColor);
+ $('#bbLineWidth').val(bb.lineWidth);
+ $('#bbLineWidthDisplay').text(bb.lineWidth);
+ $('#bbStyle').val(bb.style);
+
+ // 更新确认按钮
+ $('#addBollingerBandConfirm').text('更新布林带').data('editId', bbId);
+
+ // 显示对话框
+ const modal = new bootstrap.Modal(document.getElementById('bollingerBandModal'));
+ modal.show();
+ console.log('编辑对话框已显示');
+ } catch (error) {
+ console.error('编辑布林带配置时出错:', error);
+ }
+ }
+
+ // 更新所有布林带数据
+ function updateAllBollingerBands() {
+ if (!currentData) return;
+
+ console.log('更新所有布林带数据,布林带数量:', bollingerBands.length);
+
+ bollingerBands.forEach(bb => {
+ const priceData = getPriceData(null, bb.source);
+
+ if (priceData.length === 0) {
+ console.warn('布林带数据为空,跳过:', bb.id);
+ return;
+ }
+
+ const bbData = calculateBollingerBands(priceData, bb.period, bb.multiplier);
+ bb.data = bbData;
+
+ // 如果序列不存在或无效,重新创建
+ if ((!bb.series.upper || !bb.series.middle || !bb.series.lower) && tvWidget.mainChart) {
+ console.log('重新创建布林带序列:', bb.period, bb.multiplier);
+ try {
+ bb.series.upper = tvWidget.mainChart.addLineSeries({
+ color: bb.upperColor,
+ lineWidth: bb.lineWidth,
+ lineStyle: bb.style === 'solid' ? 0 : bb.style === 'dotted' ? 1 : bb.style === 'dashed' ? 2 : 0,
+ title: `BOLL上轨(${bb.period},${bb.multiplier})`,
+ visible: bb.visible !== false,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ bb.series.middle = tvWidget.mainChart.addLineSeries({
+ color: bb.middleColor,
+ lineWidth: bb.lineWidth,
+ lineStyle: bb.style === 'solid' ? 0 : bb.style === 'dotted' ? 1 : bb.style === 'dashed' ? 2 : 0,
+ title: `BOLL中轨(${bb.period})`,
+ visible: bb.visible !== false,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ bb.series.lower = tvWidget.mainChart.addLineSeries({
+ color: bb.lowerColor,
+ lineWidth: bb.lineWidth,
+ lineStyle: bb.style === 'solid' ? 0 : bb.style === 'dotted' ? 1 : bb.style === 'dashed' ? 2 : 0,
+ title: `BOLL下轨(${bb.period},${bb.multiplier})`,
+ visible: bb.visible !== false,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ if (!tvWidget.series.movingAverageSeries) {
+ tvWidget.series.movingAverageSeries = [];
+ }
+ tvWidget.series.movingAverageSeries.push(bb.series.upper, bb.series.middle, bb.series.lower);
+ console.log('布林带序列创建成功:', bb.id);
+ } catch (error) {
+ console.error('创建布林带序列失败:', error);
+ return;
+ }
+ } else if (!tvWidget.mainChart) {
+ console.warn('主图表不存在,跳过布林带更新:', bb.id);
+ return;
+ }
+
+ // 设置数据
+ if (bb.series.upper && bb.series.middle && bb.series.lower) {
+ bb.series.upper.setData(bbData.upper);
+ bb.series.middle.setData(bbData.middle);
+ bb.series.lower.setData(bbData.lower);
+ console.log(`布林带 BOLL(${bb.period},${bb.multiplier}) 数据已更新,数据点数:`, bbData.upper.length);
+ }
+ });
+
+ updateMovingAverageIndicators();
+ }
+
// 确保函数在全局作用域中可用
window.removeMovingAverage = removeMovingAverage;
window.toggleMovingAverage = toggleMovingAverage;
window.editMovingAverage = editMovingAverage;
+ window.removeBollingerBand = removeBollingerBand;
+ window.toggleBollingerBand = toggleBollingerBand;
+ window.editBollingerBand = editBollingerBand;
// 测试函数 - 用于验证均线系统是否工作
window.testMovingAverageSystem = function() {
@@ -1358,6 +1694,28 @@
modal.show();
});
+ // 布林带按钮点击事件
+ $('#addBollingerBandBtn').click(function() {
+ // 重置表单
+ $('#bollingerBandForm')[0].reset();
+ $('#bbPeriod').val(20);
+ $('#bbMultiplier').val(2);
+ $('#bbSource').val('close');
+ $('#bbUpperColor').val('#FF6B6B');
+ $('#bbMiddleColor').val('#4ECDC4');
+ $('#bbLowerColor').val('#45B7D1');
+ $('#bbLineWidth').val(1);
+ $('#bbLineWidthDisplay').text('1');
+ $('#bbStyle').val('solid');
+
+ // 重置确认按钮
+ $('#addBollingerBandConfirm').text('添加布林带').removeData('editId');
+
+ // 显示对话框
+ const modal = new bootstrap.Modal(document.getElementById('bollingerBandModal'));
+ modal.show();
+ });
+
// 平滑算法变更事件
$('#maSmoothing').change(function() {
const smoothing = $(this).val();
@@ -1369,6 +1727,11 @@
$('#lineWidthDisplay').text($(this).val());
});
+ // 布林带线宽滑块变更事件
+ $('#bbLineWidth').on('input', function() {
+ $('#bbLineWidthDisplay').text($(this).val());
+ });
+
// 确认添加/更新均线
$('#addMovingAverageConfirm').click(function() {
const form = $('#movingAverageForm')[0];
@@ -1440,6 +1803,101 @@
const modal = bootstrap.Modal.getInstance(document.getElementById('movingAverageModal'));
modal.hide();
});
+
+ // 确认添加/更新布林带
+ $('#addBollingerBandConfirm').click(function() {
+ const editId = $(this).data('editId');
+ const config = {
+ period: parseInt($('#bbPeriod').val()),
+ multiplier: parseFloat($('#bbMultiplier').val()),
+ source: $('#bbSource').val(),
+ upperColor: $('#bbUpperColor').val(),
+ middleColor: $('#bbMiddleColor').val(),
+ lowerColor: $('#bbLowerColor').val(),
+ lineWidth: parseInt($('#bbLineWidth').val()),
+ style: $('#bbStyle').val()
+ };
+
+ if (editId) {
+ // 更新现有布林带
+ const bb = bollingerBands.find(bb => bb.id === editId);
+ if (bb) {
+ // 删除旧系列
+ if (bb.series.upper && bb.series.middle && bb.series.lower) {
+ tvWidget.mainChart.removeSeries(bb.series.upper);
+ tvWidget.mainChart.removeSeries(bb.series.middle);
+ tvWidget.mainChart.removeSeries(bb.series.lower);
+
+ const removeFromArray = (arr, series) => {
+ const seriesIndex = arr.indexOf(series);
+ if (seriesIndex !== -1) {
+ arr.splice(seriesIndex, 1);
+ }
+ };
+
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.upper);
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.middle);
+ removeFromArray(tvWidget.series.movingAverageSeries, bb.series.lower);
+ }
+
+ // 更新配置
+ Object.assign(bb, config);
+
+ // 重新计算和添加
+ const priceData = getPriceData(null, config.source);
+ const bbData = calculateBollingerBands(priceData, config.period, config.multiplier);
+
+ const upperSeries = tvWidget.mainChart.addLineSeries({
+ color: config.upperColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL上轨(${config.period},${config.multiplier})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ const middleSeries = tvWidget.mainChart.addLineSeries({
+ color: config.middleColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL中轨(${config.period})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ const lowerSeries = tvWidget.mainChart.addLineSeries({
+ color: config.lowerColor,
+ lineWidth: config.lineWidth,
+ lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
+ title: `BOLL下轨(${config.period},${config.multiplier})`,
+ lastValueVisible: false,
+ priceLineVisible: false,
+ crosshairMarkerVisible: true
+ });
+
+ upperSeries.setData(bbData.upper);
+ middleSeries.setData(bbData.middle);
+ lowerSeries.setData(bbData.lower);
+
+ bb.series.upper = upperSeries;
+ bb.series.middle = middleSeries;
+ bb.series.lower = lowerSeries;
+ bb.data = bbData;
+
+ tvWidget.series.movingAverageSeries.push(upperSeries, middleSeries, lowerSeries);
+ updateMovingAverageIndicators();
+ }
+ } else {
+ // 添加新布林带
+ addBollingerBandToChart(config);
+ }
+
+ // 关闭对话框
+ const modal = bootstrap.Modal.getInstance(document.getElementById('bollingerBandModal'));
+ modal.hide();
+ });
}
// 买卖点样式定义 - 根据desc字段直接显示
@@ -2063,6 +2521,16 @@
});
console.log('已清空均线序列引用,配置保留:', movingAverages.length);
+ // 清空所有布林带的序列引用
+ bollingerBands.forEach(bb => {
+ bb.series = {
+ upper: null,
+ middle: null,
+ lower: null
+ };
+ });
+ console.log('已清空布林带序列引用,配置保留:', bollingerBands.length);
+
tvWidget = {
mainChart: null,
volumeChart: null,
@@ -4385,6 +4853,14 @@
}, 200);
}
+ // 如果有布林带配置,用新数据重新计算和显示布林带
+ if (bollingerBands.length > 0) {
+ console.log('图表重新初始化后,更新布林带数据,布林带数量:', bollingerBands.length);
+ setTimeout(() => {
+ updateAllBollingerBands();
+ }, 200);
+ }
+
// 窗口大小变化时重绘图表
window.addEventListener('resize', () => {
// 调整主图大小
@@ -6870,5 +7346,94 @@