feat: Crypto.com-Client und Backfill-Script

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 20:58:27 +00:00
parent 27a10dc794
commit a007c9dc6f
3 changed files with 90 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
import { expect, test } from 'bun:test';
import { parseCandlestickResponse } from './cryptocom';
test('parst Crypto.com-Candlestick-Response', () => {
const json = {
result: {
data: [
{ t: 900000, o: '1.0', h: '1.2', l: '0.9', c: '1.1', v: '1000' },
{ t: 1800000, o: '1.1', h: '1.3', l: '1.0', c: '1.2', v: '500' },
],
},
};
const out = parseCandlestickResponse(json);
expect(out).toHaveLength(2);
expect(out[0]).toEqual({ ts: 900000, open: 1, high: 1.2, low: 0.9, close: 1.1, volume: 1000 });
});
test('leere/fehlende Daten → leeres Array', () => {
expect(parseCandlestickResponse({})).toEqual([]);
expect(parseCandlestickResponse({ result: {} })).toEqual([]);
});

View File

@@ -0,0 +1,35 @@
import type { Candle, Pair } from '../types';
const BASE = 'https://api.crypto.com/exchange/v1';
export function parseCandlestickResponse(json: any): Candle[] {
const data = json?.result?.data;
if (!Array.isArray(data)) return [];
return data.map((d: any) => ({
ts: Number(d.t),
open: Number(d.o),
high: Number(d.h),
low: Number(d.l),
close: Number(d.c),
volume: Number(d.v),
}));
}
export async function fetchCandles(pair: Pair, timeframe: string, count: number, endTs?: number): Promise<Candle[]> {
const url = new URL(`${BASE}/public/get-candlestick`);
url.searchParams.set('instrument_name', pair);
url.searchParams.set('timeframe', timeframe);
url.searchParams.set('count', String(count));
if (endTs !== undefined) url.searchParams.set('end_ts', String(endTs));
for (let attempt = 1; ; attempt++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} für ${pair}`);
return parseCandlestickResponse(await res.json());
} catch (err) {
if (attempt >= 3) throw err;
await Bun.sleep(1000 * 2 ** (attempt - 1));
}
}
}