import { describe, it, expect } from 'bun:test'; import { // Common utilities createProxyUrl, sleep, // Date utilities dateUtils, // Generic functions extractCloses, extractOHLC, extractVolumes, calculateSMA, calculateTypicalPrice, calculateTrueRange, calculateReturns, calculateLogReturns, calculateVWAP, filterBySymbol, filterByTimeRange, groupBySymbol, convertTimestamps, } from '../src/index'; describe('Utility Functions', () => { describe('common utilities', () => { it('should create proxy URL with auth', () => { const proxy = { protocol: 'http', host: '192.168.1.1', port: 8080, username: 'user', password: 'pass', }; const url = createProxyUrl(proxy); expect(url).toBe('http://user:pass@192.168.1.1:8080'); }); it('should create proxy URL without auth', () => { const proxy = { protocol: 'socks5', host: '192.168.1.1', port: 1080, }; const url = createProxyUrl(proxy); expect(url).toBe('socks5://192.168.1.1:1080'); }); it('should sleep for specified milliseconds', async () => { const start = Date.now(); await sleep(100); const elapsed = Date.now() - start; expect(elapsed).toBeGreaterThanOrEqual(90); expect(elapsed).toBeLessThan(200); }); }); describe('date utilities', () => { it('should check if date is trading day', () => { const monday = new Date('2023-12-25'); // Monday const saturday = new Date('2023-12-23'); // Saturday const sunday = new Date('2023-12-24'); // Sunday expect(dateUtils.isTradingDay(monday)).toBe(true); expect(dateUtils.isTradingDay(saturday)).toBe(false); expect(dateUtils.isTradingDay(sunday)).toBe(false); }); it('should get next trading day', () => { const friday = new Date('2023-12-22'); // Friday const nextDay = dateUtils.getNextTradingDay(friday); expect(nextDay.getDay()).toBe(1); // Monday }); it('should get previous trading day', () => { const monday = new Date('2023-12-25'); // Monday const prevDay = dateUtils.getPreviousTradingDay(monday); expect(prevDay.getDay()).toBe(5); // Friday }); it('should format date as YYYY-MM-DD', () => { const date = new Date('2023-12-25T10:30:00Z'); const formatted = dateUtils.formatDate(date); expect(formatted).toBe('2023-12-25'); }); it('should parse date from string', () => { const date = dateUtils.parseDate('2023-12-25'); expect(date.getFullYear()).toBe(2023); expect(date.getMonth()).toBe(11); // 0-based expect(date.getDate()).toBe(25); }); }); describe('generic functions', () => { const testData = [ { open: 100, high: 105, low: 98, close: 103, volume: 1000 }, { open: 103, high: 107, low: 101, close: 105, volume: 1200 }, { open: 105, high: 108, low: 104, close: 106, volume: 1100 }, ]; it('should extract close prices', () => { const closes = extractCloses(testData); expect(closes).toEqual([103, 105, 106]); }); it('should extract OHLC data', () => { const ohlc = extractOHLC(testData); expect(ohlc.opens).toEqual([100, 103, 105]); expect(ohlc.highs).toEqual([105, 107, 108]); expect(ohlc.lows).toEqual([98, 101, 104]); expect(ohlc.closes).toEqual([103, 105, 106]); }); it('should extract volumes', () => { const volumes = extractVolumes(testData); expect(volumes).toEqual([1000, 1200, 1100]); }); it('should calculate SMA', () => { const sma = calculateSMA(testData, 2); expect(sma).toHaveLength(2); expect(sma[0]).toBe(104); expect(sma[1]).toBe(105.5); }); it('should calculate typical price', () => { const typical = calculateTypicalPrice(testData); expect(typical[0]).toBeCloseTo((105 + 98 + 103) / 3); expect(typical[1]).toBeCloseTo((107 + 101 + 105) / 3); expect(typical[2]).toBeCloseTo((108 + 104 + 106) / 3); }); it('should calculate true range', () => { const tr = calculateTrueRange(testData); expect(tr).toHaveLength(3); expect(tr[0]).toBe(7); // 105 - 98 }); it('should calculate returns', () => { const returns = calculateReturns(testData); expect(returns).toHaveLength(2); expect(returns[0]).toBeCloseTo((105 - 103) / 103); expect(returns[1]).toBeCloseTo((106 - 105) / 105); }); it('should calculate log returns', () => { const logReturns = calculateLogReturns(testData); expect(logReturns).toHaveLength(2); expect(logReturns[0]).toBeCloseTo(Math.log(105 / 103)); expect(logReturns[1]).toBeCloseTo(Math.log(106 / 105)); }); it('should calculate VWAP', () => { const vwap = calculateVWAP(testData); expect(vwap).toHaveLength(3); expect(vwap[0]).toBeGreaterThan(0); }); }); describe('OHLCV data operations', () => { const ohlcvData = [ { symbol: 'AAPL', open: 100, high: 105, low: 98, close: 103, volume: 1000, timestamp: 1000000 }, { symbol: 'GOOGL', open: 200, high: 205, low: 198, close: 203, volume: 2000, timestamp: 1000000 }, { symbol: 'AAPL', open: 103, high: 107, low: 101, close: 105, volume: 1200, timestamp: 2000000 }, ]; it('should filter by symbol', () => { const filtered = filterBySymbol(ohlcvData, 'AAPL'); expect(filtered).toHaveLength(2); expect(filtered.every(item => item.symbol === 'AAPL')).toBe(true); }); it('should filter by time range', () => { const filtered = filterByTimeRange(ohlcvData, 1500000, 2500000); expect(filtered).toHaveLength(1); expect(filtered[0].timestamp).toBe(2000000); }); it('should group by symbol', () => { const grouped = groupBySymbol(ohlcvData); expect(grouped['AAPL']).toHaveLength(2); expect(grouped['GOOGL']).toHaveLength(1); }); it('should convert timestamps to dates', () => { const converted = convertTimestamps(ohlcvData); expect(converted[0].date).toBeInstanceOf(Date); expect(converted[0].date.getTime()).toBe(1000000); }); }); });