Add ATR
This commit is contained in:
+14
@@ -299,6 +299,10 @@ def add_indicators(df):
|
||||
# 处理Infinity和-Infinity值
|
||||
df['volume_ratio'] = df['volume_ratio'].replace([float('inf'), float('-inf')], 1.0)
|
||||
|
||||
# 计算ATR (Average True Range) - 14周期
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
df['atr'] = df['atr'].fillna(0)
|
||||
|
||||
return df
|
||||
|
||||
def calculate_macd(df):
|
||||
@@ -568,6 +572,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
|
||||
'middle': element_current_df['element_bb_middle'].tolist(),
|
||||
'lower': element_current_df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加次周期ATR数据
|
||||
'element_atr': element_current_df['atr'].tolist(),
|
||||
'element_klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
'price': float(point['price']),
|
||||
@@ -644,6 +650,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
|
||||
'middle': current_df['element_bb_middle'].tolist(),
|
||||
'lower': current_df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加ATR数据
|
||||
'atr': current_df['atr'].tolist(),
|
||||
'klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
'price': float(point['price']),
|
||||
@@ -680,6 +688,7 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
|
||||
'element_macd': {'macd': [], 'signal': [], 'histogram': []},
|
||||
'element_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
||||
'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
||||
'element_atr': [],
|
||||
'element_klc_fx_info': [],
|
||||
'element_klu_fx_info': []
|
||||
})
|
||||
@@ -1116,6 +1125,8 @@ def analyze():
|
||||
'middle': df['element_bb_middle'].tolist(),
|
||||
'lower': df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加ATR数据
|
||||
'atr': df['atr'].tolist(),
|
||||
# 添加K线分型信息
|
||||
'klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
@@ -1173,6 +1184,9 @@ def analyze():
|
||||
'lower': element_df['element_bb_lower'].tolist()
|
||||
}
|
||||
|
||||
# 添加小周期ATR数据
|
||||
result['element_atr'] = element_df['atr'].tolist()
|
||||
|
||||
result['element_bi_list'] = [{
|
||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
||||
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
|
||||
|
||||
+337
-40
@@ -407,6 +407,10 @@
|
||||
<input class="form-check-input" type="checkbox" id="showMainBollinger">
|
||||
<label class="form-check-label" for="showMainBollinger">布林带</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="showMacd" checked>
|
||||
<label class="form-check-label" for="showMacd">MACD</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mt-2">
|
||||
<label class="form-label me-3 mb-0">次周期:</label>
|
||||
@@ -741,6 +745,7 @@
|
||||
candleSeries: null,
|
||||
lineSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
@@ -1387,7 +1392,7 @@
|
||||
container.style.height = '100%';
|
||||
|
||||
// 是否显示MACD
|
||||
const showMacd = true;
|
||||
const showMacd = $('#showMacd').is(':checked');
|
||||
const showOriginalKline = $('#showOriginalKline').is(':checked');
|
||||
|
||||
// 创建主图容器
|
||||
@@ -1406,31 +1411,46 @@
|
||||
volumeChartContainer.style.right = '0';
|
||||
volumeChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
|
||||
// 添加ATR图表容器
|
||||
const atrChartContainer = document.createElement('div');
|
||||
atrChartContainer.style.width = '100%';
|
||||
atrChartContainer.style.position = 'absolute';
|
||||
atrChartContainer.style.left = '0';
|
||||
atrChartContainer.style.right = '0';
|
||||
atrChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
|
||||
// 如果需要显示MACD,创建MACD容器
|
||||
let macdChartContainer = null;
|
||||
if (showMacd) {
|
||||
// 设置各图表高度 - 为三个图表分配合理比例,主图表适度增加高度
|
||||
mainChartContainer.style.height = '55%'; // 主图占55%(约385px)
|
||||
volumeChartContainer.style.top = '55%';
|
||||
volumeChartContainer.style.height = '22.5%'; // 成交量图占22.5%(约157.5px)
|
||||
// 设置各图表高度 - 为四个图表分配合理比例
|
||||
mainChartContainer.style.height = '45%'; // 主图占45%
|
||||
volumeChartContainer.style.top = '45%';
|
||||
volumeChartContainer.style.height = '20%'; // 成交量图占20%
|
||||
|
||||
atrChartContainer.style.top = '65%'; // ATR图从65%位置开始
|
||||
atrChartContainer.style.height = '17.5%'; // ATR图占17.5%
|
||||
|
||||
macdChartContainer = document.createElement('div');
|
||||
macdChartContainer.style.width = '100%';
|
||||
macdChartContainer.style.height = '22.5%'; // MACD图占22.5%(约157.5px)
|
||||
macdChartContainer.style.height = '17.5%'; // MACD图占17.5%
|
||||
macdChartContainer.style.position = 'absolute';
|
||||
macdChartContainer.style.top = '77.5%'; // 从77.5%位置开始
|
||||
macdChartContainer.style.top = '82.5%'; // 从82.5%位置开始
|
||||
macdChartContainer.style.left = '0';
|
||||
macdChartContainer.style.right = '0';
|
||||
macdChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
} else {
|
||||
// 不显示MACD时的高度 - 主图和成交量图分配
|
||||
mainChartContainer.style.height = '72%'; // 主图占72%(约504px)
|
||||
volumeChartContainer.style.top = '72%';
|
||||
volumeChartContainer.style.height = '28%'; // 成交量图占28%(约196px)
|
||||
// 不显示MACD时的高度 - 主图、成交量图和ATR图分配
|
||||
mainChartContainer.style.height = '55%'; // 主图占55%
|
||||
volumeChartContainer.style.top = '55%';
|
||||
volumeChartContainer.style.height = '22.5%'; // 成交量图占22.5%
|
||||
|
||||
atrChartContainer.style.top = '77.5%'; // ATR图从77.5%位置开始
|
||||
atrChartContainer.style.height = '22.5%'; // ATR图占22.5%
|
||||
}
|
||||
|
||||
container.appendChild(mainChartContainer);
|
||||
container.appendChild(volumeChartContainer);
|
||||
container.appendChild(atrChartContainer);
|
||||
if (showMacd) container.appendChild(macdChartContainer);
|
||||
|
||||
// 防止同步过程中的无限循环
|
||||
@@ -1444,6 +1464,8 @@
|
||||
chartHeight = mainChartContainer.clientHeight;
|
||||
} else if (chartType === 'volume') {
|
||||
chartHeight = volumeChartContainer.clientHeight;
|
||||
} else if (chartType === 'atr') {
|
||||
chartHeight = atrChartContainer.clientHeight;
|
||||
} else if (chartType === 'macd') {
|
||||
chartHeight = macdChartContainer ? macdChartContainer.clientHeight : 0;
|
||||
} else {
|
||||
@@ -1556,6 +1578,8 @@
|
||||
visible: showTimeScale,
|
||||
borderColor: '#ddd',
|
||||
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
|
||||
// 确保所有图表使用相同的边距设置
|
||||
rightOffset: 12,
|
||||
// 移除可能影响拖动的固定边缘设置
|
||||
// fixLeftEdge: true,
|
||||
// fixRightEdge: true,
|
||||
@@ -1593,6 +1617,9 @@
|
||||
// 创建成交量图表 - 只显示底部的时间轴
|
||||
const volumeChart = LightweightCharts.createChart(volumeChartContainer, createChartOptions(false, 'volume'));
|
||||
|
||||
// 创建ATR图表
|
||||
const atrChart = LightweightCharts.createChart(atrChartContainer, createChartOptions(false, 'atr'));
|
||||
|
||||
// 创建MACD图表(如果需要)
|
||||
let macdChart = null;
|
||||
if (showMacd) {
|
||||
@@ -1663,6 +1690,59 @@
|
||||
volumeSeries.setData(volumes);
|
||||
tvWidget.series.volumeSeries = volumeSeries;
|
||||
|
||||
// 添加ATR图表
|
||||
const atrLineSeries = atrChart.addLineSeries({
|
||||
color: '#FF9800',
|
||||
lineWidth: 2,
|
||||
title: 'ATR',
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
});
|
||||
|
||||
// 准备ATR数据
|
||||
const atrData = [];
|
||||
// 使用与K线数据相同的数据源来确保时间对齐
|
||||
const atrKlineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
const atrDataSource = useElementPeriod ?
|
||||
(currentData.element_atr || currentData.atr) : // 如果有次周期ATR数据则使用,否则使用主周期
|
||||
currentData.atr; // 主周期使用主周期ATR数据
|
||||
|
||||
console.log('ATR数据源选择:', useElementPeriod ? '次周期' : '主周期');
|
||||
console.log('ATR数据长度:', atrDataSource ? atrDataSource.length : 0);
|
||||
console.log('K线数据长度:', atrKlineDataSource ? atrKlineDataSource.length : 0);
|
||||
|
||||
if (atrDataSource && Array.isArray(atrDataSource) && atrKlineDataSource && Array.isArray(atrKlineDataSource)) {
|
||||
// 关键修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据
|
||||
for (let i = 0; i < atrKlineDataSource.length; i++) {
|
||||
const kline = atrKlineDataSource[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
|
||||
// 为每个时间点都添加数据以保持时间轴对齐,但ATR为0时不显示
|
||||
if (atrDataSource[i] !== undefined) {
|
||||
if (atrDataSource[i] > 0) {
|
||||
// ATR有效值,正常显示
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: atrDataSource[i]
|
||||
});
|
||||
} else {
|
||||
// ATR为0,添加时间点但不显示线条(使用undefined作为value)
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的ATR数据点数:', atrData.length);
|
||||
console.log('ATR数据样本:', atrData.slice(0, 5));
|
||||
}
|
||||
|
||||
console.log('处理后的ATR数据点数:', atrData.length);
|
||||
atrLineSeries.setData(atrData);
|
||||
tvWidget.series.atrLineSeries = atrLineSeries;
|
||||
|
||||
// 添加MACD图表 - 始终使用主K线周期的MACD数据
|
||||
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 创建MACD线
|
||||
@@ -1758,7 +1838,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
try {
|
||||
if (sourceChart && sourceChart.timeScale) {
|
||||
@@ -1787,6 +1868,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 同步ATR图
|
||||
if (sourceChart !== atrChart && atrChart && atrChart.timeScale) {
|
||||
try {
|
||||
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
console.log('✅ ATR图同步完成');
|
||||
} catch (e) {
|
||||
console.error('❌ ATR图同步失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 同步MACD图
|
||||
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
|
||||
try {
|
||||
@@ -1822,6 +1913,7 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
atr: false,
|
||||
macd: false
|
||||
};
|
||||
|
||||
@@ -1838,10 +1930,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
const addChartSyncEvents = (chartContainer, chart) => {
|
||||
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图');
|
||||
console.log('为图表添加同步事件监听:',
|
||||
chart === mainChart ? '主图' :
|
||||
chart === volumeChart ? '成交量图' :
|
||||
chart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
// 确定当前图表类型
|
||||
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd';
|
||||
const chartType = chart === mainChart ? 'main' :
|
||||
chart === volumeChart ? 'volume' :
|
||||
chart === atrChart ? 'atr' : 'macd';
|
||||
|
||||
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -1897,6 +1994,7 @@
|
||||
// 添加事件监听
|
||||
addChartSyncEvents(mainChartContainer, mainChart);
|
||||
addChartSyncEvents(volumeChartContainer, volumeChart);
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
if (showMacd && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
@@ -1915,6 +2013,12 @@
|
||||
height: volumeChartContainer.clientHeight
|
||||
});
|
||||
|
||||
// 调整ATR图大小
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
|
||||
// 调整MACD图大小
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
macdChart.applyOptions({
|
||||
@@ -2914,6 +3018,8 @@
|
||||
// 清除之前的十字线标记
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
|
||||
@@ -2941,6 +3047,26 @@
|
||||
document.body.appendChild(volumeLine);
|
||||
}
|
||||
|
||||
// 在ATR图上绘制垂直线
|
||||
if (atrChart && atrChartContainer) {
|
||||
const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time);
|
||||
if (atrTimeCoordinate !== null) {
|
||||
const atrChartRect = atrChartContainer.getBoundingClientRect();
|
||||
const atrLine = document.createElement('div');
|
||||
atrLine.className = 'atr-crosshair-line';
|
||||
atrLine.style.position = 'fixed'; // 改为fixed定位
|
||||
atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px';
|
||||
atrLine.style.top = atrChartRect.top + 'px';
|
||||
atrLine.style.width = '1px';
|
||||
atrLine.style.height = atrChartRect.height + 'px';
|
||||
atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
||||
atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
||||
atrLine.style.pointerEvents = 'none';
|
||||
atrLine.style.zIndex = '1000';
|
||||
document.body.appendChild(atrLine);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有MACD图,也在MACD图上绘制垂直线
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
|
||||
@@ -2969,6 +3095,8 @@
|
||||
try {
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -3314,7 +3442,7 @@
|
||||
|
||||
// 构建显示文本,包含分型类型和强度信息
|
||||
let displayText = `${fx.fx_strength.toFixed(1)}`;
|
||||
if (fx.fx_strength < 2.0) { // 降低阈值,让更多分型显示
|
||||
if (fx.fx_strength < 1.0) { // 降低阈值,让更多分型显示
|
||||
displayText = fx.fx_strength >= 1.5 ? '' : '' // 0.8以上显示点,0.8以下不显示文本
|
||||
}
|
||||
|
||||
@@ -3399,7 +3527,7 @@
|
||||
let strengthColor = fx.is_bottom ? '#11116B' : '#222222'; // 底分型用珊瑚红,顶分型用薄荷绿
|
||||
let displayText = `${fx.fx_strength.toFixed(1)}`;
|
||||
// 构建小周期分型显示文本
|
||||
if (fx.fx_strength < 2.0){ // 调整小周期阈值
|
||||
if (fx.fx_strength < 1.0){ // 调整小周期阈值
|
||||
displayText = fx.fx_strength >= 0.6 ? '' : '' // 0.6以上显示点
|
||||
}
|
||||
|
||||
@@ -3544,21 +3672,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 调整所有图表以适应数据
|
||||
// 同步所有图表的时间轴配置
|
||||
const syncTimeScaleSettings = () => {
|
||||
// 获取主图表的时间轴设置
|
||||
const mainTimeScale = mainChart.timeScale();
|
||||
const baseOptions = {
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
borderColor: '#ddd',
|
||||
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
|
||||
rightOffset: 12,
|
||||
lockVisibleTimeRangeOnResize: true,
|
||||
// 关键:确保所有图表边缘行为完全一致
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
// 确保时间刻度行为一致
|
||||
ticksVisible: true,
|
||||
minimumHeight: 0,
|
||||
};
|
||||
|
||||
console.log('🔧 同步时间轴设置:', baseOptions);
|
||||
|
||||
// 应用相同的设置到所有图表
|
||||
mainChart.timeScale().applyOptions(baseOptions);
|
||||
volumeChart.timeScale().applyOptions(baseOptions);
|
||||
atrChart.timeScale().applyOptions(baseOptions);
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().applyOptions(baseOptions);
|
||||
}
|
||||
};
|
||||
|
||||
// 首先同步时间轴设置
|
||||
syncTimeScaleSettings();
|
||||
|
||||
// 然后让主图表适应内容
|
||||
mainChart.timeScale().fitContent();
|
||||
volumeChart.timeScale().fitContent();
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().fitContent();
|
||||
}
|
||||
|
||||
// 立即同步其他图表到主图表的范围
|
||||
setTimeout(() => {
|
||||
const visibleRange = mainChart.timeScale().getVisibleRange();
|
||||
if (visibleRange) {
|
||||
console.log('🔧 同步可见范围:', visibleRange);
|
||||
volumeChart.timeScale().setVisibleRange(visibleRange);
|
||||
atrChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().setVisibleRange(visibleRange);
|
||||
}
|
||||
console.log('🔧 时间轴同步完成');
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// 保存图表对象
|
||||
tvWidget.mainChart = mainChart;
|
||||
tvWidget.volumeChart = volumeChart;
|
||||
tvWidget.atrChart = atrChart;
|
||||
tvWidget.macdChart = macdChart;
|
||||
tvWidget.state.isInitialized = true;
|
||||
|
||||
// 绑定同步事件
|
||||
bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd);
|
||||
bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd);
|
||||
|
||||
// 最终确保所有图表时间轴对齐
|
||||
setTimeout(() => {
|
||||
const visibleRange = mainChart.timeScale().getVisibleRange();
|
||||
if (visibleRange) {
|
||||
console.log('🔧 最终同步可见范围:', visibleRange);
|
||||
|
||||
// 强制重新设置所有图表的可见范围
|
||||
volumeChart.timeScale().setVisibleRange(visibleRange);
|
||||
atrChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().setVisibleRange(visibleRange);
|
||||
}
|
||||
|
||||
console.log('🔧 最终时间轴对齐完成');
|
||||
}
|
||||
}, 150);
|
||||
|
||||
// 只有在时间输入框都为空时才设置图表默认时间范围
|
||||
if (!$('#start_time').val() && !$('#end_time').val()) {
|
||||
@@ -3566,7 +3755,7 @@
|
||||
}
|
||||
|
||||
// 添加买卖点提示
|
||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd);
|
||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, volumeChart, atrChart, macdChart, showMacd);
|
||||
|
||||
// 显示买卖点
|
||||
if ($('#showTradePoints').is(':checked')) {
|
||||
@@ -3674,8 +3863,46 @@
|
||||
tvWidget.series.volumeSeries.setData(volumes);
|
||||
}
|
||||
|
||||
// 更新ATR数据
|
||||
if (tvWidget.series.atrLineSeries) {
|
||||
const atrData = [];
|
||||
const atrDataSource = useElementPeriod ?
|
||||
(currentData.element_atr || currentData.atr) :
|
||||
currentData.atr;
|
||||
|
||||
if (atrDataSource && Array.isArray(atrDataSource)) {
|
||||
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
// 修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据
|
||||
for (let i = 0; i < klineDataSource.length; i++) {
|
||||
const kline = klineDataSource[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
|
||||
// 为每个时间点都添加数据以保持时间轴对齐,但ATR为0时不显示
|
||||
if (atrDataSource[i] !== undefined) {
|
||||
if (atrDataSource[i] > 0) {
|
||||
// ATR有效值,正常显示
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: atrDataSource[i]
|
||||
});
|
||||
} else {
|
||||
// ATR为0,添加时间点但不显示线条(使用undefined作为value)
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🔄 增量更新ATR数据点数:', atrData.length);
|
||||
}
|
||||
|
||||
tvWidget.series.atrLineSeries.setData(atrData);
|
||||
}
|
||||
|
||||
// 更新MACD数据
|
||||
if (true && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.macdLineSeries) {
|
||||
if (tvWidget.series.macdLineSeries && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 提取MACD数据
|
||||
const macdData = [];
|
||||
const signalData = [];
|
||||
@@ -3714,16 +3941,20 @@
|
||||
// 重新显示笔、线段和中枢等图形
|
||||
redrawFractalElements();
|
||||
|
||||
// 恢复之前的可视范围
|
||||
// 恢复之前的可视范围 - 优先使用visibleRange以确保时间轴对齐
|
||||
if (tvWidget.mainChart) {
|
||||
if (tvWidget.state.logicalRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
} else if (tvWidget.state.visibleRange) {
|
||||
if (tvWidget.state.visibleRange) {
|
||||
console.log('🔄 恢复可见范围:', tvWidget.state.visibleRange);
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
} else if (tvWidget.state.logicalRange) {
|
||||
console.log('🔄 恢复逻辑范围:', tvWidget.state.logicalRange);
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3735,7 +3966,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd) {
|
||||
function bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd) {
|
||||
// 防止同步过程中的无限循环
|
||||
let syncInProgress = false;
|
||||
|
||||
@@ -3743,6 +3974,7 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
atr: false,
|
||||
macd: false
|
||||
};
|
||||
|
||||
@@ -3757,7 +3989,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
try {
|
||||
if (sourceChart && sourceChart.timeScale) {
|
||||
@@ -3786,6 +4019,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 同步ATR图
|
||||
if (sourceChart !== atrChart && atrChart && atrChart.timeScale) {
|
||||
try {
|
||||
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
console.log('✅ ATR图同步完成');
|
||||
} catch (e) {
|
||||
console.error('❌ ATR图同步失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 同步MACD图
|
||||
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
|
||||
try {
|
||||
@@ -3799,6 +4042,14 @@
|
||||
// 保存当前的可见范围到全局状态
|
||||
if (tvWidget && tvWidget.state) {
|
||||
tvWidget.state.logicalRange = logicalRange;
|
||||
// 同时保存可见范围以确保精确对齐
|
||||
try {
|
||||
const visibleRange = sourceChart.timeScale().getVisibleRange();
|
||||
tvWidget.state.visibleRange = visibleRange;
|
||||
console.log('💾 保存状态 - 逻辑范围:', logicalRange, '可见范围:', visibleRange);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ 保存可见范围失败:', e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 无效的逻辑范围:', logicalRange);
|
||||
@@ -3819,10 +4070,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
const addChartSyncEvents = (chartContainer, chart) => {
|
||||
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图');
|
||||
console.log('为图表添加同步事件监听:',
|
||||
chart === mainChart ? '主图' :
|
||||
chart === volumeChart ? '成交量图' :
|
||||
chart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
// 确定当前图表类型
|
||||
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd';
|
||||
const chartType = chart === mainChart ? 'main' :
|
||||
chart === volumeChart ? 'volume' :
|
||||
chart === atrChart ? 'atr' : 'macd';
|
||||
|
||||
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -3882,6 +4138,9 @@
|
||||
if (volumeChartContainer && volumeChart) {
|
||||
addChartSyncEvents(volumeChartContainer, volumeChart);
|
||||
}
|
||||
if (atrChartContainer && atrChart) {
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
}
|
||||
if (showMacd && macdChartContainer && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
@@ -3904,6 +4163,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 调整ATR图大小
|
||||
if (atrChart && atrChartContainer) {
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
}
|
||||
|
||||
// 调整MACD图大小
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
macdChart.applyOptions({
|
||||
@@ -3921,7 +4188,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd) {
|
||||
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, volumeChart, atrChart, macdChart, showMacd) {
|
||||
// 调试变量
|
||||
window.debugMode = true;
|
||||
|
||||
@@ -3953,6 +4220,8 @@
|
||||
// 清除之前的十字线标记
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
|
||||
@@ -3980,6 +4249,26 @@
|
||||
document.body.appendChild(volumeLine);
|
||||
}
|
||||
|
||||
// 在ATR图上绘制垂直线
|
||||
if (atrChart && atrChartContainer) {
|
||||
const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time);
|
||||
if (atrTimeCoordinate !== null) {
|
||||
const atrChartRect = atrChartContainer.getBoundingClientRect();
|
||||
const atrLine = document.createElement('div');
|
||||
atrLine.className = 'atr-crosshair-line';
|
||||
atrLine.style.position = 'fixed'; // 改为fixed定位
|
||||
atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px';
|
||||
atrLine.style.top = atrChartRect.top + 'px';
|
||||
atrLine.style.width = '1px';
|
||||
atrLine.style.height = atrChartRect.height + 'px';
|
||||
atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
||||
atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
||||
atrLine.style.pointerEvents = 'none';
|
||||
atrLine.style.zIndex = '1000';
|
||||
document.body.appendChild(atrLine);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有MACD图,也在MACD图上绘制垂直线
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
|
||||
@@ -4008,6 +4297,8 @@
|
||||
try {
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -4754,12 +5045,14 @@
|
||||
// 销毁主图表及其关联的线系列
|
||||
tvWidget.mainChart = null;
|
||||
tvWidget.volumeChart = null;
|
||||
tvWidget.atrChart = null;
|
||||
tvWidget.macdChart = null;
|
||||
// 重置系列数据
|
||||
tvWidget.series = {
|
||||
candleSeries: null,
|
||||
lineSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
@@ -4809,6 +5102,8 @@
|
||||
// 清除所有十字线延长线,防止它们跟着页面滚动
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -5020,7 +5315,7 @@
|
||||
const logicalRange = mainChart.timeScale().getVisibleLogicalRange();
|
||||
|
||||
// 获取当前图表设置
|
||||
const showMacd = true;
|
||||
const showMacd = $('#showMacd').is(':checked');
|
||||
const showOriginalKline = $('#showOriginalKline').is(':checked');
|
||||
const showBi = $('#showMainBi').is(':checked');
|
||||
const showSeg = $('#showMainSeg').is(':checked');
|
||||
@@ -5264,7 +5559,7 @@
|
||||
'showMainZs': $('#showMainZs').is(':checked'),
|
||||
'showMainUncompletedZs': $('#showMainUncompletedZs').is(':checked'),
|
||||
'showVolume': false,
|
||||
'showMacd': true,
|
||||
'showMacd': $('#showMacd').is(':checked'),
|
||||
'showKlcFxType': $('#showKlcFxType').is(':checked'),
|
||||
'showKluFxType': $('#showKluFxType').is(':checked'),
|
||||
'showElementKlcFxType': $('#showElementKlcFxType').is(':checked'),
|
||||
@@ -6335,9 +6630,11 @@
|
||||
try {
|
||||
// 清除所有十字线延长线,防止它们跟着页面滚动
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
console.debug('清除滚动中的十字线时出错:', e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user