diff --git a/tests/live/authenticated.test.ts b/tests/live/authenticated.test.ts index 5331b8f..c5533e3 100644 --- a/tests/live/authenticated.test.ts +++ b/tests/live/authenticated.test.ts @@ -45,9 +45,31 @@ describe.skipIf(!LIVE || !credentials)('live: authenticated', () => { } }); - it('lists private indicators', async () => { + it('runs the public Pine study covered by V3', async () => { + const result = await getIndicatorData({ + symbol: 'BINANCE:BTCEUR', timeframe: '60', count: 30, + indicator: 'PUB;uA35GeckoTA2EfgI63SD2WCSmca4njxp', + credentials: auth, timeoutMs: 30_000, + }); + const latest = result.values.at(-1); + expect(latest?.VWAP).toBeTypeOf('number'); + expect(latest?.rsiMFI).toBeTypeOf('number'); + expect(latest?.Buy_and_sell_circle).toBeTypeOf('number'); + }); + + it('lists and runs the account’s first private indicators', async () => { const list = await getPrivateIndicators(auth); - expect(Array.isArray(list)).toBe(true); + expect(list.length).toBeGreaterThan(0); + for (const item of list.slice(0, 3)) { + expect(item.id).toBeTruthy(); + expect(item.name).toBeTruthy(); + const result = await getIndicatorData({ + symbol: 'BINANCE:BTCEUR', timeframe: 'D', count: 30, + indicator: item.id, credentials: auth, timeoutMs: 30_000, + }); + expect(result.candles.length).toBeGreaterThan(0); + expect(result.values.length, item.id).toBeGreaterThan(0); + } }); it('uses an account timeframe with a recent historical reference', async () => { diff --git a/tests/live/data.test.ts b/tests/live/data.test.ts index 77d19ed..25c4a77 100644 --- a/tests/live/data.test.ts +++ b/tests/live/data.test.ts @@ -22,12 +22,19 @@ describe.skipIf(!LIVE)('live: high-level data API', () => { expect(range.length).toBeGreaterThanOrEqual(40); }); - it('gets custom chart types', async () => { - for (const chartType of ['HeikinAshi', 'Renko', 'LineBreak', 'Kagi', 'PointAndFigure', 'Range'] as const) { + it.each(['HeikinAshi', 'Renko', 'LineBreak', 'Kagi', 'PointAndFigure', 'Range'] as const)( + 'returns valid %s candles', async (chartType) => { const candles = await getCandles({ symbol: 'BINANCE:BTCEUR', timeframe: 'D', count: 10, chartType }); - expect(candles.length, chartType).toBeGreaterThan(0); - } - }); + expect(candles.length).toBeGreaterThanOrEqual(5); + for (const candle of candles) { + expect(Number.isFinite(candle.time)).toBe(true); + for (const field of ['open', 'high', 'low', 'close'] as const) { + expect(Number.isFinite(candle[field]), `${chartType} ${field}`).toBe(true); + } + expect(candle.high).toBeGreaterThanOrEqual(candle.low); + } + }, + ); it('watches candles and stops cleanly', async () => { let updates = 0; @@ -68,9 +75,15 @@ describe.skipIf(!LIVE)('live: high-level data API', () => { it('searches markets and indicators, and gets technical analysis', async () => { expect((await searchMarkets('BINANCE:')).length).toBeGreaterThan(10); - expect((await searchMarkets('nasdaq apple'))[0]?.id).toBe('NASDAQ:AAPL'); expect((await searchIndicators('RSI')).length).toBeGreaterThan(10); - const ta = await getTechnicalAnalysis('BINANCE:BTCUSD'); + }); + + it.each([ + ['binance:BTCUSD', 'BINANCE:BTCUSD'], + ['nasdaq apple', 'NASDAQ:AAPL'], + ] as const)('searches %s and gets technical analysis', async (query, symbol) => { + expect((await searchMarkets(query))[0]?.id).toBe(symbol); + const ta = await getTechnicalAnalysis(symbol); for (const period of ['1', '5', '15', '60', '240', '1D', '1W', '1M'] as const) { expect(ta?.[period]).toMatchObject({ Other: expect.any(Number), All: expect.any(Number), MA: expect.any(Number) }); }