分拆了index
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
// Namespace setup
|
||||
window.App = window.App || {};
|
||||
window.App.Charts = (function() {
|
||||
// 依赖 Indicators
|
||||
const Indicators = (window.App && window.App.Indicators) || {};
|
||||
|
||||
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 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(smoothedData);
|
||||
maConfig.data = smoothedData;
|
||||
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 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 })));
|
||||
bbConfig.data = bbData;
|
||||
tvWidget.series.bbSeries.push(upperSeries, middleSeries, lowerSeries);
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
|
||||
return { addMovingAveragesToChart, addBollingerBandsToChart };
|
||||
})();
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Namespace setup
|
||||
window.App = window.App || {};
|
||||
window.App.Indicators = (function() {
|
||||
function computeEMA(arr, period) {
|
||||
const k = 2 / (period + 1);
|
||||
const out = [];
|
||||
let emaPrev = null;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const price = arr[i];
|
||||
if (price == null || !isFinite(price)) { out.push(null); continue; }
|
||||
if (emaPrev == null) {
|
||||
const start = Math.max(0, i - period + 1);
|
||||
const windowArr = arr.slice(start, i + 1).filter(v => v != null && isFinite(v));
|
||||
const sma = windowArr.length ? windowArr.reduce((a,b)=>a+b,0)/windowArr.length : price;
|
||||
emaPrev = sma;
|
||||
}
|
||||
const ema = price * k + emaPrev * (1 - k);
|
||||
out.push(ema);
|
||||
emaPrev = ema;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function calculateMA(data, type, length, source) {
|
||||
if (!data || data.length < length) return [];
|
||||
const sourceData = data.map(candle => {
|
||||
switch(source) {
|
||||
case 'open': return candle.open;
|
||||
case 'high': return candle.high;
|
||||
case 'low': return candle.low;
|
||||
case 'close': return candle.close;
|
||||
case 'hl2': return (candle.high + candle.low) / 2;
|
||||
case 'hlc3': return (candle.high + candle.low + candle.close) / 3;
|
||||
case 'ohlc4': return (candle.open + candle.high + candle.low + candle.close) / 4;
|
||||
default: return candle.close;
|
||||
}
|
||||
});
|
||||
const result = [];
|
||||
for (let i = length - 1; i < sourceData.length; i++) {
|
||||
let value;
|
||||
switch(type) {
|
||||
case 'SMA':
|
||||
value = sourceData.slice(i - length + 1, i + 1).reduce((sum, v) => sum + v, 0) / length;
|
||||
break;
|
||||
case 'EMA':
|
||||
const multiplier = 2 / (length + 1);
|
||||
if (result.length === 0) {
|
||||
value = sourceData.slice(i - length + 1, i + 1).reduce((sum, v) => sum + v, 0) / length;
|
||||
} else {
|
||||
value = sourceData[i] * multiplier + result[result.length - 1].value * (1 - multiplier);
|
||||
}
|
||||
break;
|
||||
case 'WMA':
|
||||
let weightSum = 0;
|
||||
let valueSum = 0;
|
||||
for (let j = 0; j < length; j++) {
|
||||
const weight = j + 1;
|
||||
weightSum += weight;
|
||||
valueSum += sourceData[i - length + 1 + j] * weight;
|
||||
}
|
||||
value = valueSum / weightSum;
|
||||
break;
|
||||
default:
|
||||
value = sourceData[i];
|
||||
}
|
||||
result.push({ time: data[i].time, value });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function calculateBB(data, length, upperMultiplier, lowerMultiplier, source) {
|
||||
if (!data || data.length < length) return [];
|
||||
const sourceData = data.map(candle => {
|
||||
switch(source) {
|
||||
case 'open': return candle.open;
|
||||
case 'high': return candle.high;
|
||||
case 'low': return candle.low;
|
||||
case 'close': return candle.close;
|
||||
case 'hl2': return (candle.high + candle.low) / 2;
|
||||
case 'hlc3': return (candle.high + candle.low + candle.close) / 3;
|
||||
case 'ohlc4': return (candle.open + candle.high + candle.low + candle.close) / 4;
|
||||
default: return candle.close;
|
||||
}
|
||||
});
|
||||
const result = [];
|
||||
for (let i = length - 1; i < sourceData.length; i++) {
|
||||
const start = Math.max(0, i - length + 1);
|
||||
const slice = sourceData.slice(start, i + 1);
|
||||
const avg = slice.reduce((sum, v) => sum + v, 0) / length;
|
||||
const std = Math.sqrt(slice.reduce((sum, v) => sum + Math.pow(v - avg, 2), 0) / length);
|
||||
result.push({ time: data[i].time, upper: avg + upperMultiplier * std, middle: avg, lower: avg - lowerMultiplier * std });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return { computeEMA, calculateMA, calculateBB };
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user