添加均线可以用了
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -415,6 +481,14 @@
|
|||||||
<input class="form-check-input" type="checkbox" id="showMainBollinger">
|
<input class="form-check-input" type="checkbox" id="showMainBollinger">
|
||||||
<label class="form-check-label" for="showMainBollinger">布林带</label>
|
<label class="form-check-label" for="showMainBollinger">布林带</label>
|
||||||
</div>
|
</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>
|
||||||
<div class="d-flex align-items-center mt-2">
|
<div class="d-flex align-items-center mt-2">
|
||||||
<label class="form-label me-3 mb-0">次周期:</label>
|
<label class="form-label me-3 mb-0">次周期:</label>
|
||||||
@@ -524,6 +598,13 @@
|
|||||||
<div id="loadingIndicator" style="display:none;"></div>
|
<div id="loadingIndicator" style="display:none;"></div>
|
||||||
|
|
||||||
<div class="chart-container">
|
<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 id="tradingview_chart"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -778,7 +859,8 @@
|
|||||||
elementUncompletedZsSeries: [],
|
elementUncompletedZsSeries: [],
|
||||||
tradePointSeries: [],
|
tradePointSeries: [],
|
||||||
mainBollingerSeries: [],
|
mainBollingerSeries: [],
|
||||||
elementBollingerSeries: []
|
elementBollingerSeries: [],
|
||||||
|
movingAverageSeries: []
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
isInitialized: false,
|
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字段直接显示
|
// 买卖点样式定义 - 根据desc字段直接显示
|
||||||
function getTradePointStyle(point) {
|
function getTradePointStyle(point) {
|
||||||
const isBuy = point.type > 0;
|
const isBuy = point.type > 0;
|
||||||
@@ -1308,6 +1890,11 @@
|
|||||||
|
|
||||||
// 刷新图表
|
// 刷新图表
|
||||||
refreshChart(data);
|
refreshChart(data);
|
||||||
|
|
||||||
|
// 更新均线数据
|
||||||
|
setTimeout(() => {
|
||||||
|
updateAllMovingAverages();
|
||||||
|
}, 100);
|
||||||
},
|
},
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
// 隐藏加载图标
|
// 隐藏加载图标
|
||||||
@@ -1396,7 +1983,13 @@
|
|||||||
// 清除图表容器
|
// 清除图表容器
|
||||||
document.getElementById('tradingview_chart').innerHTML = '';
|
document.getElementById('tradingview_chart').innerHTML = '';
|
||||||
|
|
||||||
// 重置图表对象
|
// 重置图表对象(保留均线配置,但清空序列引用)
|
||||||
|
// 清空所有均线的序列引用(图表重新初始化后序列无效)
|
||||||
|
movingAverages.forEach(ma => {
|
||||||
|
ma.series = null;
|
||||||
|
});
|
||||||
|
console.log('已清空均线序列引用,配置保留:', movingAverages.length);
|
||||||
|
|
||||||
tvWidget = {
|
tvWidget = {
|
||||||
mainChart: null,
|
mainChart: null,
|
||||||
volumeChart: null,
|
volumeChart: null,
|
||||||
@@ -1418,7 +2011,8 @@
|
|||||||
elementUncompletedZsSeries: [],
|
elementUncompletedZsSeries: [],
|
||||||
tradePointSeries: [],
|
tradePointSeries: [],
|
||||||
mainBollingerSeries: [],
|
mainBollingerSeries: [],
|
||||||
elementBollingerSeries: []
|
elementBollingerSeries: [],
|
||||||
|
movingAverageSeries: []
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
@@ -1715,13 +2309,13 @@
|
|||||||
tvWidget.series.lineSeries = lineSeries;
|
tvWidget.series.lineSeries = lineSeries;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换成交量数据 - 始终使用主K线周期数据
|
// 转换成交量数据 - 根据周期选择使用对应数据源
|
||||||
let volumes = [];
|
let volumes = [];
|
||||||
// 使用与K线和MACD相同的数据源选择逻辑
|
// 使用已定义的useElementPeriod变量
|
||||||
// 强制使用主周期数据源,确保与MACD、ATR图表时间完全一致
|
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||||
const volumeDataSource = currentData.kline_data; // 总是使用主周期数据
|
const dataSourceType = useElementPeriod ? '小周期' : '主周期';
|
||||||
|
|
||||||
console.log('成交量数据源: 主周期(强制与MACD、ATR一致)');
|
console.log('成交量数据源:', dataSourceType);
|
||||||
console.log('成交量数据长度:', volumeDataSource.length);
|
console.log('成交量数据长度:', volumeDataSource.length);
|
||||||
|
|
||||||
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
||||||
@@ -1749,8 +2343,12 @@
|
|||||||
volumeSeries.setData(volumes);
|
volumeSeries.setData(volumes);
|
||||||
tvWidget.series.volumeSeries = volumeSeries;
|
tvWidget.series.volumeSeries = volumeSeries;
|
||||||
|
|
||||||
// 添加MACD图表 - 始终使用主K线周期的MACD数据
|
// 添加MACD图表 - 根据周期选择使用对应数据源
|
||||||
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
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线
|
// 创建MACD线
|
||||||
const macdLineSeries = macdChart.addLineSeries({
|
const macdLineSeries = macdChart.addLineSeries({
|
||||||
color: '#2962FF',
|
color: '#2962FF',
|
||||||
@@ -1784,11 +2382,12 @@
|
|||||||
const signalData = [];
|
const signalData = [];
|
||||||
const histogramData = [];
|
const histogramData = [];
|
||||||
|
|
||||||
// 强制使用主周期数据源,确保与ATR图表时间完全一致
|
// 根据周期选择使用对应数据源
|
||||||
const klineDataSource = currentData.kline_data; // 总是使用主周期数据
|
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||||
const macdDataSource = currentData.macd; // 总是使用主周期MACD数据
|
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('K线数据长度:', klineDataSource.length);
|
||||||
console.log('MACD数据:', macdDataSource);
|
console.log('MACD数据:', macdDataSource);
|
||||||
|
|
||||||
@@ -1829,8 +2428,12 @@
|
|||||||
tvWidget.series.histogramSeries = histogramSeries;
|
tvWidget.series.histogramSeries = histogramSeries;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加ATR图表 - 独立显示ATR数据
|
// 添加ATR图表 - 根据周期选择使用对应数据源
|
||||||
if (showMacd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
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线
|
// 创建ATR线
|
||||||
const atrLineSeries = atrChart.addLineSeries({
|
const atrLineSeries = atrChart.addLineSeries({
|
||||||
color: '#FF9800',
|
color: '#FF9800',
|
||||||
@@ -1846,10 +2449,11 @@
|
|||||||
// 提取ATR数据
|
// 提取ATR数据
|
||||||
const atrData = [];
|
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);
|
console.log('ATR K线数据长度:', klineDataSource.length);
|
||||||
|
|
||||||
// 创建一个隐藏的基础数据系列来保持时间轴对齐
|
// 创建一个隐藏的基础数据系列来保持时间轴对齐
|
||||||
@@ -3350,10 +3954,11 @@
|
|||||||
tvWidget.series.lineSeries.setData(lineData);
|
tvWidget.series.lineSeries.setData(lineData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新成交量数据 - 强制使用主周期数据源确保时间对齐
|
// 更新成交量数据 - 根据周期选择使用对应数据源
|
||||||
let volumes = [];
|
let volumes = [];
|
||||||
if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||||
volumes = currentData.kline_data.map(kline => {
|
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
||||||
|
volumes = volumeDataSource.map(kline => {
|
||||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||||
return {
|
return {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
@@ -3367,30 +3972,33 @@
|
|||||||
tvWidget.series.volumeSeries.setData(volumes);
|
tvWidget.series.volumeSeries.setData(volumes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新MACD数据
|
// 更新MACD数据 - 根据周期选择使用对应数据源
|
||||||
if (true && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.macdLineSeries) {
|
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数据
|
// 提取MACD数据
|
||||||
const macdData = [];
|
const macdData = [];
|
||||||
const signalData = [];
|
const signalData = [];
|
||||||
const histogramData = [];
|
const histogramData = [];
|
||||||
|
|
||||||
for (let i = 0; i < currentData.kline_data.length; i++) {
|
for (let i = 0; i < macdKlineSource.length; i++) {
|
||||||
const kline = currentData.kline_data[i];
|
const kline = macdKlineSource[i];
|
||||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
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({
|
macdData.push({
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
value: currentData.macd.macd[i]
|
value: macdDataSource.macd[i]
|
||||||
});
|
});
|
||||||
|
|
||||||
signalData.push({
|
signalData.push({
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
value: currentData.macd.signal[i]
|
value: macdDataSource.signal[i]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置直方图颜色
|
// 设置直方图颜色
|
||||||
const histValue = currentData.macd.histogram[i];
|
const histValue = macdDataSource.histogram[i];
|
||||||
histogramData.push({
|
histogramData.push({
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
value: histValue,
|
value: histValue,
|
||||||
@@ -3404,12 +4012,13 @@
|
|||||||
tvWidget.series.histogramSeries.setData(histogramData);
|
tvWidget.series.histogramSeries.setData(histogramData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新ATR数据 - 保持时间轴对齐
|
// 更新ATR数据 - 根据周期选择使用对应数据源
|
||||||
if (currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.atrLineSeries) {
|
const atrKlineSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||||
|
if (atrKlineSource && Array.isArray(atrKlineSource) && tvWidget.series.atrLineSeries) {
|
||||||
const atrData = [];
|
const atrData = [];
|
||||||
|
|
||||||
for (let i = 0; i < currentData.kline_data.length; i++) {
|
for (let i = 0; i < atrKlineSource.length; i++) {
|
||||||
const kline = currentData.kline_data[i];
|
const kline = atrKlineSource[i];
|
||||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||||
|
|
||||||
// 只添加ATR>0的有效数据,不显示ATR=0的点
|
// 只添加ATR>0的有效数据,不显示ATR=0的点
|
||||||
@@ -3692,6 +4301,14 @@
|
|||||||
addChartSyncEvents(atrChartContainer, atrChart);
|
addChartSyncEvents(atrChartContainer, atrChart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果有均线配置,用新数据重新计算和显示均线
|
||||||
|
if (movingAverages.length > 0) {
|
||||||
|
console.log('图表重新初始化后,更新均线数据,均线数量:', movingAverages.length);
|
||||||
|
setTimeout(() => {
|
||||||
|
updateAllMovingAverages();
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
// 窗口大小变化时重绘图表
|
// 窗口大小变化时重绘图表
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
// 调整主图大小
|
// 调整主图大小
|
||||||
@@ -6073,6 +6690,109 @@
|
|||||||
// 已禁用突出显示功能,无需错误日志
|
// 已禁用突出显示功能,无需错误日志
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化均线系统
|
||||||
|
$(document).ready(function() {
|
||||||
|
initMovingAverageEvents();
|
||||||
|
});
|
||||||
</script>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user