Add replay good now
This commit is contained in:
+382
-262
@@ -3191,22 +3191,38 @@
|
||||
tvWidget.series.histogramSeries.setData(histogramData);
|
||||
}
|
||||
|
||||
// 重新显示笔、线段和中枢等图形
|
||||
redrawFractalElements();
|
||||
|
||||
// 恢复之前的可视范围
|
||||
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) {
|
||||
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.mainChart && (tvWidget.state.logicalRange || tvWidget.state.visibleRange)) {
|
||||
console.log('回放模式:优先恢复视图范围以避免跳跃');
|
||||
|
||||
// 在回放模式下,图表已锁定,不设置范围
|
||||
if (isReplaying) {
|
||||
// 回放模式下跳过范围设置,因为图表已锁定
|
||||
console.log('回放模式:跳过范围设置,图表已锁定');
|
||||
} else {
|
||||
// 非回放模式下延迟应用
|
||||
setTimeout(() => {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新显示笔、线段和中枢等图形
|
||||
if (isReplaying) {
|
||||
// 回放模式下,直接redraw,图表已锁定
|
||||
redrawFractalElements();
|
||||
} else {
|
||||
redrawFractalElements();
|
||||
}
|
||||
|
||||
console.log('增量更新图表完成');
|
||||
} catch (e) {
|
||||
console.error('增量更新图表错误,回退到完全重绘:', e);
|
||||
@@ -4319,25 +4335,44 @@
|
||||
// 调用初始化函数
|
||||
initTradingView($('#symbol').val(), $('#timeframe').val());
|
||||
|
||||
// 恢复原始可见范围
|
||||
setTimeout(() => {
|
||||
if (tvWidget && tvWidget.mainChart) {
|
||||
console.log('恢复图表可见范围...');
|
||||
if (logicalRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
} else if (visibleRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(visibleRange);
|
||||
// 在回放模式下立即恢复范围,避免跳跃效果
|
||||
if (isReplaying) {
|
||||
// 回放模式下立即设置范围,减少延迟
|
||||
setTimeout(() => {
|
||||
if (tvWidget && tvWidget.mainChart) {
|
||||
console.log('回放模式:立即恢复图表可见范围...');
|
||||
if (logicalRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
} else if (visibleRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(visibleRange);
|
||||
}
|
||||
console.log('图表可见范围已恢复(回放模式)');
|
||||
}
|
||||
|
||||
console.log('图表可见范围已恢复');
|
||||
} else {
|
||||
console.error('恢复图表可见范围失败 - 图表未初始化');
|
||||
}
|
||||
}, 200);
|
||||
}, 50); // 回放模式下减少延迟到50ms
|
||||
} else {
|
||||
// 非回放模式下保持原有的延迟
|
||||
setTimeout(() => {
|
||||
if (tvWidget && tvWidget.mainChart) {
|
||||
console.log('正常模式:恢复图表可见范围...');
|
||||
if (logicalRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange);
|
||||
} else if (visibleRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(visibleRange);
|
||||
}
|
||||
console.log('图表可见范围已恢复(正常模式)');
|
||||
} else {
|
||||
console.error('恢复图表可见范围失败 - 图表未初始化');
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
console.log('重绘分形元素 - 完成');
|
||||
}
|
||||
@@ -4617,7 +4652,7 @@
|
||||
const elementTimeframe = $('#elementTimeframe').val() || '1m';
|
||||
|
||||
// 显示回放状态
|
||||
$('#replayStatus').text('正在加载数据...');
|
||||
$('#replayStatus').text('正在生成回放数据...');
|
||||
$('#replayProgress').show();
|
||||
$('.progress-bar').css('width', '0%');
|
||||
|
||||
@@ -4625,62 +4660,51 @@
|
||||
$('#startReplay').hide();
|
||||
$('#stopReplay').show();
|
||||
|
||||
// 尝试使用当前已有数据
|
||||
if (currentData && currentData.kline_data && currentData.kline_data.length > 0) {
|
||||
const currentStart = new Date(currentData.kline_data[0].date).getTime();
|
||||
const currentEnd = new Date(currentData.kline_data[currentData.kline_data.length - 1].date).getTime();
|
||||
|
||||
// 检查当前数据是否覆盖所需的时间范围
|
||||
if (currentStart <= replayStartTime && currentEnd >= replayEndTime) {
|
||||
console.log('使用当前数据进行回放,无需重新请求');
|
||||
replayData = currentData;
|
||||
initReplay();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求整个时间范围的数据
|
||||
// 使用新的回放数据API
|
||||
$.ajax({
|
||||
url: '/api/analyze',
|
||||
url: '/api/replay_data',
|
||||
data: {
|
||||
symbol: symbol,
|
||||
timeframe: timeframe,
|
||||
timezone: timezone,
|
||||
element_timeframe: elementTimeframe,
|
||||
start_time: replayStartTime,
|
||||
end_time: replayEndTime,
|
||||
elements_only: false
|
||||
end_time: replayEndTime
|
||||
},
|
||||
success: function(data) {
|
||||
// 检查是否有有效数据
|
||||
if (!data || !data.kline_data || data.kline_data.length === 0) {
|
||||
$('#replayStatus').text('服务器返回的数据无效');
|
||||
if (!data || !data.replay_data || Object.keys(data.replay_data).length === 0) {
|
||||
$('#replayStatus').text('服务器返回的回放数据无效');
|
||||
$('#replayProgress').hide();
|
||||
$('#startReplay').show();
|
||||
$('#stopReplay').hide();
|
||||
console.error('服务器返回的数据无效:', data);
|
||||
console.error('服务器返回的回放数据无效:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存完整数据
|
||||
replayData = data;
|
||||
// 保存回放数据
|
||||
replayData = data.replay_data;
|
||||
totalReplaySteps = data.total_length;
|
||||
|
||||
console.log('回放数据加载完成,总步骤数:', totalReplaySteps);
|
||||
console.log('回放数据已从后台预计算完成,确保不会使用未来数据');
|
||||
|
||||
// 初始化回放
|
||||
initReplay();
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$('#replayStatus').text('加载数据失败');
|
||||
$('#replayStatus').text('生成回放数据失败');
|
||||
$('#replayProgress').hide();
|
||||
$('#startReplay').show();
|
||||
$('#stopReplay').hide();
|
||||
alert('加载数据失败: ' + (jqXHR.responseJSON?.error || errorThrown));
|
||||
alert('生成回放数据失败: ' + (jqXHR.responseJSON?.error || errorThrown));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化回放
|
||||
function initReplay() {
|
||||
if (!replayData || !replayData.kline_data || replayData.kline_data.length === 0) {
|
||||
if (!replayData || Object.keys(replayData).length === 0) {
|
||||
$('#replayStatus').text('没有可回放的数据');
|
||||
$('#replayProgress').hide();
|
||||
$('#startReplay').show();
|
||||
@@ -4691,98 +4715,208 @@
|
||||
// 设置回放变量
|
||||
isReplaying = true;
|
||||
currentReplayIndex = 0;
|
||||
totalReplaySteps = replayData.kline_data.length;
|
||||
// totalReplaySteps已经在startDataReplay中设置了
|
||||
|
||||
// 更新回放状态
|
||||
$('#replayStatus').text(`准备回放 (0/${totalReplaySteps})`);
|
||||
$('.progress-bar').css('width', '0%');
|
||||
|
||||
// 准备初始数据
|
||||
prepareInitialReplayData();
|
||||
// 预先锁定图表,防止任何自动调整
|
||||
lockChartsForReplay();
|
||||
|
||||
// 准备初始数据(显示第一个时间点的数据)
|
||||
if (replayData[0]) {
|
||||
currentData = replayData[0];
|
||||
updateTradingViewData();
|
||||
updateTables(currentData);
|
||||
}
|
||||
|
||||
// 开始回放
|
||||
continueDataReplay();
|
||||
}
|
||||
|
||||
// 准备初始回放数据
|
||||
function prepareInitialReplayData() {
|
||||
// 创建初始数据集,只包含第一根K线
|
||||
const initialData = $.extend(true, {}, replayData);
|
||||
// 为回放优化图表设置,减少跳跃效果
|
||||
function optimizeChartForReplay() {
|
||||
if (!tvWidget || !tvWidget.mainChart) return;
|
||||
|
||||
// 确保第一根K线数据有效
|
||||
if (!replayData.kline_data || replayData.kline_data.length === 0) {
|
||||
console.error('无有效K线数据用于回放');
|
||||
$('#replayStatus').text('无有效数据用于回放');
|
||||
$('#replayProgress').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
initialData.kline_data = [replayData.kline_data[0]];
|
||||
|
||||
// 确保初始K线数据的所有字段都有值
|
||||
const firstKline = initialData.kline_data[0];
|
||||
if (firstKline) {
|
||||
// 确保K线数据的每个字段都是有效的数值
|
||||
['open', 'high', 'low', 'close', 'volume'].forEach(field => {
|
||||
if (firstKline[field] === null || firstKline[field] === undefined || isNaN(firstKline[field])) {
|
||||
console.warn(`K线数据的${field}字段无效,设置为默认值`);
|
||||
firstKline[field] = field === 'volume' ? 0 : firstKline.close || 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清空其他数据列表,因为还没有处理
|
||||
initialData.bi_list = [];
|
||||
initialData.seg_list = [];
|
||||
initialData.zs_list = [];
|
||||
initialData.uncompleted_zs_list = [];
|
||||
initialData.element_bi_list = [];
|
||||
initialData.element_seg_list = [];
|
||||
initialData.element_zs_list = [];
|
||||
initialData.element_uncompleted_zs_list = [];
|
||||
initialData.macd_divergence = [];
|
||||
initialData.klc_fx_type = [];
|
||||
|
||||
// 初始化MACD数据
|
||||
if (initialData.macd && initialData.macd.length > 0) {
|
||||
initialData.macd = [initialData.macd[0]];
|
||||
try {
|
||||
console.log('优化图表设置以减少回放跳跃...');
|
||||
|
||||
// 确保MACD数据有效
|
||||
const firstMacd = initialData.macd[0];
|
||||
if (firstMacd) {
|
||||
['dif', 'dea', 'macd'].forEach(field => {
|
||||
if (firstMacd[field] === null || firstMacd[field] === undefined || isNaN(firstMacd[field])) {
|
||||
console.warn(`MACD数据的${field}字段无效,设置为0`);
|
||||
firstMacd[field] = 0;
|
||||
// 临时禁用动画,提高性能
|
||||
if (tvWidget.mainChart.applyOptions) {
|
||||
tvWidget.mainChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 5, // 减少右侧偏移
|
||||
barSpacing: 6, // 固定K线间距
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
lockVisibleTimeRangeOnResize: true // 窗口大小改变时锁定可视范围
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表,固定坐标轴范围
|
||||
currentData = initialData;
|
||||
|
||||
// 清除当前图表
|
||||
if (tvWidget.state.isInitialized) {
|
||||
// 保存当前的图表可见范围以供后续使用
|
||||
try {
|
||||
tvWidget.state.visibleRange = tvWidget.mainChart.timeScale().getVisibleRange();
|
||||
tvWidget.state.logicalRange = tvWidget.mainChart.timeScale().getVisibleLogicalRange();
|
||||
} catch (e) {
|
||||
console.error('保存图表范围失败', e);
|
||||
|
||||
if (tvWidget.volumeChart && tvWidget.volumeChart.applyOptions) {
|
||||
tvWidget.volumeChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 5,
|
||||
barSpacing: 6,
|
||||
lockVisibleTimeRangeOnResize: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (tvWidget.macdChart && tvWidget.macdChart.applyOptions) {
|
||||
tvWidget.macdChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 5,
|
||||
barSpacing: 6,
|
||||
lockVisibleTimeRangeOnResize: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('图表优化设置完成');
|
||||
} catch (e) {
|
||||
console.error('优化图表设置失败:', e);
|
||||
}
|
||||
|
||||
// 刷新图表
|
||||
refreshChart(initialData);
|
||||
|
||||
// 固定y轴范围
|
||||
fixYAxisRange();
|
||||
}
|
||||
|
||||
// 锁定图表,阻止所有自动调整行为
|
||||
function lockChartsForReplay() {
|
||||
try {
|
||||
if (tvWidget && tvWidget.mainChart) {
|
||||
// 完全禁用自动缩放和范围调整
|
||||
tvWidget.mainChart.applyOptions({
|
||||
handleScroll: false,
|
||||
handleScale: false,
|
||||
timeScale: {
|
||||
rightOffset: 0,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
lockVisibleTimeRangeOnResize: true,
|
||||
borderVisible: false
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: false,
|
||||
borderVisible: false
|
||||
}
|
||||
});
|
||||
|
||||
if (tvWidget.volumeChart) {
|
||||
tvWidget.volumeChart.applyOptions({
|
||||
handleScroll: false,
|
||||
handleScale: false,
|
||||
timeScale: {
|
||||
rightOffset: 0,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
lockVisibleTimeRangeOnResize: true,
|
||||
borderVisible: false
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: false,
|
||||
borderVisible: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (tvWidget.macdChart) {
|
||||
tvWidget.macdChart.applyOptions({
|
||||
handleScroll: false,
|
||||
handleScale: false,
|
||||
timeScale: {
|
||||
rightOffset: 0,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
lockVisibleTimeRangeOnResize: true,
|
||||
borderVisible: false
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: false,
|
||||
borderVisible: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('图表已锁定,禁用所有自动调整');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('锁定图表失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 解锁图表,恢复正常交互
|
||||
function unlockChartsAfterReplay() {
|
||||
try {
|
||||
if (tvWidget && tvWidget.mainChart) {
|
||||
// 恢复正常的交互和自动调整
|
||||
tvWidget.mainChart.applyOptions({
|
||||
handleScroll: true,
|
||||
handleScale: true,
|
||||
timeScale: {
|
||||
rightOffset: 12,
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
lockVisibleTimeRangeOnResize: false,
|
||||
borderVisible: true
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: true,
|
||||
borderVisible: true
|
||||
}
|
||||
});
|
||||
|
||||
if (tvWidget.volumeChart) {
|
||||
tvWidget.volumeChart.applyOptions({
|
||||
handleScroll: true,
|
||||
handleScale: true,
|
||||
timeScale: {
|
||||
rightOffset: 12,
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
lockVisibleTimeRangeOnResize: false,
|
||||
borderVisible: true
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: true,
|
||||
borderVisible: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (tvWidget.macdChart) {
|
||||
tvWidget.macdChart.applyOptions({
|
||||
handleScroll: true,
|
||||
handleScale: true,
|
||||
timeScale: {
|
||||
rightOffset: 12,
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
lockVisibleTimeRangeOnResize: false,
|
||||
borderVisible: true
|
||||
},
|
||||
priceScale: {
|
||||
autoScale: true,
|
||||
borderVisible: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('图表已解锁,恢复正常交互');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解锁图表失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 固定y轴范围,保持图表不因数据变化而变化
|
||||
function fixYAxisRange() {
|
||||
if (!tvWidget.mainChart) return;
|
||||
if (!tvWidget.mainChart || !replayData || !replayData.kline_data || replayData.kline_data.length === 0) {
|
||||
console.log('无法固定Y轴范围:图表或数据不可用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取全部数据的最高价和最低价,为了适当的范围
|
||||
let minPrice = Infinity;
|
||||
@@ -4793,10 +4927,16 @@
|
||||
const high = parseFloat(kline.high);
|
||||
const low = parseFloat(kline.low);
|
||||
|
||||
if (high > maxPrice) maxPrice = high;
|
||||
if (low < minPrice) minPrice = low;
|
||||
if (!isNaN(high) && high > maxPrice) maxPrice = high;
|
||||
if (!isNaN(low) && low < minPrice) minPrice = low;
|
||||
});
|
||||
|
||||
// 检查是否获得了有效的价格范围
|
||||
if (minPrice === Infinity || maxPrice === -Infinity) {
|
||||
console.warn('无法获得有效的价格范围,跳过Y轴固定');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加一些边距(约5%)
|
||||
const priceRange = maxPrice - minPrice;
|
||||
maxPrice += priceRange * 0.05;
|
||||
@@ -4815,6 +4955,7 @@
|
||||
minimumValue: minPrice,
|
||||
maximumValue: maxPrice
|
||||
});
|
||||
console.log(`Y轴范围已固定: ${minPrice.toFixed(2)} - ${maxPrice.toFixed(2)}`);
|
||||
} catch (e) {
|
||||
console.error('设置Y轴范围失败:', e);
|
||||
}
|
||||
@@ -4847,164 +4988,104 @@
|
||||
currentReplayIndex++;
|
||||
updateReplayStatus();
|
||||
|
||||
// 创建截止到当前索引的数据子集
|
||||
const subsetData = createDataSubset(currentReplayIndex);
|
||||
// 直接从后台预计算的数据中取出当前时间点的数据
|
||||
const currentStepData = replayData[currentReplayIndex - 1]; // 索引从0开始
|
||||
|
||||
// 更新图表
|
||||
currentData = subsetData;
|
||||
refreshChart(subsetData);
|
||||
if (!currentStepData) {
|
||||
console.error(`回放步骤 ${currentReplayIndex} 没有数据`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 固定Y轴范围
|
||||
fixYAxisRange();
|
||||
// 更新当前数据
|
||||
currentData = currentStepData;
|
||||
|
||||
// 保持最新数据可见
|
||||
keepLatestDataVisible();
|
||||
// 直接更新数据,图表已锁定不会跳跃
|
||||
console.log(`回放步骤 ${currentReplayIndex}/${totalReplaySteps}:使用预计算数据`);
|
||||
|
||||
try {
|
||||
updateTradingViewData();
|
||||
|
||||
// 表格更新频率降低,每20步更新一次
|
||||
if (currentReplayIndex % 20 === 0 || currentReplayIndex === totalReplaySteps) {
|
||||
updateTables(currentData);
|
||||
}
|
||||
|
||||
// 不再需要频繁的Y轴调整和视图调整,因为图表已锁定
|
||||
} catch (e) {
|
||||
console.error('回放步骤更新失败:', e);
|
||||
}
|
||||
|
||||
// 如果到达最后一步,停止回放
|
||||
if (currentReplayIndex >= totalReplaySteps) {
|
||||
$('#replayStatus').text('回放完成');
|
||||
// 回放完成时解锁图表
|
||||
unlockChartsAfterReplay();
|
||||
pauseDataReplay();
|
||||
}
|
||||
}
|
||||
|
||||
// 保持最新数据可见
|
||||
function keepLatestDataVisible() {
|
||||
if (!tvWidget.mainChart) return;
|
||||
if (!tvWidget.mainChart || !currentData || !currentData.kline_data || currentData.kline_data.length === 0) return;
|
||||
|
||||
try {
|
||||
// 获取当前数据的最后一个时间点
|
||||
if (currentData && currentData.kline_data && currentData.kline_data.length > 0) {
|
||||
const lastBar = currentData.kline_data[currentData.kline_data.length - 1];
|
||||
const lastTime = Math.floor(new Date(lastBar.date).getTime() / 1000);
|
||||
// 在回放模式下,更平滑地调整视图范围
|
||||
const lastBar = currentData.kline_data[currentData.kline_data.length - 1];
|
||||
const lastTime = Math.floor(new Date(lastBar.date).getTime() / 1000);
|
||||
|
||||
// 如果已经有保存的视图范围,则基于它进行微调,避免大幅跳跃
|
||||
if (tvWidget.state.logicalRange) {
|
||||
const currentLogicalRange = tvWidget.mainChart.timeScale().getVisibleLogicalRange();
|
||||
const currentTimeRange = tvWidget.mainChart.timeScale().getVisibleRange();
|
||||
|
||||
// 计算时间范围
|
||||
const barCount = Math.min(50, currentData.kline_data.length); // 显示最近的50根K线或所有K线
|
||||
if (currentTimeRange && currentTimeRange.to) {
|
||||
// 检查最新数据是否已经在可视范围内
|
||||
const timeDiff = lastTime - currentTimeRange.to;
|
||||
|
||||
// 只有当最新数据超出当前可视范围的一定程度时才调整
|
||||
if (timeDiff > 0) {
|
||||
const barCount = Math.min(50, currentData.kline_data.length);
|
||||
const firstVisibleIndex = Math.max(0, currentData.kline_data.length - barCount);
|
||||
const firstVisibleBar = currentData.kline_data[firstVisibleIndex];
|
||||
const firstTime = Math.floor(new Date(firstVisibleBar.date).getTime() / 1000);
|
||||
|
||||
// 平滑地扩展视图范围,而不是突然跳跃
|
||||
const newRange = {
|
||||
from: Math.min(currentTimeRange.from || firstTime, firstTime),
|
||||
to: lastTime
|
||||
};
|
||||
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(newRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(newRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(newRange);
|
||||
|
||||
console.log('平滑调整视图范围:', newRange);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 初始情况,设置默认的视图范围
|
||||
const barCount = Math.min(50, currentData.kline_data.length);
|
||||
const firstVisibleIndex = Math.max(0, currentData.kline_data.length - barCount);
|
||||
const firstVisibleBar = currentData.kline_data[firstVisibleIndex];
|
||||
const firstTime = Math.floor(new Date(firstVisibleBar.date).getTime() / 1000);
|
||||
|
||||
// 设置时间轴范围
|
||||
tvWidget.mainChart.timeScale().setVisibleRange({
|
||||
const newRange = {
|
||||
from: firstTime,
|
||||
to: lastTime
|
||||
});
|
||||
};
|
||||
|
||||
tvWidget.mainChart.timeScale().setVisibleRange(newRange);
|
||||
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(newRange);
|
||||
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(newRange);
|
||||
|
||||
console.log('设置初始视图范围:', newRange);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('保持最新数据可见失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建数据子集
|
||||
function createDataSubset(endIndex) {
|
||||
// 创建数据副本
|
||||
const subsetData = $.extend(true, {}, replayData);
|
||||
|
||||
// 截取K线数据
|
||||
subsetData.kline_data = replayData.kline_data.slice(0, endIndex);
|
||||
|
||||
// 验证K线数据
|
||||
subsetData.kline_data.forEach((kline, index) => {
|
||||
['open', 'high', 'low', 'close', 'volume'].forEach(field => {
|
||||
if (kline[field] === null || kline[field] === undefined || isNaN(kline[field])) {
|
||||
console.warn(`K线数据[${index}]的${field}字段无效,使用替代值`);
|
||||
// 对于价格字段,尝试使用其他价格字段,或者前一个K线的对应值
|
||||
if (field !== 'volume') {
|
||||
if (field === 'open' && kline.close !== null && !isNaN(kline.close)) {
|
||||
kline[field] = kline.close;
|
||||
} else if (index > 0) {
|
||||
kline[field] = subsetData.kline_data[index-1][field] || 0;
|
||||
} else {
|
||||
kline[field] = 0;
|
||||
}
|
||||
} else {
|
||||
kline[field] = 0; // 对于成交量,设为0
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 处理其他数据结构(笔、线段、中枢等)
|
||||
// 我们需要根据当前K线的时间来过滤其他数据结构
|
||||
try {
|
||||
const currentEndTime = new Date(subsetData.kline_data[endIndex - 1].date).getTime();
|
||||
|
||||
// 过滤笔列表
|
||||
if (subsetData.bi_list && subsetData.bi_list.length) {
|
||||
subsetData.bi_list = filterDataByTime(replayData.bi_list, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤线段列表
|
||||
if (subsetData.seg_list && subsetData.seg_list.length) {
|
||||
subsetData.seg_list = filterDataByTime(replayData.seg_list, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤中枢列表
|
||||
if (subsetData.zs_list && subsetData.zs_list.length) {
|
||||
subsetData.zs_list = filterDataByTime(replayData.zs_list, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤未完成中枢列表
|
||||
if (subsetData.uncompleted_zs_list && subsetData.uncompleted_zs_list.length) {
|
||||
subsetData.uncompleted_zs_list = filterDataByTime(replayData.uncompleted_zs_list, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤小周期数据
|
||||
if (subsetData.element_bi_list && subsetData.element_bi_list.length) {
|
||||
subsetData.element_bi_list = filterDataByTime(replayData.element_bi_list, currentEndTime);
|
||||
}
|
||||
|
||||
if (subsetData.element_seg_list && subsetData.element_seg_list.length) {
|
||||
subsetData.element_seg_list = filterDataByTime(replayData.element_seg_list, currentEndTime);
|
||||
}
|
||||
|
||||
if (subsetData.element_zs_list && subsetData.element_zs_list.length) {
|
||||
subsetData.element_zs_list = filterDataByTime(replayData.element_zs_list, currentEndTime);
|
||||
}
|
||||
|
||||
if (subsetData.element_uncompleted_zs_list && subsetData.element_uncompleted_zs_list.length) {
|
||||
subsetData.element_uncompleted_zs_list = filterDataByTime(replayData.element_uncompleted_zs_list, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤MACD
|
||||
if (subsetData.macd && subsetData.macd.length) {
|
||||
subsetData.macd = replayData.macd.slice(0, endIndex);
|
||||
|
||||
// 验证MACD数据
|
||||
subsetData.macd.forEach((item, index) => {
|
||||
['dif', 'dea', 'macd'].forEach(field => {
|
||||
if (item[field] === null || item[field] === undefined || isNaN(item[field])) {
|
||||
console.warn(`MACD数据[${index}]的${field}字段无效,设置为0`);
|
||||
item[field] = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤MACD背离
|
||||
if (subsetData.macd_divergence && subsetData.macd_divergence.length) {
|
||||
subsetData.macd_divergence = filterDataByTime(replayData.macd_divergence, currentEndTime);
|
||||
}
|
||||
|
||||
// 过滤分型类型
|
||||
if (subsetData.klc_fx_type && subsetData.klc_fx_type.length) {
|
||||
subsetData.klc_fx_type = filterDataByTime(replayData.klc_fx_type, currentEndTime, 'time');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('过滤数据时出错:', e);
|
||||
}
|
||||
|
||||
return subsetData;
|
||||
}
|
||||
|
||||
// 根据时间过滤数据
|
||||
function filterDataByTime(dataList, endTime, timeField = 'end_time') {
|
||||
if (!dataList || !Array.isArray(dataList)) return [];
|
||||
|
||||
return dataList.filter(item => {
|
||||
const itemTime = new Date(item[timeField]).getTime();
|
||||
return itemTime <= endTime;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 更新回放状态显示
|
||||
function updateReplayStatus() {
|
||||
@@ -5044,6 +5125,9 @@
|
||||
currentReplayIndex = 0;
|
||||
replayData = null;
|
||||
|
||||
// 清空当前数据,确保下次回放是干净状态
|
||||
currentData = null;
|
||||
|
||||
// 更新UI
|
||||
$('#replayStatus').text('');
|
||||
$('#replayProgress').hide();
|
||||
@@ -5051,12 +5135,48 @@
|
||||
$('#stopReplay').hide();
|
||||
$('#startReplay').show();
|
||||
|
||||
// 恢复图表自动缩放
|
||||
// 恢复图表自动缩放和正常设置
|
||||
if (tvWidget.mainChart) {
|
||||
tvWidget.mainChart.priceScale().applyOptions({
|
||||
autoScale: true
|
||||
});
|
||||
|
||||
// 恢复图表的正常设置
|
||||
tvWidget.mainChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 12, // 恢复默认右侧偏移
|
||||
barSpacing: 6,
|
||||
fixLeftEdge: false,
|
||||
fixRightEdge: false,
|
||||
lockVisibleTimeRangeOnResize: false // 恢复窗口调整时的自动适应
|
||||
}
|
||||
});
|
||||
|
||||
if (tvWidget.volumeChart) {
|
||||
tvWidget.volumeChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 12,
|
||||
barSpacing: 6,
|
||||
lockVisibleTimeRangeOnResize: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (tvWidget.macdChart) {
|
||||
tvWidget.macdChart.applyOptions({
|
||||
timeScale: {
|
||||
rightOffset: 12,
|
||||
barSpacing: 6,
|
||||
lockVisibleTimeRangeOnResize: false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 解锁图表,恢复正常交互
|
||||
unlockChartsAfterReplay();
|
||||
|
||||
console.log('回放已停止,数据已清空,图表已解锁');
|
||||
}
|
||||
|
||||
// 获取A股股票列表
|
||||
|
||||
Reference in New Issue
Block a user