refactor: 缠论引擎包化与 Web 分层(ECR-001)
将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务; 前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/* chart_view.js — split from chart.js */
|
||||
function updateChart() {
|
||||
// 只显示旋转加载图标
|
||||
$('#refreshLoadingSpinner').show();
|
||||
|
||||
// 获取参数
|
||||
const dataSource = $('#dataSource').val() || 'crypto';
|
||||
let symbol;
|
||||
if (dataSource === 'crypto') {
|
||||
symbol = $('#symbol').val() || 'BTC/USDT:USDT';
|
||||
} else {
|
||||
symbol = $('#astockSymbol').val() || '000001';
|
||||
}
|
||||
|
||||
const timeframe = $('#timeframe').val() || window.DEFAULT_MAIN_TIMEFRAME || '5m';
|
||||
const timezone = $('#timezone').val() || 'Asia/Shanghai';
|
||||
const elementTimeframe = $('#elementTimeframe').val() || window.DEFAULT_ELEMENT_TIMEFRAME || '1m';
|
||||
const subSubTimeframe = $('#subSubTimeframe').val() || '';
|
||||
|
||||
// 确保时区参数有效
|
||||
console.log('更新图表使用时区:', timezone);
|
||||
console.log('数据源:', dataSource, '交易对/股票:', symbol);
|
||||
|
||||
// 如果symbol为空,不发送请求
|
||||
if (!symbol) {
|
||||
console.error('交易对/股票代码不能为空');
|
||||
$('#refreshLoadingSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`更新图表: symbol=${symbol}, timeframe=${timeframe}, elementTimeframe=${elementTimeframe}, timezone=${timezone}`);
|
||||
|
||||
// 获取开始和结束时间(如果已设置)
|
||||
let startTimeMs = null;
|
||||
let endTimeMs = null;
|
||||
|
||||
if ($('#start_time').val()) {
|
||||
startTimeMs = new Date($('#start_time').val()).getTime();
|
||||
}
|
||||
|
||||
if ($('#end_time').val()) {
|
||||
endTimeMs = new Date($('#end_time').val()).getTime();
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
const requestId = ++lastRequestId; // 标记本次请求
|
||||
$.ajax({
|
||||
url: '/api/analyze',
|
||||
data: {
|
||||
symbol: symbol,
|
||||
timeframe: timeframe,
|
||||
timezone: timezone,
|
||||
element_timeframe: elementTimeframe,
|
||||
sub_sub_timeframe: subSubTimeframe || undefined,
|
||||
start_time: startTimeMs,
|
||||
end_time: endTimeMs,
|
||||
elements_only: false,
|
||||
zone_kl_lines: parseInt($('#zoneKlLines').val()) || 1000,
|
||||
include_structure_zones: $('#showMainStructureZone').is(':checked') ? 1 : 0
|
||||
},
|
||||
success: function(data) {
|
||||
// 隐藏加载图标
|
||||
$('#refreshLoadingSpinner').hide();
|
||||
|
||||
// 忽略过期响应
|
||||
if (requestId !== lastRequestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前数据
|
||||
if (currentData) {
|
||||
// 覆盖前断开旧引用,帮助GC尽快回收
|
||||
delete currentData.original_kline_data;
|
||||
delete currentData.original_macd;
|
||||
}
|
||||
currentData = data;
|
||||
|
||||
refreshChart(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
// 隐藏加载图标
|
||||
$('#refreshLoadingSpinner').hide();
|
||||
|
||||
// 显示错误信息
|
||||
console.error('加载数据失败:', errorThrown);
|
||||
alert('加载数据失败: ' + (jqXHR.responseJSON?.error || errorThrown));
|
||||
}
|
||||
});
|
||||
}
|
||||
function captureChartViewState(chart) {
|
||||
if (!chart || !chart.timeScale) return null;
|
||||
const ts = chart.timeScale();
|
||||
const tsOptions = ts.options ? ts.options() : {};
|
||||
return {
|
||||
barSpacing: tsOptions.barSpacing,
|
||||
rightOffset: tsOptions.rightOffset,
|
||||
scrollPosition: ts.scrollPosition ? ts.scrollPosition() : null,
|
||||
visibleRange: ts.getVisibleRange ? ts.getVisibleRange() : null,
|
||||
logicalRange: ts.getVisibleLogicalRange ? ts.getVisibleLogicalRange() : null
|
||||
};
|
||||
}
|
||||
|
||||
function restoreChartViewState(charts, viewState) {
|
||||
if (!viewState || !Array.isArray(charts) || charts.length === 0) return;
|
||||
const validCharts = charts.filter(c => c && c.timeScale);
|
||||
if (validCharts.length === 0) return;
|
||||
|
||||
validCharts.forEach(c => {
|
||||
try {
|
||||
const optionsPatch = {};
|
||||
if (typeof viewState.barSpacing === 'number') optionsPatch.barSpacing = viewState.barSpacing;
|
||||
if (typeof viewState.rightOffset === 'number') optionsPatch.rightOffset = viewState.rightOffset;
|
||||
if (Object.keys(optionsPatch).length) {
|
||||
c.timeScale().applyOptions(optionsPatch);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
let restored = false;
|
||||
|
||||
// 优先按逻辑范围恢复(对新数据更稳健)
|
||||
if (viewState.logicalRange && viewState.logicalRange.from !== undefined && viewState.logicalRange.to !== undefined) {
|
||||
validCharts.forEach(c => {
|
||||
try {
|
||||
c.timeScale().setVisibleLogicalRange(viewState.logicalRange);
|
||||
restored = true;
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
// 逻辑范围失败时,回退到时间可见范围
|
||||
if (!restored && viewState.visibleRange && viewState.visibleRange.from !== undefined && viewState.visibleRange.to !== undefined) {
|
||||
validCharts.forEach(c => {
|
||||
try {
|
||||
c.timeScale().setVisibleRange(viewState.visibleRange);
|
||||
restored = true;
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
// 最后回退到滚动位置
|
||||
if (!restored && typeof viewState.scrollPosition === 'number') {
|
||||
validCharts.forEach(c => {
|
||||
try { c.timeScale().scrollToPosition(viewState.scrollPosition, false); } catch (e) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 初始化图表
|
||||
Reference in New Issue
Block a user