添加均线系统
This commit is contained in:
+742
-2
@@ -286,6 +286,186 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 主次周期元素样式区分 */
|
/* 主次周期元素样式区分 */
|
||||||
|
|
||||||
|
/* 均线系统样式 */
|
||||||
|
.ma-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
max-width: 300px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-value {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-action-btn:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-color-indicator {
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-ma-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 120px;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-ma-btn:hover {
|
||||||
|
background-color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置弹窗样式 */
|
||||||
|
.ma-config-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 10000;
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-content {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-input, .ma-config-select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-input:focus, .ma-config-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-btn-primary {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-btn-primary:hover {
|
||||||
|
background-color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ma-config-btn-secondary:hover {
|
||||||
|
background-color: #5c636a;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -513,6 +693,12 @@
|
|||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<div id="tradingview_chart"></div>
|
<div id="tradingview_chart"></div>
|
||||||
|
<!-- 添加均线按钮 -->
|
||||||
|
<button class="add-ma-btn" onclick="showMAConfig()">
|
||||||
|
<i class="bi bi-plus-lg"></i> 添加均线
|
||||||
|
</button>
|
||||||
|
<!-- 均线信息面板 -->
|
||||||
|
<div id="maPanel" class="ma-panel" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="data-container">
|
<div class="data-container">
|
||||||
@@ -1382,7 +1568,8 @@
|
|||||||
elementUncompletedZsSeries: [],
|
elementUncompletedZsSeries: [],
|
||||||
tradePointSeries: [],
|
tradePointSeries: [],
|
||||||
mainBollingerSeries: [],
|
mainBollingerSeries: [],
|
||||||
elementBollingerSeries: []
|
elementBollingerSeries: [],
|
||||||
|
maSeries: [] // 添加均线系列数组
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
@@ -3387,7 +3574,7 @@
|
|||||||
if (fx.fx_strength < 1.0) { // 降低阈值,让更多分型显示
|
if (fx.fx_strength < 1.0) { // 降低阈值,让更多分型显示
|
||||||
displayText = fx.fx_strength >= 0.8 ? '' : '' // 0.8以上显示点,0.8以下不显示文本
|
displayText = fx.fx_strength >= 0.8 ? '' : '' // 0.8以上显示点,0.8以下不显示文本
|
||||||
}
|
}
|
||||||
displayText = fx.fx_type.replace("TOP", "").replace("BOTTOM", "").replace("1", "").replace("2", "").replace("30", "");
|
displayText = fx.fx_type.replace("TOP", "").replace("BOTTOM", "").replace("11", "").replace("21", "").replace("3", "");
|
||||||
// 添加标记配置
|
// 添加标记配置
|
||||||
const markerConfig = {
|
const markerConfig = {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
@@ -3735,6 +3922,9 @@
|
|||||||
tvWidget.macdChart = macdChart;
|
tvWidget.macdChart = macdChart;
|
||||||
tvWidget.state.isInitialized = true;
|
tvWidget.state.isInitialized = true;
|
||||||
|
|
||||||
|
// 添加均线到图表
|
||||||
|
addMovingAveragesToChart(candles);
|
||||||
|
|
||||||
// 绑定同步事件
|
// 绑定同步事件
|
||||||
bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd);
|
bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd);
|
||||||
|
|
||||||
@@ -3843,6 +4033,9 @@
|
|||||||
tvWidget.series.lineSeries.setData(lineData);
|
tvWidget.series.lineSeries.setData(lineData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新均线数据
|
||||||
|
addMovingAveragesToChart(candles);
|
||||||
|
|
||||||
// 更新成交量数据
|
// 更新成交量数据
|
||||||
let volumes = [];
|
let volumes = [];
|
||||||
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
|
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
|
||||||
@@ -6621,6 +6814,499 @@
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 均线系统全局变量
|
||||||
|
let movingAverages = []; // 存储所有均线配置
|
||||||
|
let maIdCounter = 0; // 均线ID计数器
|
||||||
|
|
||||||
|
// 显示均线配置弹窗
|
||||||
|
function showMAConfig() {
|
||||||
|
$('#maConfigModal').css('display', 'flex');
|
||||||
|
// 重置表单
|
||||||
|
$('#maType').val('SMA');
|
||||||
|
$('#maLength').val(20);
|
||||||
|
$('#maSource').val('close');
|
||||||
|
$('#maSmoothType').val('none');
|
||||||
|
$('#maSmoothLength').val(3).prop('disabled', true);
|
||||||
|
$('#maColor').val(getRandomColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏均线配置弹窗
|
||||||
|
function hideMAConfig() {
|
||||||
|
$('#maConfigModal').css('display', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取随机颜色
|
||||||
|
function getRandomColor() {
|
||||||
|
const colors = ['#2962FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
|
||||||
|
'#FECA57', '#48CAE4', '#F38BA8', '#A8DADC', '#F1C0E8'];
|
||||||
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加均线
|
||||||
|
function addMovingAverage() {
|
||||||
|
const config = {
|
||||||
|
id: ++maIdCounter,
|
||||||
|
type: $('#maType').val(),
|
||||||
|
length: parseInt($('#maLength').val()),
|
||||||
|
source: $('#maSource').val(),
|
||||||
|
smoothType: $('#maSmoothType').val(),
|
||||||
|
smoothLength: parseInt($('#maSmoothLength').val()),
|
||||||
|
color: $('#maColor').val(),
|
||||||
|
visible: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
if (config.length < 1 || config.length > 200) {
|
||||||
|
alert('均线长度必须在1-200之间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.smoothType !== 'none' && (config.smoothLength < 1 || config.smoothLength > 50)) {
|
||||||
|
alert('平滑长度必须在1-50之间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
movingAverages.push(config);
|
||||||
|
hideMAConfig();
|
||||||
|
|
||||||
|
// 重新绘制图表以包含新均线
|
||||||
|
if (typeof updateChart === 'function') {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算均线数据
|
||||||
|
function calculateMA(data, type, length, source) {
|
||||||
|
if (!data || data.length < length) return [];
|
||||||
|
|
||||||
|
const sourceData = data.map(candle => {
|
||||||
|
switch(source) {
|
||||||
|
case 'open': return candle.open;
|
||||||
|
case 'high': return candle.high;
|
||||||
|
case 'low': return candle.low;
|
||||||
|
case 'close': return candle.close;
|
||||||
|
case 'hl2': return (candle.high + candle.low) / 2;
|
||||||
|
case 'hlc3': return (candle.high + candle.low + candle.close) / 3;
|
||||||
|
case 'ohlc4': return (candle.open + candle.high + candle.low + candle.close) / 4;
|
||||||
|
default: return candle.close;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = length - 1; i < sourceData.length; i++) {
|
||||||
|
let value;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'SMA':
|
||||||
|
value = sourceData.slice(i - length + 1, i + 1)
|
||||||
|
.reduce((sum, val) => sum + val, 0) / length;
|
||||||
|
break;
|
||||||
|
case 'EMA':
|
||||||
|
if (i === length - 1) {
|
||||||
|
// 第一个EMA值使用SMA
|
||||||
|
value = sourceData.slice(0, length)
|
||||||
|
.reduce((sum, val) => sum + val, 0) / length;
|
||||||
|
} else {
|
||||||
|
const multiplier = 2 / (length + 1);
|
||||||
|
value = sourceData[i] * multiplier + result[result.length - 1].value * (1 - multiplier);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'WMA':
|
||||||
|
let weightSum = 0;
|
||||||
|
let valueSum = 0;
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
const weight = j + 1;
|
||||||
|
weightSum += weight;
|
||||||
|
valueSum += sourceData[i - length + 1 + j] * weight;
|
||||||
|
}
|
||||||
|
value = valueSum / weightSum;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = sourceData[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
time: data[i].time,
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用平滑处理
|
||||||
|
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':
|
||||||
|
if (i === smoothLength - 1) {
|
||||||
|
value = maData.slice(0, smoothLength)
|
||||||
|
.reduce((sum, item) => sum + item.value, 0) / smoothLength;
|
||||||
|
} else {
|
||||||
|
const multiplier = 2 / (smoothLength + 1);
|
||||||
|
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 updateMAPanel() {
|
||||||
|
const panel = $('#maPanel');
|
||||||
|
|
||||||
|
if (movingAverages.length === 0) {
|
||||||
|
panel.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
let hasVisibleMA = 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 itemStyle = ma.visible ? '' : 'opacity: 0.5;';
|
||||||
|
const eyeIcon = ma.visible ? 'bi-eye' : 'bi-eye-slash';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="ma-item" data-ma-id="${ma.id}" style="${itemStyle}">
|
||||||
|
<div class="ma-info">
|
||||||
|
<div class="ma-color-indicator" style="background-color: ${ma.color}"></div>
|
||||||
|
<span class="ma-label">${ma.type}(${ma.length})${smoothText}</span>
|
||||||
|
<span class="ma-value">${ma.visible ? latestValue : '--'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ma-actions">
|
||||||
|
<button class="ma-action-btn" onclick="toggleMAVisibility(${ma.id})" title="${ma.visible ? '隐藏' : '显示'}">
|
||||||
|
<i class="bi ${eyeIcon}"></i>
|
||||||
|
</button>
|
||||||
|
<button class="ma-action-btn" onclick="configMA(${ma.id})" title="配置">
|
||||||
|
<i class="bi bi-gear"></i>
|
||||||
|
</button>
|
||||||
|
<button class="ma-action-btn" onclick="deleteMA(${ma.id})" title="删除">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (ma.visible) {
|
||||||
|
hasVisibleMA = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (html.trim() === '') {
|
||||||
|
panel.hide();
|
||||||
|
} else {
|
||||||
|
panel.html(html);
|
||||||
|
panel.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换均线可见性
|
||||||
|
function toggleMAVisibility(maId) {
|
||||||
|
const ma = movingAverages.find(m => m.id === maId);
|
||||||
|
if (ma) {
|
||||||
|
ma.visible = !ma.visible;
|
||||||
|
|
||||||
|
// 如果图表已初始化,直接更新均线显示
|
||||||
|
if (tvWidget.mainChart && currentData && currentData.kline_data) {
|
||||||
|
// 获取K线数据
|
||||||
|
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||||
|
currentData.element_kline_data &&
|
||||||
|
Array.isArray(currentData.element_kline_data);
|
||||||
|
|
||||||
|
let candles = [];
|
||||||
|
if (useElementPeriod) {
|
||||||
|
candles = currentData.element_kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
candles = currentData.kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新添加均线到图表
|
||||||
|
addMovingAveragesToChart(candles);
|
||||||
|
} else {
|
||||||
|
// 如果图表未初始化,重新加载整个图表
|
||||||
|
if (typeof updateChart === 'function') {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置均线
|
||||||
|
function configMA(maId) {
|
||||||
|
const ma = movingAverages.find(m => m.id === maId);
|
||||||
|
if (ma) {
|
||||||
|
// 填充表单
|
||||||
|
$('#maType').val(ma.type);
|
||||||
|
$('#maLength').val(ma.length);
|
||||||
|
$('#maSource').val(ma.source);
|
||||||
|
$('#maSmoothType').val(ma.smoothType);
|
||||||
|
$('#maSmoothLength').val(ma.smoothLength);
|
||||||
|
$('#maColor').val(ma.color);
|
||||||
|
|
||||||
|
// 启用/禁用平滑长度输入
|
||||||
|
$('#maSmoothLength').prop('disabled', ma.smoothType === 'none');
|
||||||
|
|
||||||
|
// 修改按钮为更新模式
|
||||||
|
$('.ma-config-btn-primary').text('更新').off('click').on('click', function() {
|
||||||
|
updateMovingAverage(maId);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#maConfigModal').css('display', 'flex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新均线
|
||||||
|
function updateMovingAverage(maId) {
|
||||||
|
const ma = movingAverages.find(m => m.id === maId);
|
||||||
|
if (ma) {
|
||||||
|
// 验证输入
|
||||||
|
const newLength = parseInt($('#maLength').val());
|
||||||
|
const newSmoothLength = parseInt($('#maSmoothLength').val());
|
||||||
|
const newSmoothType = $('#maSmoothType').val();
|
||||||
|
|
||||||
|
if (newLength < 1 || newLength > 200) {
|
||||||
|
alert('均线长度必须在1-200之间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSmoothType !== 'none' && (newSmoothLength < 1 || newSmoothLength > 50)) {
|
||||||
|
alert('平滑长度必须在1-50之间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
ma.type = $('#maType').val();
|
||||||
|
ma.length = newLength;
|
||||||
|
ma.source = $('#maSource').val();
|
||||||
|
ma.smoothType = newSmoothType;
|
||||||
|
ma.smoothLength = newSmoothLength;
|
||||||
|
ma.color = $('#maColor').val();
|
||||||
|
|
||||||
|
hideMAConfig();
|
||||||
|
|
||||||
|
// 恢复按钮为添加模式
|
||||||
|
$('.ma-config-btn-primary').text('添加').off('click').on('click', addMovingAverage);
|
||||||
|
|
||||||
|
// 如果图表已初始化,直接更新均线显示
|
||||||
|
if (tvWidget.mainChart && currentData && currentData.kline_data) {
|
||||||
|
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||||
|
currentData.element_kline_data &&
|
||||||
|
Array.isArray(currentData.element_kline_data);
|
||||||
|
|
||||||
|
let candles = [];
|
||||||
|
if (useElementPeriod) {
|
||||||
|
candles = currentData.element_kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
candles = currentData.kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addMovingAveragesToChart(candles);
|
||||||
|
} else {
|
||||||
|
if (typeof updateChart === 'function') {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除均线
|
||||||
|
function deleteMA(maId) {
|
||||||
|
movingAverages = movingAverages.filter(m => m.id !== maId);
|
||||||
|
|
||||||
|
// 如果图表已初始化,直接更新均线显示
|
||||||
|
if (tvWidget.mainChart && currentData && currentData.kline_data) {
|
||||||
|
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||||
|
currentData.element_kline_data &&
|
||||||
|
Array.isArray(currentData.element_kline_data);
|
||||||
|
|
||||||
|
let candles = [];
|
||||||
|
if (useElementPeriod) {
|
||||||
|
candles = currentData.element_kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
candles = currentData.kline_data.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),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addMovingAveragesToChart(candles);
|
||||||
|
} else {
|
||||||
|
if (typeof updateChart === 'function') {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加均线到图表
|
||||||
|
function addMovingAveragesToChart(candleData) {
|
||||||
|
if (!tvWidget.mainChart || !candleData || candleData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除现有的均线系列
|
||||||
|
if (tvWidget.series.maSeries) {
|
||||||
|
tvWidget.series.maSeries.forEach(series => {
|
||||||
|
try {
|
||||||
|
tvWidget.mainChart.removeSeries(series);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('移除均线系列时出错:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tvWidget.series.maSeries = [];
|
||||||
|
|
||||||
|
// 为每个均线配置创建系列
|
||||||
|
movingAverages.forEach(maConfig => {
|
||||||
|
if (!maConfig.visible) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 计算均线数据
|
||||||
|
const maData = calculateMA(candleData, maConfig.type, maConfig.length, maConfig.source);
|
||||||
|
|
||||||
|
// 应用平滑处理
|
||||||
|
const smoothedData = maConfig.smoothType !== 'none' ?
|
||||||
|
applySmoothToMA(maData, maConfig.smoothType, maConfig.smoothLength) :
|
||||||
|
maData;
|
||||||
|
|
||||||
|
// 创建线系列
|
||||||
|
const maSeries = tvWidget.mainChart.addLineSeries({
|
||||||
|
color: maConfig.color,
|
||||||
|
lineWidth: 2,
|
||||||
|
title: `${maConfig.type}(${maConfig.length})`,
|
||||||
|
lastValueVisible: false,
|
||||||
|
priceLineVisible: false,
|
||||||
|
crosshairMarkerVisible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
maSeries.setData(smoothedData);
|
||||||
|
|
||||||
|
// 保存数据到配置中以便显示最新值
|
||||||
|
maConfig.data = smoothedData;
|
||||||
|
|
||||||
|
// 保存系列引用
|
||||||
|
tvWidget.series.maSeries.push(maSeries);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('添加均线时出错:', e, maConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新均线面板
|
||||||
|
updateMAPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听平滑类型变化
|
||||||
|
$(document).on('change', '#maSmoothType', function() {
|
||||||
|
const isNone = $(this).val() === 'none';
|
||||||
|
$('#maSmoothLength').prop('disabled', isNone);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击弹窗外部关闭
|
||||||
|
$(document).on('click', '#maConfigModal', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
hideMAConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
// 页面加载完成后初始化
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// 设置默认的筛选时间(最近7天)
|
// 设置默认的筛选时间(最近7天)
|
||||||
@@ -6680,5 +7366,59 @@
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- 均线配置弹窗 -->
|
||||||
|
<div id="maConfigModal" class="ma-config-modal">
|
||||||
|
<div class="ma-config-content">
|
||||||
|
<div class="ma-config-title">添加均线</div>
|
||||||
|
<div class="ma-config-form">
|
||||||
|
<div class="ma-config-group">
|
||||||
|
<label class="ma-config-label">类型</label>
|
||||||
|
<select id="maType" class="ma-config-select">
|
||||||
|
<option value="SMA">SMA (简单移动平均)</option>
|
||||||
|
<option value="EMA">EMA (指数移动平均)</option>
|
||||||
|
<option value="WMA">WMA (加权移动平均)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ma-config-group">
|
||||||
|
<label class="ma-config-label">长度</label>
|
||||||
|
<input type="number" id="maLength" class="ma-config-input" value="20" min="1" max="200">
|
||||||
|
</div>
|
||||||
|
<div class="ma-config-group">
|
||||||
|
<label class="ma-config-label">数据来源</label>
|
||||||
|
<select id="maSource" class="ma-config-select">
|
||||||
|
<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 class="ma-config-group">
|
||||||
|
<label class="ma-config-label">平滑线类型</label>
|
||||||
|
<select id="maSmoothType" class="ma-config-select">
|
||||||
|
<option value="none">无平滑</option>
|
||||||
|
<option value="SMA">SMA</option>
|
||||||
|
<option value="EMA">EMA</option>
|
||||||
|
<option value="WMA">WMA</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ma-config-group">
|
||||||
|
<label class="ma-config-label">平滑长度</label>
|
||||||
|
<input type="number" id="maSmoothLength" class="ma-config-input" value="3" min="1" max="50" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="ma-config-group">
|
||||||
|
<label class="ma-config-label">颜色</label>
|
||||||
|
<input type="color" id="maColor" class="ma-config-input" value="#2962FF">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ma-config-actions">
|
||||||
|
<button class="ma-config-btn ma-config-btn-secondary" onclick="hideMAConfig()">取消</button>
|
||||||
|
<button class="ma-config-btn ma-config-btn-primary" onclick="addMovingAverage()">添加</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user