// Namespace setup window.App = window.App || {}; 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; if (!tvWidget.series) tvWidget.series = {}; if (tvWidget.series.maSeries && tvWidget.series.maSeries.length > 0) { tvWidget.series.maSeries.forEach(series => { try { tvWidget.mainChart.removeSeries(series); } catch(e) {} }); } tvWidget.series.maSeries = []; window.movingAverages.forEach(maConfig => { if (!maConfig.visible) return; 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, lineStyle: maConfig.lineStyle || 0, title: `${maConfig.type}(${maConfig.length})`, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: true, }); maSeries.setData(cleanData); maConfig.data = cleanData; tvWidget.series.maSeries.push(maSeries); } catch(e) {} }); } function addBollingerBandsToChart(candleData) { if (!window.tvWidget || !tvWidget.mainChart || !candleData || candleData.length === 0) return; if (!window.bollingerBands) return; if (!tvWidget.series) tvWidget.series = {}; if (tvWidget.series.bbSeries && tvWidget.series.bbSeries.length > 0) { tvWidget.series.bbSeries.forEach(series => { try { tvWidget.mainChart.removeSeries(series); } catch(e) {} }); } tvWidget.series.bbSeries = []; window.bollingerBands.forEach(bbConfig => { 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(upper); middleSeries.setData(middle); lowerSeries.setData(lower); bbConfig.data = bbData; tvWidget.series.bbSeries.push(upperSeries, middleSeries, lowerSeries); } catch(e) {} }); } return { addMovingAveragesToChart, addBollingerBandsToChart }; })();