import { TechnicalIndicators, IncrementalSMA, IncrementalEMA, IncrementalRSI } from '@stock-bot/core'; import { TechnicalAnalysis, IncrementalIndicators, SignalGenerator } from '../src/indicators/TechnicalAnalysis'; describe('Technical Analysis Library', () => { let ta: TechnicalAnalysis; let indicators: TechnicalIndicators; beforeEach(() => { ta = new TechnicalAnalysis(); indicators = new TechnicalIndicators(); }); describe('Simple Moving Average', () => { it('should calculate SMA correctly', () => { const values = [10, 12, 13, 14, 15, 16, 17, 18, 19, 20]; const sma = ta.sma(values, 5); expect(sma).toHaveLength(6); // 10 values - 5 period + 1 expect(sma[0]).toBeCloseTo(12.8); // (10+12+13+14+15)/5 expect(sma[5]).toBeCloseTo(18); // (16+17+18+19+20)/5 }); it('should handle incremental SMA updates', () => { const incSMA = new IncrementalSMA(3); expect(incSMA.update(10)).toBeNull(); expect(incSMA.update(12)).toBeNull(); expect(incSMA.update(14)).toBeCloseTo(12); // (10+12+14)/3 expect(incSMA.update(16)).toBeCloseTo(14); // (12+14+16)/3 expect(incSMA.current()).toBeCloseTo(14); }); }); describe('Exponential Moving Average', () => { it('should calculate EMA correctly', () => { const values = [10, 12, 13, 14, 15, 16, 17, 18, 19, 20]; const ema = ta.ema(values, 5); expect(ema).toHaveLength(6); expect(ema[0]).toBeGreaterThan(0); expect(ema[ema.length - 1]).toBeGreaterThan(ema[0]); }); }); describe('RSI', () => { it('should calculate RSI correctly', () => { const values = [ 44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28, 46.00, 46.03, 46.41, 46.22, 45.64 ]; const rsi = ta.rsi(values, 14); expect(rsi).toHaveLength(7); // 20 values - 14 period + 1 expect(rsi[rsi.length - 1]).toBeGreaterThan(0); expect(rsi[rsi.length - 1]).toBeLessThan(100); }); it('should identify overbought/oversold conditions', () => { // Trending up values should give high RSI const uptrend = Array.from({ length: 20 }, (_, i) => 100 + i); const rsiUp = ta.rsi(uptrend, 14); expect(rsiUp[rsiUp.length - 1]).toBeGreaterThan(70); // Trending down values should give low RSI const downtrend = Array.from({ length: 20 }, (_, i) => 100 - i); const rsiDown = ta.rsi(downtrend, 14); expect(rsiDown[rsiDown.length - 1]).toBeLessThan(30); }); }); describe('MACD', () => { it('should calculate MACD components correctly', () => { const values = Array.from({ length: 50 }, (_, i) => 100 + Math.sin(i * 0.1) * 10); const macd = ta.macd(values); expect(macd.macd).toHaveLength(39); // 50 - 26 + 1 - 9 + 1 expect(macd.signal).toHaveLength(39); expect(macd.histogram).toHaveLength(39); // Histogram should be the difference between MACD and signal expect(macd.histogram[0]).toBeCloseTo(macd.macd[0] - macd.signal[0]); }); }); describe('Bollinger Bands', () => { it('should calculate bands correctly', () => { const values = Array.from({ length: 30 }, (_, i) => 100 + Math.random() * 10); const bb = ta.bollingerBands(values, 20, 2); expect(bb.middle).toHaveLength(11); // 30 - 20 + 1 expect(bb.upper).toHaveLength(11); expect(bb.lower).toHaveLength(11); // Upper should be above middle, lower should be below for (let i = 0; i < bb.middle.length; i++) { expect(bb.upper[i]).toBeGreaterThan(bb.middle[i]); expect(bb.lower[i]).toBeLessThan(bb.middle[i]); } }); }); describe('ATR', () => { it('should calculate ATR correctly', () => { const high = [48.70, 48.72, 48.90, 48.87, 48.82, 49.05, 49.20, 49.35, 49.92, 50.19]; const low = [47.79, 48.14, 48.39, 48.37, 48.24, 48.64, 48.94, 48.86, 49.50, 49.87]; const close = [48.16, 48.61, 48.75, 48.63, 48.74, 49.03, 49.07, 49.32, 49.91, 50.13]; const atr = ta.atr(high, low, close, 5); expect(atr).toHaveLength(5); // 10 - 5 - 1 + 1 expect(atr.every(v => v > 0)).toBe(true); }); }); describe('Stochastic', () => { it('should calculate Stochastic correctly', () => { const high = Array.from({ length: 20 }, () => Math.random() * 10 + 100); const low = Array.from({ length: 20 }, (_, i) => high[i] - Math.random() * 5); const close = Array.from({ length: 20 }, (_, i) => (high[i] + low[i]) / 2); const stoch = ta.stochastic(high, low, close, 14, 3, 3); expect(stoch.k.length).toBeGreaterThan(0); expect(stoch.d.length).toBeGreaterThan(0); // %K and %D should be between 0 and 100 expect(stoch.k.every(v => v >= 0 && v <= 100)).toBe(true); expect(stoch.d.every(v => v >= 0 && v <= 100)).toBe(true); }); }); describe('Signal Generator', () => { it('should generate trading signals based on indicators', () => { const generator = new SignalGenerator(); // Create synthetic price data const prices = { close: Array.from({ length: 50 }, (_, i) => 100 + Math.sin(i * 0.2) * 10), high: Array.from({ length: 50 }, (_, i) => 102 + Math.sin(i * 0.2) * 10), low: Array.from({ length: 50 }, (_, i) => 98 + Math.sin(i * 0.2) * 10), volume: Array.from({ length: 50 }, () => 1000000) }; const signal = generator.generateSignals('TEST', prices, Date.now()); expect(signal.symbol).toBe('TEST'); expect(['BUY', 'SELL', 'HOLD']).toContain(signal.action); expect(signal.strength).toBeGreaterThanOrEqual(0); expect(signal.strength).toBeLessThanOrEqual(1); expect(signal.indicators).toBeDefined(); expect(signal.reason).toBeDefined(); }); }); describe('Incremental Indicators Manager', () => { it('should manage multiple incremental indicators', () => { const manager = new IncrementalIndicators(); manager.createSMA('fast', 10); manager.createSMA('slow', 20); manager.createRSI('rsi', 14); // Update all indicators with same value for (let i = 0; i < 25; i++) { const value = 100 + i; manager.update('fast', value); manager.update('slow', value); manager.update('rsi', value); } expect(manager.current('fast')).toBeDefined(); expect(manager.current('slow')).toBeDefined(); expect(manager.current('rsi')).toBeDefined(); // RSI should be high for uptrending values const rsiValue = manager.current('rsi'); expect(rsiValue).toBeGreaterThan(70); }); }); describe('Crossover Detection', () => { it('should detect crossovers correctly', () => { const series1 = [10, 11, 12, 13, 14]; const series2 = [12, 12, 12, 12, 12]; expect(TechnicalAnalysis.crossover(series1, series2)).toBe(true); expect(TechnicalAnalysis.crossunder(series2, series1)).toBe(true); const series3 = [15, 14, 13, 12, 11]; expect(TechnicalAnalysis.crossunder(series3, series2)).toBe(true); expect(TechnicalAnalysis.crossover(series2, series3)).toBe(true); }); }); });