import type { Candle, ChartIndicator, IndicatorType } from './chartTypes'; export interface IndicatorData { index: number; values: number[]; } /** * Calculate Simple Moving Average */ export function calculateSMA(candles: Candle[], period: number): IndicatorData[] { const result: IndicatorData[] = []; if (!candles || !Array.isArray(candles) || candles.length === 0 || candles.length < period || period < 1) return result; for (let i = period - 1; i < candles.length; i++) { let sum = 0; for (let j = 0; j < period; j++) { sum += candles[i - j].close; } result.push({ index: i, values: [sum / period] }); } return result; } /** * Calculate Exponential Moving Average */ export function calculateEMA(candles: Candle[], period: number): IndicatorData[] { const result: IndicatorData[] = []; if (!candles || !Array.isArray(candles) || candles.length === 0 || candles.length < period || period < 1) return result; const multiplier = 2 / (period + 1); // Start with SMA for first value let sum = 0; for (let i = 0; i < period; i++) { if (!candles[i]) return result; sum += candles[i].close; } let ema = sum / period; result.push({ index: period - 1, values: [ema] }); // Calculate EMA for remaining candles for (let i = period; i < candles.length; i++) { ema = (candles[i].close - ema) * multiplier + ema; result.push({ index: i, values: [ema] }); } return result; } /** * Calculate Bollinger Bands * Returns [middle, upper, lower] bands */ export function calculateBollingerBands( candles: Candle[], period: number, stdDev: number ): IndicatorData[] { const result: IndicatorData[] = []; if (!candles || !Array.isArray(candles) || candles.length === 0 || candles.length < period || period < 1) return result; for (let i = period - 1; i < candles.length; i++) { // Calculate SMA (middle band) let sum = 0; for (let j = 0; j < period; j++) { sum += candles[i - j].close; } const middle = sum / period; // Calculate standard deviation let sumSquares = 0; for (let j = 0; j < period; j++) { const diff = candles[i - j].close - middle; sumSquares += diff * diff; } const std = Math.sqrt(sumSquares / period); const upper = middle + stdDev * std; const lower = middle - stdDev * std; result.push({ index: i, values: [middle, upper, lower] }); } return result; } /** * Calculate RSI (Relative Strength Index) */ export function calculateRSI(candles: Candle[], period: number): IndicatorData[] { const result: IndicatorData[] = []; if (!candles || !Array.isArray(candles) || candles.length === 0 || candles.length < period + 1 || period < 1) return result; // Calculate initial average gains and losses let avgGain = 0; let avgLoss = 0; for (let i = 1; i <= period; i++) { const change = candles[i].close - candles[i - 1].close; if (change > 0) { avgGain += change; } else { avgLoss += Math.abs(change); } } avgGain /= period; avgLoss /= period; // First RSI value let rs = avgLoss === 0 ? 100 : avgGain / avgLoss; let rsi = 100 - 100 / (1 + rs); result.push({ index: period, values: [rsi] }); // Calculate remaining RSI values using smoothed averages for (let i = period + 1; i < candles.length; i++) { const change = candles[i].close - candles[i - 1].close; const gain = change > 0 ? change : 0; const loss = change < 0 ? Math.abs(change) : 0; avgGain = (avgGain * (period - 1) + gain) / period; avgLoss = (avgLoss * (period - 1) + loss) / period; rs = avgLoss === 0 ? 100 : avgGain / avgLoss; rsi = 100 - 100 / (1 + rs); result.push({ index: i, values: [rsi] }); } return result; } /** * Calculate MACD (Moving Average Convergence Divergence) * Returns [macdLine, signalLine, histogram] */ export function calculateMACD( candles: Candle[], fastPeriod: number, slowPeriod: number, signalPeriod: number ): IndicatorData[] { const result: IndicatorData[] = []; if (!candles || !Array.isArray(candles) || candles.length === 0 || candles.length < slowPeriod || slowPeriod < 1) return result; // Calculate fast and slow EMAs const fastEMA = calculateEMA(candles, fastPeriod); const slowEMA = calculateEMA(candles, slowPeriod); if (slowEMA.length === 0) return result; // Calculate MACD line (fast EMA - slow EMA) const macdValues: { index: number; value: number }[] = []; for (const slow of slowEMA) { const fast = fastEMA.find(f => f.index === slow.index); if (fast) { macdValues.push({ index: slow.index, value: fast.values[0] - slow.values[0], }); } } if (macdValues.length < signalPeriod) return result; // Calculate signal line (EMA of MACD) const signalMultiplier = 2 / (signalPeriod + 1); let signalSum = 0; for (let i = 0; i < signalPeriod; i++) { signalSum += macdValues[i].value; } let signal = signalSum / signalPeriod; const macd = macdValues[signalPeriod - 1].value; result.push({ index: macdValues[signalPeriod - 1].index, values: [macd, signal, macd - signal], }); for (let i = signalPeriod; i < macdValues.length; i++) { const macdVal = macdValues[i].value; signal = (macdVal - signal) * signalMultiplier + signal; result.push({ index: macdValues[i].index, values: [macdVal, signal, macdVal - signal], }); } return result; } /** * Get indicator data based on type */ export function getIndicatorData( indicator: ChartIndicator, candles: Candle[] ): IndicatorData[] { switch (indicator.type) { case 'sma': return calculateSMA(candles, indicator.params.period || 20); case 'ema': return calculateEMA(candles, indicator.params.period || 12); case 'boll': return calculateBollingerBands( candles, indicator.params.period || 20, indicator.params.stdDev || 2 ); case 'rsi': return calculateRSI(candles, indicator.params.period || 14); case 'macd': return calculateMACD( candles, indicator.params.fastPeriod || 12, indicator.params.slowPeriod || 26, indicator.params.signalPeriod || 9 ); default: return []; } } /** * Get display name for indicator type */ export function getIndicatorDisplayName(type: IndicatorType): string { switch (type) { case 'sma': return 'SMA'; case 'ema': return 'EMA'; case 'boll': return 'Bollinger Bands'; case 'rsi': return 'RSI'; case 'macd': return 'MACD'; case 'volume': return 'Volume Profile'; default: return type.toUpperCase(); } } /** * Get indicator parameter label */ export function getIndicatorParamLabel(indicator: ChartIndicator): string { switch (indicator.type) { case 'sma': case 'ema': return `(${indicator.params.period})`; case 'boll': return `(${indicator.params.period}, ${indicator.params.stdDev})`; case 'rsi': return `(${indicator.params.period})`; case 'macd': return `(${indicator.params.fastPeriod}, ${indicator.params.slowPeriod}, ${indicator.params.signalPeriod})`; default: return ''; } }