fix(web): 自动刷新保留 K 线视窗;威科夫与图表增量更新

自动刷新改用 tail update 与 scrollToPosition 恢复视窗,避免 setData 后跳到最右;拆分 chart_tv 模块并扩展 analyze/recent API。同步威科夫分析、pipeline 增量构建及相关策略与配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-25 22:57:43 +08:00
co-authored by Cursor
parent 1e60ab3bfa
commit 8ee11317d3
104 changed files with 21452 additions and 4988 deletions
+21 -5
View File
@@ -4,6 +4,16 @@ window.App.Charts = (function() {
// 依赖 Indicators
const Indicators = (window.App && window.App.Indicators) || {};
function sanitizeLinePoints(points) {
if (!Array.isArray(points)) return [];
return points.filter(function (p) {
return p && p.time != null && p.value != null &&
isFinite(Number(p.time)) && isFinite(Number(p.value));
}).map(function (p) {
return { time: Math.floor(Number(p.time)), value: Number(p.value) };
});
}
function addMovingAveragesToChart(candleData) {
if (!window.tvWidget || !tvWidget.mainChart || !candleData || candleData.length === 0) return;
if (!window.movingAverages) return;
@@ -21,6 +31,8 @@ window.App.Charts = (function() {
try {
const maData = Indicators.calculateMA(candleData, maConfig.type, maConfig.length, maConfig.source);
const smoothedData = maConfig.smoothType !== 'none' ? (window.applySmoothToMA ? window.applySmoothToMA(maData, maConfig.smoothType, maConfig.smoothLength) : maData) : maData;
const cleanData = sanitizeLinePoints(smoothedData);
if (!cleanData.length) return;
const maSeries = tvWidget.mainChart.addLineSeries({
color: maConfig.color,
lineWidth: maConfig.lineWidth || 2,
@@ -30,8 +42,8 @@ window.App.Charts = (function() {
priceLineVisible: false,
crosshairMarkerVisible: true,
});
maSeries.setData(smoothedData);
maConfig.data = smoothedData;
maSeries.setData(cleanData);
maConfig.data = cleanData;
tvWidget.series.maSeries.push(maSeries);
} catch(e) {}
});
@@ -51,12 +63,16 @@ window.App.Charts = (function() {
if (!bbConfig.visible) return;
try {
const bbData = Indicators.calculateBB(candleData, bbConfig.length, bbConfig.upperMultiplier, bbConfig.lowerMultiplier, bbConfig.source);
const upper = sanitizeLinePoints(bbData.map(item => ({ time: item.time, value: item.upper })));
const middle = sanitizeLinePoints(bbData.map(item => ({ time: item.time, value: item.middle })));
const lower = sanitizeLinePoints(bbData.map(item => ({ time: item.time, value: item.lower })));
if (!upper.length || !middle.length || !lower.length) return;
const upperSeries = tvWidget.mainChart.addLineSeries({ color: bbConfig.upperColor, lineWidth: bbConfig.lineWidth || 2, lineStyle: bbConfig.lineStyle || 0, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: true });
const middleSeries = tvWidget.mainChart.addLineSeries({ color: bbConfig.middleColor, lineWidth: bbConfig.lineWidth || 2, lineStyle: bbConfig.lineStyle || 0, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: true });
const lowerSeries = tvWidget.mainChart.addLineSeries({ color: bbConfig.lowerColor, lineWidth: bbConfig.lineWidth || 2, lineStyle: bbConfig.lineStyle || 0, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: true });
upperSeries.setData(bbData.map(item => ({ time: item.time, value: item.upper })));
middleSeries.setData(bbData.map(item => ({ time: item.time, value: item.middle })));
lowerSeries.setData(bbData.map(item => ({ time: item.time, value: item.lower })));
upperSeries.setData(upper);
middleSeries.setData(middle);
lowerSeries.setData(lower);
bbConfig.data = bbData;
tvWidget.series.bbSeries.push(upperSeries, middleSeries, lowerSeries);
} catch(e) {}