添加均线可以用了
This commit is contained in:
+754
-34
@@ -286,6 +286,72 @@
|
||||
}
|
||||
|
||||
/* 主次周期元素样式区分 */
|
||||
|
||||
/* 均线指标样式 */
|
||||
.ma-indicators {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
max-width: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.ma-indicator-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.ma-indicator-color {
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.ma-indicator-controls {
|
||||
display: flex;
|
||||
margin-left: 4px;
|
||||
gap: 2px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.ma-indicator-item:hover .ma-indicator-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ma-control-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.ma-control-btn:hover {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ma-indicator-value {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -415,6 +481,14 @@
|
||||
<input class="form-check-input" type="checkbox" id="showMainBollinger">
|
||||
<label class="form-check-label" for="showMainBollinger">布林带</label>
|
||||
</div>
|
||||
<div class="d-inline-block ms-3">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="addMovingAverageBtn" title="添加均线">
|
||||
<i class="bi bi-graph-up"></i> 均线
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="window.testMovingAverageSystem()" title="测试均线系统">
|
||||
<i class="bi bi-bug"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mt-2">
|
||||
<label class="form-label me-3 mb-0">次周期:</label>
|
||||
@@ -524,6 +598,13 @@
|
||||
<div id="loadingIndicator" style="display:none;"></div>
|
||||
|
||||
<div class="chart-container">
|
||||
<!-- 均线显示区域 -->
|
||||
<div id="movingAverageIndicators" class="ma-indicators" style="display: none;">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<span class="text-muted small">均线:</span>
|
||||
<div id="maIndicatorsList" class="d-flex flex-wrap gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tradingview_chart"></div>
|
||||
</div>
|
||||
|
||||
@@ -778,7 +859,8 @@
|
||||
elementUncompletedZsSeries: [],
|
||||
tradePointSeries: [],
|
||||
mainBollingerSeries: [],
|
||||
elementBollingerSeries: []
|
||||
elementBollingerSeries: [],
|
||||
movingAverageSeries: []
|
||||
},
|
||||
state: {
|
||||
isInitialized: false,
|
||||
@@ -787,6 +869,506 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 均线系统相关变量
|
||||
let movingAverages = [];
|
||||
let maCounter = 0;
|
||||
|
||||
// 均线计算函数
|
||||
function calculateMovingAverage(data, period, type = 'SMA') {
|
||||
if (!data || data.length < period) return [];
|
||||
|
||||
const result = [];
|
||||
|
||||
if (type === 'SMA') {
|
||||
// 简单移动平均线
|
||||
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);
|
||||
result.push({
|
||||
time: data[i].time,
|
||||
value: sum / period
|
||||
});
|
||||
}
|
||||
} else if (type === 'EMA') {
|
||||
// 指数移动平均线
|
||||
const multiplier = 2 / (period + 1);
|
||||
let ema = data.slice(0, period).reduce((acc, val) => acc + val.value, 0) / period;
|
||||
|
||||
result.push({
|
||||
time: data[period - 1].time,
|
||||
value: ema
|
||||
});
|
||||
|
||||
for (let i = period; i < data.length; i++) {
|
||||
ema = (data[i].value * multiplier) + (ema * (1 - multiplier));
|
||||
result.push({
|
||||
time: data[i].time,
|
||||
value: ema
|
||||
});
|
||||
}
|
||||
} else if (type === 'WMA') {
|
||||
// 加权移动平均线
|
||||
const weights = [];
|
||||
let weightSum = 0;
|
||||
for (let i = 1; i <= period; i++) {
|
||||
weights.push(i);
|
||||
weightSum += i;
|
||||
}
|
||||
|
||||
for (let i = period - 1; i < data.length; i++) {
|
||||
let weightedSum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
weightedSum += data[i - period + 1 + j].value * weights[j];
|
||||
}
|
||||
result.push({
|
||||
time: data[i].time,
|
||||
value: weightedSum / weightSum
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取价格数据源
|
||||
function getPriceData(klineData, source) {
|
||||
// 如果没有传入K线数据,使用当前数据源
|
||||
if (!klineData) {
|
||||
// 检查是否使用小周期K线数据
|
||||
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||
currentData.element_kline_data &&
|
||||
Array.isArray(currentData.element_kline_data);
|
||||
|
||||
console.log('均线使用数据源:', useElementPeriod ? '小周期' : '主周期');
|
||||
klineData = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
}
|
||||
|
||||
if (!klineData || !Array.isArray(klineData)) {
|
||||
console.warn('K线数据无效:', klineData);
|
||||
return [];
|
||||
}
|
||||
|
||||
return klineData.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
let value;
|
||||
|
||||
switch (source) {
|
||||
case 'open':
|
||||
value = parseFloat(kline.open);
|
||||
break;
|
||||
case 'high':
|
||||
value = parseFloat(kline.high);
|
||||
break;
|
||||
case 'low':
|
||||
value = parseFloat(kline.low);
|
||||
break;
|
||||
case 'close':
|
||||
value = parseFloat(kline.close);
|
||||
break;
|
||||
case 'hl2':
|
||||
value = (parseFloat(kline.high) + parseFloat(kline.low)) / 2;
|
||||
break;
|
||||
case 'hlc3':
|
||||
value = (parseFloat(kline.high) + parseFloat(kline.low) + parseFloat(kline.close)) / 3;
|
||||
break;
|
||||
case 'ohlc4':
|
||||
value = (parseFloat(kline.open) + parseFloat(kline.high) + parseFloat(kline.low) + parseFloat(kline.close)) / 4;
|
||||
break;
|
||||
default:
|
||||
value = parseFloat(kline.close);
|
||||
}
|
||||
|
||||
return { time: timestamp, value: value };
|
||||
});
|
||||
}
|
||||
|
||||
// 应用平滑处理
|
||||
function applySmoothing(data, smoothingType, smoothingPeriod) {
|
||||
if (smoothingType === 'none' || !smoothingPeriod || smoothingPeriod < 2) {
|
||||
return data;
|
||||
}
|
||||
return calculateMovingAverage(data, smoothingPeriod, smoothingType);
|
||||
}
|
||||
|
||||
// 添加均线到图表
|
||||
function addMovingAverageToChart(config) {
|
||||
const ma = {
|
||||
id: `ma_${++maCounter}`,
|
||||
...config,
|
||||
series: null,
|
||||
visible: true
|
||||
};
|
||||
|
||||
if (!currentData) {
|
||||
console.error('没有数据,无法添加均线');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取价格数据(自动选择数据源)
|
||||
const priceData = getPriceData(null, config.source);
|
||||
|
||||
// 计算均线
|
||||
let maData = calculateMovingAverage(priceData, config.period, config.type);
|
||||
|
||||
// 应用平滑处理
|
||||
if (config.smoothing !== 'none' && config.smoothingPeriod > 1) {
|
||||
maData = applySmoothing(maData, config.smoothing, config.smoothingPeriod);
|
||||
}
|
||||
|
||||
// 添加到图表
|
||||
const lineSeries = tvWidget.mainChart.addLineSeries({
|
||||
color: config.color,
|
||||
lineWidth: config.lineWidth,
|
||||
lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
|
||||
title: `${config.type}(${config.period})`,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: true
|
||||
});
|
||||
|
||||
lineSeries.setData(maData);
|
||||
ma.series = lineSeries;
|
||||
ma.data = maData;
|
||||
|
||||
movingAverages.push(ma);
|
||||
tvWidget.series.movingAverageSeries.push(lineSeries);
|
||||
|
||||
// 更新显示
|
||||
updateMovingAverageIndicators();
|
||||
|
||||
return ma;
|
||||
}
|
||||
|
||||
// 删除均线
|
||||
function removeMovingAverage(maId) {
|
||||
console.log('删除均线:', maId);
|
||||
try {
|
||||
const index = movingAverages.findIndex(ma => ma.id === maId);
|
||||
if (index !== -1) {
|
||||
const ma = movingAverages[index];
|
||||
console.log('找到要删除的均线:', ma);
|
||||
|
||||
if (ma.series && tvWidget.mainChart) {
|
||||
tvWidget.mainChart.removeSeries(ma.series);
|
||||
|
||||
// 从 tvWidget.series.movingAverageSeries 中移除
|
||||
const seriesIndex = tvWidget.series.movingAverageSeries.indexOf(ma.series);
|
||||
if (seriesIndex !== -1) {
|
||||
tvWidget.series.movingAverageSeries.splice(seriesIndex, 1);
|
||||
}
|
||||
}
|
||||
movingAverages.splice(index, 1);
|
||||
updateMovingAverageIndicators();
|
||||
console.log('均线删除成功');
|
||||
} else {
|
||||
console.warn('未找到要删除的均线:', maId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除均线时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换均线显示/隐藏
|
||||
function toggleMovingAverage(maId) {
|
||||
console.log('切换均线显示状态:', maId);
|
||||
try {
|
||||
const ma = movingAverages.find(ma => ma.id === maId);
|
||||
if (ma && ma.series) {
|
||||
ma.visible = !ma.visible;
|
||||
console.log('均线新状态:', ma.visible ? '显示' : '隐藏');
|
||||
ma.series.applyOptions({
|
||||
visible: ma.visible
|
||||
});
|
||||
updateMovingAverageIndicators();
|
||||
console.log('均线状态切换成功');
|
||||
} else {
|
||||
console.warn('未找到均线或series不存在:', maId, ma);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换均线显示状态时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新均线指标显示
|
||||
function updateMovingAverageIndicators() {
|
||||
console.log('更新均线指标显示,当前均线数量:', movingAverages.length);
|
||||
try {
|
||||
const container = $('#movingAverageIndicators');
|
||||
const list = $('#maIndicatorsList');
|
||||
|
||||
if (movingAverages.length === 0) {
|
||||
container.hide();
|
||||
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;
|
||||
const smoothingText = ma.smoothing !== 'none' ? `/${ma.smoothing}(${ma.smoothingPeriod})` : '';
|
||||
|
||||
const indicator = $(`
|
||||
<div class="ma-indicator-item" data-ma-id="${ma.id}" style="opacity: ${ma.visible ? 1 : 0.5}">
|
||||
<div class="ma-indicator-color" style="background-color: ${ma.color}"></div>
|
||||
<span>${ma.type}(${ma.period})${smoothingText}</span>
|
||||
<span class="ma-indicator-value" style="color: ${ma.color}">${latestValue.toFixed(2)}</span>
|
||||
<div class="ma-indicator-controls">
|
||||
<button class="ma-control-btn" onclick="window.toggleMovingAverage('${ma.id}')" title="${ma.visible ? '隐藏' : '显示'}">
|
||||
<i class="bi ${ma.visible ? 'bi-eye-slash' : 'bi-eye'}"></i>
|
||||
</button>
|
||||
<button class="ma-control-btn" onclick="window.editMovingAverage('${ma.id}')" title="配置">
|
||||
<i class="bi bi-gear"></i>
|
||||
</button>
|
||||
<button class="ma-control-btn" onclick="window.removeMovingAverage('${ma.id}')" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
list.append(indicator);
|
||||
});
|
||||
|
||||
container.show();
|
||||
console.log('均线指标显示更新完成');
|
||||
} catch (error) {
|
||||
console.error('更新均线指标显示时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑均线配置
|
||||
function editMovingAverage(maId) {
|
||||
console.log('编辑均线配置:', maId);
|
||||
try {
|
||||
const ma = movingAverages.find(ma => ma.id === maId);
|
||||
if (!ma) {
|
||||
console.warn('未找到要编辑的均线:', maId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('找到均线配置:', ma);
|
||||
|
||||
// 填充表单
|
||||
$('#maType').val(ma.type);
|
||||
$('#maPeriod').val(ma.period);
|
||||
$('#maSource').val(ma.source);
|
||||
$('#maColor').val(ma.color);
|
||||
$('#maSmoothing').val(ma.smoothing);
|
||||
$('#maSmoothingPeriod').val(ma.smoothingPeriod);
|
||||
$('#maLineWidth').val(ma.lineWidth);
|
||||
$('#lineWidthDisplay').text(ma.lineWidth);
|
||||
$('#maStyle').val(ma.style);
|
||||
|
||||
// 更新确认按钮
|
||||
$('#addMovingAverageConfirm').text('更新均线').data('editId', maId);
|
||||
|
||||
// 启用/禁用平滑周期
|
||||
$('#maSmoothingPeriod').prop('disabled', ma.smoothing === 'none');
|
||||
|
||||
// 显示对话框
|
||||
const modal = new bootstrap.Modal(document.getElementById('movingAverageModal'));
|
||||
modal.show();
|
||||
console.log('编辑对话框已显示');
|
||||
} catch (error) {
|
||||
console.error('编辑均线配置时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新所有均线数据
|
||||
function updateAllMovingAverages() {
|
||||
if (!currentData) return;
|
||||
|
||||
console.log('更新所有均线数据,均线数量:', movingAverages.length);
|
||||
|
||||
movingAverages.forEach(ma => {
|
||||
// 自动选择数据源(主周期或次周期)
|
||||
const priceData = getPriceData(null, ma.source);
|
||||
|
||||
if (priceData.length === 0) {
|
||||
console.warn('均线数据为空,跳过:', ma.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let maData = calculateMovingAverage(priceData, ma.period, ma.type);
|
||||
|
||||
if (ma.smoothing !== 'none' && ma.smoothingPeriod > 1) {
|
||||
maData = applySmoothing(maData, ma.smoothing, ma.smoothingPeriod);
|
||||
}
|
||||
|
||||
ma.data = maData;
|
||||
|
||||
// 如果序列不存在或无效,重新创建
|
||||
if (!ma.series && tvWidget.mainChart) {
|
||||
console.log('重新创建均线序列:', ma.type, ma.period);
|
||||
try {
|
||||
ma.series = tvWidget.mainChart.addLineSeries({
|
||||
color: ma.color,
|
||||
lineWidth: ma.lineWidth,
|
||||
lineStyle: ma.style === 'solid' ? 0 :
|
||||
ma.style === 'dotted' ? 1 :
|
||||
ma.style === 'dashed' ? 2 : 0,
|
||||
title: `${ma.type}(${ma.period})`,
|
||||
visible: ma.visible !== false,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: true
|
||||
});
|
||||
|
||||
// 添加到movingAverageSeries数组
|
||||
if (!tvWidget.series.movingAverageSeries) {
|
||||
tvWidget.series.movingAverageSeries = [];
|
||||
}
|
||||
tvWidget.series.movingAverageSeries.push(ma.series);
|
||||
console.log('均线序列创建成功:', ma.id);
|
||||
} catch (error) {
|
||||
console.error('创建均线序列失败:', error);
|
||||
return;
|
||||
}
|
||||
} else if (!tvWidget.mainChart) {
|
||||
console.warn('主图表不存在,跳过均线更新:', ma.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置数据
|
||||
if (ma.series) {
|
||||
ma.series.setData(maData);
|
||||
console.log(`均线 ${ma.type}(${ma.period}) 数据已更新,数据点数:`, maData.length);
|
||||
}
|
||||
});
|
||||
|
||||
updateMovingAverageIndicators();
|
||||
}
|
||||
|
||||
// 确保函数在全局作用域中可用
|
||||
window.removeMovingAverage = removeMovingAverage;
|
||||
window.toggleMovingAverage = toggleMovingAverage;
|
||||
window.editMovingAverage = editMovingAverage;
|
||||
|
||||
// 测试函数 - 用于验证均线系统是否工作
|
||||
window.testMovingAverageSystem = function() {
|
||||
console.log('=== 均线系统测试 ===');
|
||||
console.log('当前均线数量:', movingAverages.length);
|
||||
console.log('均线列表:', movingAverages);
|
||||
console.log('图表对象:', tvWidget);
|
||||
console.log('主图表:', tvWidget.mainChart);
|
||||
console.log('=================');
|
||||
|
||||
if (movingAverages.length === 0) {
|
||||
alert('当前没有添加任何均线。请先添加一个均线来测试。');
|
||||
} else {
|
||||
alert(`均线系统正常,当前有 ${movingAverages.length} 条均线。请检查浏览器控制台查看详细信息。`);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化均线系统事件处理程序
|
||||
function initMovingAverageEvents() {
|
||||
// 均线按钮点击事件
|
||||
$('#addMovingAverageBtn').click(function() {
|
||||
// 重置表单
|
||||
$('#movingAverageForm')[0].reset();
|
||||
$('#maType').val('SMA');
|
||||
$('#maPeriod').val(20);
|
||||
$('#maSource').val('close');
|
||||
$('#maColor').val('#2962FF');
|
||||
$('#maSmoothing').val('none');
|
||||
$('#maSmoothingPeriod').val(3).prop('disabled', true);
|
||||
$('#maLineWidth').val(2);
|
||||
$('#lineWidthDisplay').text('2');
|
||||
$('#maStyle').val('solid');
|
||||
|
||||
// 重置确认按钮
|
||||
$('#addMovingAverageConfirm').text('添加均线').removeData('editId');
|
||||
|
||||
// 显示对话框
|
||||
const modal = new bootstrap.Modal(document.getElementById('movingAverageModal'));
|
||||
modal.show();
|
||||
});
|
||||
|
||||
// 平滑算法变更事件
|
||||
$('#maSmoothing').change(function() {
|
||||
const smoothing = $(this).val();
|
||||
$('#maSmoothingPeriod').prop('disabled', smoothing === 'none');
|
||||
});
|
||||
|
||||
// 线宽滑块变更事件
|
||||
$('#maLineWidth').on('input', function() {
|
||||
$('#lineWidthDisplay').text($(this).val());
|
||||
});
|
||||
|
||||
// 确认添加/更新均线
|
||||
$('#addMovingAverageConfirm').click(function() {
|
||||
const form = $('#movingAverageForm')[0];
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
const editId = $(this).data('editId');
|
||||
const config = {
|
||||
type: $('#maType').val(),
|
||||
period: parseInt($('#maPeriod').val()),
|
||||
source: $('#maSource').val(),
|
||||
color: $('#maColor').val(),
|
||||
smoothing: $('#maSmoothing').val(),
|
||||
smoothingPeriod: parseInt($('#maSmoothingPeriod').val()) || 3,
|
||||
lineWidth: parseInt($('#maLineWidth').val()),
|
||||
style: $('#maStyle').val()
|
||||
};
|
||||
|
||||
if (editId) {
|
||||
// 更新现有均线
|
||||
const ma = movingAverages.find(ma => ma.id === editId);
|
||||
if (ma) {
|
||||
// 删除旧系列
|
||||
if (ma.series) {
|
||||
tvWidget.mainChart.removeSeries(ma.series);
|
||||
const seriesIndex = tvWidget.series.movingAverageSeries.indexOf(ma.series);
|
||||
if (seriesIndex !== -1) {
|
||||
tvWidget.series.movingAverageSeries.splice(seriesIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
Object.assign(ma, config);
|
||||
|
||||
// 重新计算和添加
|
||||
const klineData = currentData.kline_data;
|
||||
const priceData = getPriceData(klineData, config.source);
|
||||
let maData = calculateMovingAverage(priceData, config.period, config.type);
|
||||
|
||||
if (config.smoothing !== 'none' && config.smoothingPeriod > 1) {
|
||||
maData = applySmoothing(maData, config.smoothing, config.smoothingPeriod);
|
||||
}
|
||||
|
||||
const lineSeries = tvWidget.mainChart.addLineSeries({
|
||||
color: config.color,
|
||||
lineWidth: config.lineWidth,
|
||||
lineStyle: config.style === 'dashed' ? 1 : config.style === 'dotted' ? 3 : 0,
|
||||
title: `${config.type}(${config.period})`,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: true
|
||||
});
|
||||
|
||||
lineSeries.setData(maData);
|
||||
ma.series = lineSeries;
|
||||
ma.data = maData;
|
||||
|
||||
tvWidget.series.movingAverageSeries.push(lineSeries);
|
||||
updateMovingAverageIndicators();
|
||||
}
|
||||
} else {
|
||||
// 添加新均线
|
||||
addMovingAverageToChart(config);
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('movingAverageModal'));
|
||||
modal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
// 买卖点样式定义 - 根据desc字段直接显示
|
||||
function getTradePointStyle(point) {
|
||||
const isBuy = point.type > 0;
|
||||
@@ -1308,6 +1890,11 @@
|
||||
|
||||
// 刷新图表
|
||||
refreshChart(data);
|
||||
|
||||
// 更新均线数据
|
||||
setTimeout(() => {
|
||||
updateAllMovingAverages();
|
||||
}, 100);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
// 隐藏加载图标
|
||||
@@ -1396,7 +1983,13 @@
|
||||
// 清除图表容器
|
||||
document.getElementById('tradingview_chart').innerHTML = '';
|
||||
|
||||
// 重置图表对象
|
||||
// 重置图表对象(保留均线配置,但清空序列引用)
|
||||
// 清空所有均线的序列引用(图表重新初始化后序列无效)
|
||||
movingAverages.forEach(ma => {
|
||||
ma.series = null;
|
||||
});
|
||||
console.log('已清空均线序列引用,配置保留:', movingAverages.length);
|
||||
|
||||
tvWidget = {
|
||||
mainChart: null,
|
||||
volumeChart: null,
|
||||
@@ -1418,7 +2011,8 @@
|
||||
elementUncompletedZsSeries: [],
|
||||
tradePointSeries: [],
|
||||
mainBollingerSeries: [],
|
||||
elementBollingerSeries: []
|
||||
elementBollingerSeries: [],
|
||||
movingAverageSeries: []
|
||||
},
|
||||
state: {
|
||||
isInitialized: false,
|
||||
@@ -1715,13 +2309,13 @@
|
||||
tvWidget.series.lineSeries = lineSeries;
|
||||
}
|
||||
|
||||
// 转换成交量数据 - 始终使用主K线周期数据
|
||||
// 转换成交量数据 - 根据周期选择使用对应数据源
|
||||
let volumes = [];
|
||||
// 使用与K线和MACD相同的数据源选择逻辑
|
||||
// 强制使用主周期数据源,确保与MACD、ATR图表时间完全一致
|
||||
const volumeDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
// 使用已定义的useElementPeriod变量
|
||||
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
const dataSourceType = useElementPeriod ? '小周期' : '主周期';
|
||||
|
||||
console.log('成交量数据源: 主周期(强制与MACD、ATR一致)');
|
||||
console.log('成交量数据源:', dataSourceType);
|
||||
console.log('成交量数据长度:', volumeDataSource.length);
|
||||
|
||||
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
||||
@@ -1749,8 +2343,12 @@
|
||||
volumeSeries.setData(volumes);
|
||||
tvWidget.series.volumeSeries = volumeSeries;
|
||||
|
||||
// 添加MACD图表 - 始终使用主K线周期的MACD数据
|
||||
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 添加MACD图表 - 根据周期选择使用对应数据源
|
||||
const macdDataExists = useElementPeriod ?
|
||||
(currentData.element_macd && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) :
|
||||
(currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data));
|
||||
|
||||
if (showMacd && macdDataExists) {
|
||||
// 创建MACD线
|
||||
const macdLineSeries = macdChart.addLineSeries({
|
||||
color: '#2962FF',
|
||||
@@ -1784,11 +2382,12 @@
|
||||
const signalData = [];
|
||||
const histogramData = [];
|
||||
|
||||
// 强制使用主周期数据源,确保与ATR图表时间完全一致
|
||||
const klineDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
const macdDataSource = currentData.macd; // 总是使用主周期MACD数据
|
||||
// 根据周期选择使用对应数据源
|
||||
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
const macdDataSource = useElementPeriod ? currentData.element_macd : currentData.macd;
|
||||
const macdSourceType = useElementPeriod ? '小周期' : '主周期';
|
||||
|
||||
console.log('MACD数据源: 主周期(强制与ATR一致)');
|
||||
console.log('MACD数据源:', macdSourceType);
|
||||
console.log('K线数据长度:', klineDataSource.length);
|
||||
console.log('MACD数据:', macdDataSource);
|
||||
|
||||
@@ -1829,8 +2428,12 @@
|
||||
tvWidget.series.histogramSeries = histogramSeries;
|
||||
}
|
||||
|
||||
// 添加ATR图表 - 独立显示ATR数据
|
||||
if (showMacd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 添加ATR图表 - 根据周期选择使用对应数据源
|
||||
const atrDataExists = useElementPeriod ?
|
||||
(currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) :
|
||||
(currentData.kline_data && Array.isArray(currentData.kline_data));
|
||||
|
||||
if (showMacd && atrDataExists) {
|
||||
// 创建ATR线
|
||||
const atrLineSeries = atrChart.addLineSeries({
|
||||
color: '#FF9800',
|
||||
@@ -1846,10 +2449,11 @@
|
||||
// 提取ATR数据
|
||||
const atrData = [];
|
||||
|
||||
// 强制使用主周期数据源,确保与MACD图表时间完全一致
|
||||
const klineDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
// 根据周期选择使用对应数据源
|
||||
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
const atrSourceType = useElementPeriod ? '小周期' : '主周期';
|
||||
|
||||
console.log('ATR数据源: 主周期(强制与MACD一致)');
|
||||
console.log('ATR数据源:', atrSourceType);
|
||||
console.log('ATR K线数据长度:', klineDataSource.length);
|
||||
|
||||
// 创建一个隐藏的基础数据系列来保持时间轴对齐
|
||||
@@ -3350,10 +3954,11 @@
|
||||
tvWidget.series.lineSeries.setData(lineData);
|
||||
}
|
||||
|
||||
// 更新成交量数据 - 强制使用主周期数据源确保时间对齐
|
||||
// 更新成交量数据 - 根据周期选择使用对应数据源
|
||||
let volumes = [];
|
||||
if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
volumes = currentData.kline_data.map(kline => {
|
||||
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
||||
volumes = volumeDataSource.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
@@ -3367,30 +3972,33 @@
|
||||
tvWidget.series.volumeSeries.setData(volumes);
|
||||
}
|
||||
|
||||
// 更新MACD数据
|
||||
if (true && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.macdLineSeries) {
|
||||
// 更新MACD数据 - 根据周期选择使用对应数据源
|
||||
const macdDataSource = useElementPeriod ? currentData.element_macd : currentData.macd;
|
||||
const macdKlineSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
|
||||
if (true && macdDataSource && macdKlineSource && Array.isArray(macdKlineSource) && tvWidget.series.macdLineSeries) {
|
||||
// 提取MACD数据
|
||||
const macdData = [];
|
||||
const signalData = [];
|
||||
const histogramData = [];
|
||||
|
||||
for (let i = 0; i < currentData.kline_data.length; i++) {
|
||||
const kline = currentData.kline_data[i];
|
||||
for (let i = 0; i < macdKlineSource.length; i++) {
|
||||
const kline = macdKlineSource[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
|
||||
if (currentData.macd && currentData.macd.macd && currentData.macd.macd[i] !== undefined) {
|
||||
if (macdDataSource && macdDataSource.macd && macdDataSource.macd[i] !== undefined) {
|
||||
macdData.push({
|
||||
time: timestamp,
|
||||
value: currentData.macd.macd[i]
|
||||
value: macdDataSource.macd[i]
|
||||
});
|
||||
|
||||
signalData.push({
|
||||
time: timestamp,
|
||||
value: currentData.macd.signal[i]
|
||||
value: macdDataSource.signal[i]
|
||||
});
|
||||
|
||||
// 设置直方图颜色
|
||||
const histValue = currentData.macd.histogram[i];
|
||||
const histValue = macdDataSource.histogram[i];
|
||||
histogramData.push({
|
||||
time: timestamp,
|
||||
value: histValue,
|
||||
@@ -3404,12 +4012,13 @@
|
||||
tvWidget.series.histogramSeries.setData(histogramData);
|
||||
}
|
||||
|
||||
// 更新ATR数据 - 保持时间轴对齐
|
||||
if (currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.atrLineSeries) {
|
||||
// 更新ATR数据 - 根据周期选择使用对应数据源
|
||||
const atrKlineSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
if (atrKlineSource && Array.isArray(atrKlineSource) && tvWidget.series.atrLineSeries) {
|
||||
const atrData = [];
|
||||
|
||||
for (let i = 0; i < currentData.kline_data.length; i++) {
|
||||
const kline = currentData.kline_data[i];
|
||||
for (let i = 0; i < atrKlineSource.length; i++) {
|
||||
const kline = atrKlineSource[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
|
||||
// 只添加ATR>0的有效数据,不显示ATR=0的点
|
||||
@@ -3692,6 +4301,14 @@
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
}
|
||||
|
||||
// 如果有均线配置,用新数据重新计算和显示均线
|
||||
if (movingAverages.length > 0) {
|
||||
console.log('图表重新初始化后,更新均线数据,均线数量:', movingAverages.length);
|
||||
setTimeout(() => {
|
||||
updateAllMovingAverages();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 窗口大小变化时重绘图表
|
||||
window.addEventListener('resize', () => {
|
||||
// 调整主图大小
|
||||
@@ -6073,6 +6690,109 @@
|
||||
// 已禁用突出显示功能,无需错误日志
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// 初始化均线系统
|
||||
$(document).ready(function() {
|
||||
initMovingAverageEvents();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 均线配置对话框 -->
|
||||
<div class="modal fade" id="movingAverageModal" tabindex="-1" aria-labelledby="movingAverageModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="movingAverageModalLabel">配置均线</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="movingAverageForm">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maType" class="form-label">类型</label>
|
||||
<select class="form-select" id="maType" required>
|
||||
<option value="SMA">SMA (简单移动平均线)</option>
|
||||
<option value="EMA">EMA (指数移动平均线)</option>
|
||||
<option value="WMA">WMA (加权移动平均线)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maPeriod" class="form-label">周期长度</label>
|
||||
<input type="number" class="form-control" id="maPeriod" min="1" max="200" value="20" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maSource" class="form-label">价格来源</label>
|
||||
<select class="form-select" id="maSource" required>
|
||||
<option value="close">收盘价</option>
|
||||
<option value="open">开盘价</option>
|
||||
<option value="high">最高价</option>
|
||||
<option value="low">最低价</option>
|
||||
<option value="hl2">高低平均价 (HL2)</option>
|
||||
<option value="hlc3">高低收平均价 (HLC3)</option>
|
||||
<option value="ohlc4">开高低收平均价 (OHLC4)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maColor" class="form-label">颜色</label>
|
||||
<input type="color" class="form-control form-control-color" id="maColor" value="#2962FF" title="选择均线颜色">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maSmoothing" class="form-label">平滑算法</label>
|
||||
<select class="form-select" id="maSmoothing">
|
||||
<option value="none">无平滑</option>
|
||||
<option value="SMA">SMA</option>
|
||||
<option value="EMA">EMA</option>
|
||||
<option value="WMA">WMA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maSmoothingPeriod" class="form-label">平滑周期</label>
|
||||
<input type="number" class="form-control" id="maSmoothingPeriod" min="1" max="50" value="3" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maLineWidth" class="form-label">线宽</label>
|
||||
<input type="range" class="form-range" id="maLineWidth" min="1" max="5" value="2">
|
||||
<span id="lineWidthDisplay">2</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="maStyle" class="form-label">线型</label>
|
||||
<select class="form-select" id="maStyle">
|
||||
<option value="solid">实线</option>
|
||||
<option value="dashed">虚线</option>
|
||||
<option value="dotted">点线</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="addMovingAverageConfirm">添加均线</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user