Add ATR
This commit is contained in:
+310
-46
@@ -322,9 +322,21 @@
|
||||
<div class="col-md-1">
|
||||
<label for="timeframe" class="form-label">时间周期:</label>
|
||||
<select id="timeframe" class="form-select">
|
||||
{% for value, label in timeframes.items() %}
|
||||
<option value="{{ value }}" {% if value == '5m' %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
<option value="1m">1分钟</option>
|
||||
<option value="3m">3分钟</option>
|
||||
<option value="5m" selected>5分钟</option>
|
||||
<option value="15m">15分钟</option>
|
||||
<option value="30m">30分钟</option>
|
||||
<option value="1h">1小时</option>
|
||||
<option value="2h">2小时</option>
|
||||
<option value="4h">4小时</option>
|
||||
<option value="6h">6小时</option>
|
||||
<option value="8h">8小时</option>
|
||||
<option value="12h">12小时</option>
|
||||
<option value="1d">1日</option>
|
||||
<option value="3d">3日</option>
|
||||
<option value="1w">1周</option>
|
||||
<option value="1M">1月</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
@@ -440,9 +452,21 @@
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<label for="elementTimeframe" class="form-label me-2 mb-0">次级别时间周期:</label>
|
||||
<select id="elementTimeframe" class="form-select form-select-sm me-2" style="width: 120px;">
|
||||
{% for value, label in timeframes.items() %}
|
||||
<option value="{{ value }}" {% if value == '1m' %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
<option value="1m" selected>1分钟</option>
|
||||
<option value="3m">3分钟</option>
|
||||
<option value="5m">5分钟</option>
|
||||
<option value="15m">15分钟</option>
|
||||
<option value="30m">30分钟</option>
|
||||
<option value="1h">1小时</option>
|
||||
<option value="2h">2小时</option>
|
||||
<option value="4h">4小时</option>
|
||||
<option value="6h">6小时</option>
|
||||
<option value="8h">8小时</option>
|
||||
<option value="12h">12小时</option>
|
||||
<option value="1d">1日</option>
|
||||
<option value="3d">3日</option>
|
||||
<option value="1w">1周</option>
|
||||
<option value="1M">1月</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -663,12 +687,19 @@
|
||||
<div class="col-md-3">
|
||||
<label for="filterTimeframe" class="form-label">时间周期:</label>
|
||||
<select id="filterTimeframe" class="form-select">
|
||||
<option value="1m">1分钟</option>
|
||||
<option value="3m">3分钟</option>
|
||||
<option value="5m">5分钟</option>
|
||||
<option value="15m">15分钟</option>
|
||||
<option value="30m">30分钟</option>
|
||||
<option value="1h">1小时</option>
|
||||
<option value="2h">2小时</option>
|
||||
<option value="4h">4小时</option>
|
||||
<option value="6h">6小时</option>
|
||||
<option value="8h">8小时</option>
|
||||
<option value="12h">12小时</option>
|
||||
<option value="1d" selected>1日</option>
|
||||
<option value="3d">3日</option>
|
||||
<option value="1w">1周</option>
|
||||
<option value="1M">1月</option>
|
||||
</select>
|
||||
@@ -736,6 +767,7 @@
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
atrLineSeries: null,
|
||||
mainBiSeries: [],
|
||||
mainSegSeries: [],
|
||||
mainZsSeries: [],
|
||||
@@ -1396,22 +1428,34 @@
|
||||
volumeChartContainer.style.right = '0';
|
||||
volumeChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
|
||||
// 如果需要显示MACD,创建MACD容器
|
||||
// 如果需要显示MACD,创建MACD和ATR容器
|
||||
let macdChartContainer = null;
|
||||
let atrChartContainer = 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%(约315px)
|
||||
volumeChartContainer.style.top = '45%';
|
||||
volumeChartContainer.style.height = '18.5%'; // 成交量图占18.5%(约129.5px)
|
||||
|
||||
// 创建MACD容器
|
||||
macdChartContainer = document.createElement('div');
|
||||
macdChartContainer.style.width = '100%';
|
||||
macdChartContainer.style.height = '22.5%'; // MACD图占22.5%(约157.5px)
|
||||
macdChartContainer.style.height = '18.5%'; // MACD图占18.5%(约129.5px)
|
||||
macdChartContainer.style.position = 'absolute';
|
||||
macdChartContainer.style.top = '77.5%'; // 从77.5%位置开始
|
||||
macdChartContainer.style.top = '63.5%'; // 从63.5%位置开始
|
||||
macdChartContainer.style.left = '0';
|
||||
macdChartContainer.style.right = '0';
|
||||
macdChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
|
||||
// 创建ATR容器
|
||||
atrChartContainer = document.createElement('div');
|
||||
atrChartContainer.style.width = '100%';
|
||||
atrChartContainer.style.height = '18%'; // ATR图占18%(约126px)
|
||||
atrChartContainer.style.position = 'absolute';
|
||||
atrChartContainer.style.top = '82%'; // 从82%位置开始
|
||||
atrChartContainer.style.left = '0';
|
||||
atrChartContainer.style.right = '0';
|
||||
atrChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
} else {
|
||||
// 不显示MACD时的高度 - 主图和成交量图分配
|
||||
mainChartContainer.style.height = '72%'; // 主图占72%(约504px)
|
||||
@@ -1421,7 +1465,10 @@
|
||||
|
||||
container.appendChild(mainChartContainer);
|
||||
container.appendChild(volumeChartContainer);
|
||||
if (showMacd) container.appendChild(macdChartContainer);
|
||||
if (showMacd) {
|
||||
container.appendChild(macdChartContainer);
|
||||
container.appendChild(atrChartContainer);
|
||||
}
|
||||
|
||||
// 防止同步过程中的无限循环
|
||||
let syncInProgress = false;
|
||||
@@ -1436,6 +1483,8 @@
|
||||
chartHeight = volumeChartContainer.clientHeight;
|
||||
} else if (chartType === 'macd') {
|
||||
chartHeight = macdChartContainer ? macdChartContainer.clientHeight : 0;
|
||||
} else if (chartType === 'atr') {
|
||||
chartHeight = atrChartContainer ? atrChartContainer.clientHeight : 0;
|
||||
} else {
|
||||
chartHeight = mainChartContainer.clientHeight;
|
||||
}
|
||||
@@ -1585,8 +1634,29 @@
|
||||
|
||||
// 创建MACD图表(如果需要)
|
||||
let macdChart = null;
|
||||
let atrChart = null;
|
||||
if (showMacd) {
|
||||
macdChart = LightweightCharts.createChart(macdChartContainer, createChartOptions(false, 'macd'));
|
||||
macdChart = LightweightCharts.createChart(macdChartContainer, createChartOptions(false, 'macd'));
|
||||
atrChart = LightweightCharts.createChart(atrChartContainer, createChartOptions(false, 'atr'));
|
||||
|
||||
// 强制所有图表使用相同的时间刻度配置,确保时间对齐
|
||||
const timeScaleOptions = {
|
||||
borderVisible: false,
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
tickMarkMaxCharacterLength: 8,
|
||||
};
|
||||
|
||||
// 应用统一的时间刻度到所有图表
|
||||
mainChart.timeScale().applyOptions(timeScaleOptions);
|
||||
volumeChart.timeScale().applyOptions(timeScaleOptions);
|
||||
macdChart.timeScale().applyOptions(timeScaleOptions);
|
||||
atrChart.timeScale().applyOptions(timeScaleOptions);
|
||||
|
||||
mainChart.timeScale().applyOptions(timeScaleOptions);
|
||||
volumeChart.timeScale().applyOptions(timeScaleOptions);
|
||||
macdChart.timeScale().applyOptions(timeScaleOptions);
|
||||
atrChart.timeScale().applyOptions(timeScaleOptions);
|
||||
}
|
||||
|
||||
// 创建蜡烛图系列并设置数据
|
||||
@@ -1623,9 +1693,10 @@
|
||||
// 转换成交量数据 - 始终使用主K线周期数据
|
||||
let volumes = [];
|
||||
// 使用与K线和MACD相同的数据源选择逻辑
|
||||
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
// 强制使用主周期数据源,确保与MACD、ATR图表时间完全一致
|
||||
const volumeDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
|
||||
console.log('成交量数据源选择:', useElementPeriod ? '次周期' : '主周期');
|
||||
console.log('成交量数据源: 主周期(强制与MACD、ATR一致)');
|
||||
console.log('成交量数据长度:', volumeDataSource.length);
|
||||
|
||||
if (volumeDataSource && Array.isArray(volumeDataSource)) {
|
||||
@@ -1688,13 +1759,11 @@
|
||||
const signalData = [];
|
||||
const histogramData = [];
|
||||
|
||||
// 使用与K线数据相同的数据源来确保时间对齐
|
||||
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
|
||||
const macdDataSource = useElementPeriod ?
|
||||
(currentData.element_macd || currentData.macd) : // 如果有次周期MACD数据则使用,否则使用主周期
|
||||
currentData.macd; // 主周期使用主周期MACD数据
|
||||
// 强制使用主周期数据源,确保与ATR图表时间完全一致
|
||||
const klineDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
const macdDataSource = currentData.macd; // 总是使用主周期MACD数据
|
||||
|
||||
console.log('MACD数据源选择:', useElementPeriod ? '次周期' : '主周期');
|
||||
console.log('MACD数据源: 主周期(强制与ATR一致)');
|
||||
console.log('K线数据长度:', klineDataSource.length);
|
||||
console.log('MACD数据:', macdDataSource);
|
||||
|
||||
@@ -1735,6 +1804,71 @@
|
||||
tvWidget.series.histogramSeries = histogramSeries;
|
||||
}
|
||||
|
||||
// 添加ATR图表 - 独立显示ATR数据
|
||||
if (showMacd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 创建ATR线
|
||||
const atrLineSeries = atrChart.addLineSeries({
|
||||
color: '#FF9800',
|
||||
lineWidth: 2,
|
||||
title: 'ATR',
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
baseLineVisible: false,
|
||||
crosshairMarkerVisible: false,
|
||||
pointMarkersVisible: false,
|
||||
});
|
||||
|
||||
// 提取ATR数据
|
||||
const atrData = [];
|
||||
|
||||
// 强制使用主周期数据源,确保与MACD图表时间完全一致
|
||||
const klineDataSource = currentData.kline_data; // 总是使用主周期数据
|
||||
|
||||
console.log('ATR数据源: 主周期(强制与MACD一致)');
|
||||
console.log('ATR K线数据长度:', klineDataSource.length);
|
||||
|
||||
// 创建一个隐藏的基础数据系列来保持时间轴对齐
|
||||
const baseLineSeries = atrChart.addLineSeries({
|
||||
color: 'transparent',
|
||||
lineWidth: 0,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
baseLineVisible: false,
|
||||
crosshairMarkerVisible: false,
|
||||
pointMarkersVisible: false,
|
||||
visible: false
|
||||
});
|
||||
|
||||
// 添加完整的时间数据(透明)来保持时间轴连续
|
||||
const baseData = [];
|
||||
for (let i = 0; i < klineDataSource.length; i++) {
|
||||
const kline = klineDataSource[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
baseData.push({
|
||||
time: timestamp,
|
||||
value: 0.1 // 使用很小的值,不会显示
|
||||
});
|
||||
|
||||
// 只添加ATR>0的有效数据到可见系列
|
||||
if (kline.atr && kline.atr > 0) {
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: kline.atr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 先设置基础数据保持时间轴
|
||||
baseLineSeries.setData(baseData);
|
||||
// 再设置ATR可见数据
|
||||
atrLineSeries.setData(atrData);
|
||||
|
||||
console.log('ATR基础数据点数:', baseData.length);
|
||||
console.log('ATR可见数据点数:', atrData.length);
|
||||
|
||||
tvWidget.series.atrLineSeries = atrLineSeries;
|
||||
}
|
||||
|
||||
// 实现三图联动滚动
|
||||
|
||||
// 同步图表的时间范围
|
||||
@@ -1748,7 +1882,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === macdChart ? 'MACD图' : 'ATR图');
|
||||
|
||||
try {
|
||||
if (sourceChart && sourceChart.timeScale) {
|
||||
@@ -1787,6 +1922,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 同步ATR图
|
||||
if (showMacd && atrChart && sourceChart !== atrChart && atrChart.timeScale) {
|
||||
try {
|
||||
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
console.log('✅ ATR图同步完成');
|
||||
} catch (e) {
|
||||
console.error('❌ ATR图同步失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前的可见范围到全局状态
|
||||
if (tvWidget && tvWidget.state) {
|
||||
tvWidget.state.logicalRange = logicalRange;
|
||||
@@ -1812,7 +1957,8 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
macd: false
|
||||
macd: false,
|
||||
atr: false
|
||||
};
|
||||
|
||||
// 全局鼠标抬起事件(只添加一次)
|
||||
@@ -1828,10 +1974,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
const addChartSyncEvents = (chartContainer, chart) => {
|
||||
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图');
|
||||
console.log('为图表添加同步事件监听:',
|
||||
chart === mainChart ? '主图' :
|
||||
chart === volumeChart ? '成交量图' :
|
||||
chart === macdChart ? 'MACD图' : 'ATR图');
|
||||
|
||||
// 确定当前图表类型
|
||||
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd';
|
||||
const chartType = chart === mainChart ? 'main' :
|
||||
chart === volumeChart ? 'volume' :
|
||||
chart === macdChart ? 'macd' : 'atr';
|
||||
|
||||
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -1890,6 +2041,9 @@
|
||||
if (showMacd && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
if (showMacd && atrChart) {
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
}
|
||||
|
||||
// 窗口大小变化时重绘图表
|
||||
window.addEventListener('resize', () => {
|
||||
@@ -1913,6 +2067,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 调整ATR图大小
|
||||
if (showMacd && atrChart && atrChartContainer) {
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
}
|
||||
|
||||
// 重新同步 - 使用主图作为同步源
|
||||
setTimeout(() => {
|
||||
if (mainChart) {
|
||||
@@ -3035,15 +3197,50 @@
|
||||
if (showMacd && macdChart) {
|
||||
macdChart.timeScale().fitContent();
|
||||
}
|
||||
if (showMacd && atrChart) {
|
||||
atrChart.timeScale().fitContent();
|
||||
}
|
||||
|
||||
// 保存图表对象
|
||||
tvWidget.mainChart = mainChart;
|
||||
tvWidget.volumeChart = volumeChart;
|
||||
tvWidget.macdChart = macdChart;
|
||||
tvWidget.atrChart = atrChart;
|
||||
tvWidget.state.isInitialized = true;
|
||||
|
||||
// 绑定同步事件
|
||||
bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd);
|
||||
bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, atrChartContainer, mainChart, volumeChart, macdChart, atrChart, showMacd);
|
||||
|
||||
// 强制同步所有图表时间范围(确保时间对齐)
|
||||
setTimeout(() => {
|
||||
if (mainChart && mainChart.timeScale) {
|
||||
// 先让所有图表自适应内容
|
||||
mainChart.timeScale().fitContent();
|
||||
if (volumeChart) volumeChart.timeScale().fitContent();
|
||||
if (showMacd && macdChart) macdChart.timeScale().fitContent();
|
||||
if (showMacd && atrChart) atrChart.timeScale().fitContent();
|
||||
|
||||
// 等待适应完成后强制同步时间范围
|
||||
setTimeout(() => {
|
||||
const mainRange = mainChart.timeScale().getVisibleLogicalRange();
|
||||
console.log('主图时间范围:', mainRange);
|
||||
|
||||
if (mainRange && volumeChart && volumeChart.timeScale) {
|
||||
volumeChart.timeScale().setVisibleLogicalRange(mainRange);
|
||||
console.log('成交量图时间范围已同步');
|
||||
}
|
||||
if (mainRange && showMacd && macdChart && macdChart.timeScale) {
|
||||
macdChart.timeScale().setVisibleLogicalRange(mainRange);
|
||||
console.log('MACD图时间范围已同步');
|
||||
}
|
||||
if (mainRange && showMacd && atrChart && atrChart.timeScale) {
|
||||
atrChart.timeScale().setVisibleLogicalRange(mainRange);
|
||||
console.log('ATR图时间范围已同步');
|
||||
}
|
||||
console.log('所有图表时间范围已强制同步完成');
|
||||
}, 50);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// 只有在时间输入框都为空时才设置图表默认时间范围
|
||||
if (!$('#start_time').val() && !$('#end_time').val()) {
|
||||
@@ -3051,7 +3248,7 @@
|
||||
}
|
||||
|
||||
// 添加买卖点提示
|
||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd);
|
||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, macdChartContainer, atrChartContainer, volumeChart, macdChart, atrChart, showMacd);
|
||||
|
||||
console.log('图表初始化完成');
|
||||
} catch (e) {
|
||||
@@ -3128,18 +3325,9 @@
|
||||
tvWidget.series.lineSeries.setData(lineData);
|
||||
}
|
||||
|
||||
// 更新成交量数据
|
||||
// 更新成交量数据 - 强制使用主周期数据源确保时间对齐
|
||||
let volumes = [];
|
||||
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
|
||||
volumes = currentData.element_kline_data.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
time: timestamp,
|
||||
value: parseFloat(kline.volume),
|
||||
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)',
|
||||
};
|
||||
});
|
||||
} else if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
if (currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
volumes = currentData.kline_data.map(kline => {
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
return {
|
||||
@@ -3191,6 +3379,26 @@
|
||||
tvWidget.series.histogramSeries.setData(histogramData);
|
||||
}
|
||||
|
||||
// 更新ATR数据 - 保持时间轴对齐
|
||||
if (currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.atrLineSeries) {
|
||||
const atrData = [];
|
||||
|
||||
for (let i = 0; i < currentData.kline_data.length; i++) {
|
||||
const kline = currentData.kline_data[i];
|
||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||
|
||||
// 只添加ATR>0的有效数据,不显示ATR=0的点
|
||||
if (kline.atr && kline.atr > 0) {
|
||||
atrData.push({
|
||||
time: timestamp,
|
||||
value: kline.atr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tvWidget.series.atrLineSeries.setData(atrData);
|
||||
}
|
||||
|
||||
// 先恢复可视范围,避免先显示默认范围再跳转的跳跃效果
|
||||
if (tvWidget.mainChart && (tvWidget.state.logicalRange || tvWidget.state.visibleRange)) {
|
||||
console.log('回放模式:优先恢复视图范围以避免跳跃');
|
||||
@@ -3206,10 +3414,12 @@
|
||||
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);
|
||||
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
} else if (tvWidget.state.visibleRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
@@ -3231,7 +3441,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd) {
|
||||
function bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, atrChartContainer, mainChart, volumeChart, macdChart, atrChart, showMacd) {
|
||||
// 防止同步过程中的无限循环
|
||||
let syncInProgress = false;
|
||||
|
||||
@@ -3239,7 +3449,8 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
macd: false
|
||||
macd: false,
|
||||
atr: false
|
||||
};
|
||||
|
||||
// 同步图表的时间范围
|
||||
@@ -3253,7 +3464,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === macdChart ? 'MACD图' : 'ATR图');
|
||||
|
||||
try {
|
||||
if (sourceChart && sourceChart.timeScale) {
|
||||
@@ -3292,6 +3504,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 同步ATR图
|
||||
if (showMacd && atrChart && sourceChart !== atrChart && atrChart.timeScale) {
|
||||
try {
|
||||
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
console.log('✅ ATR图同步完成');
|
||||
} catch (e) {
|
||||
console.error('❌ ATR图同步失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前的可见范围到全局状态
|
||||
if (tvWidget && tvWidget.state) {
|
||||
tvWidget.state.logicalRange = logicalRange;
|
||||
@@ -3315,10 +3537,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
const addChartSyncEvents = (chartContainer, chart) => {
|
||||
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图');
|
||||
console.log('为图表添加同步事件监听:',
|
||||
chart === mainChart ? '主图' :
|
||||
chart === volumeChart ? '成交量图' :
|
||||
chart === macdChart ? 'MACD图' : 'ATR图');
|
||||
|
||||
// 确定当前图表类型
|
||||
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd';
|
||||
const chartType = chart === mainChart ? 'main' :
|
||||
chart === volumeChart ? 'volume' :
|
||||
chart === macdChart ? 'macd' : 'atr';
|
||||
|
||||
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -3381,6 +3608,9 @@
|
||||
if (showMacd && macdChartContainer && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
if (showMacd && atrChartContainer && atrChart) {
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
}
|
||||
|
||||
// 窗口大小变化时重绘图表
|
||||
window.addEventListener('resize', () => {
|
||||
@@ -3408,6 +3638,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 调整ATR图大小
|
||||
if (showMacd && atrChart && atrChartContainer) {
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
}
|
||||
|
||||
// 重新同步 - 使用主图作为同步源
|
||||
setTimeout(() => {
|
||||
if (mainChart) {
|
||||
@@ -3417,7 +3655,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd) {
|
||||
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, macdChartContainer, atrChartContainer, volumeChart, macdChart, atrChart, showMacd) {
|
||||
// 调试变量
|
||||
window.debugMode = true;
|
||||
|
||||
@@ -3451,6 +3689,8 @@
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
|
||||
// 获取时间对应的坐标位置
|
||||
const mainTimeCoordinate = mainChart.timeScale().timeToCoordinate(param.time);
|
||||
@@ -3495,6 +3735,26 @@
|
||||
document.body.appendChild(macdLine);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有ATR图,也在ATR图上绘制垂直线
|
||||
if (showMacd && 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('十字线同步出错:', e);
|
||||
@@ -3506,6 +3766,8 @@
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
console.debug('清除十字线时出错:', e);
|
||||
}
|
||||
@@ -4037,6 +4299,7 @@
|
||||
tvWidget.mainChart = null;
|
||||
tvWidget.volumeChart = null;
|
||||
tvWidget.macdChart = null;
|
||||
tvWidget.atrChart = null;
|
||||
// 重置系列数据
|
||||
tvWidget.series = {
|
||||
candleSeries: null,
|
||||
@@ -4045,6 +4308,7 @@
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
atrLineSeries: null,
|
||||
mainBiSeries: [],
|
||||
mainSegSeries: [],
|
||||
mainZsSeries: [],
|
||||
|
||||
Reference in New Issue
Block a user