spb/hfmarketdata
Public
Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)
JavaScript 53.7%
Python 38.3%
CSS 4.6%
TypeScript 3.1%
1import { HfmdClient } from '../src/client.js';23export interface MockRoute {4 /** Substring or RegExp matched against the full URL. */5 match: string | RegExp;6 status?: number;7 body?: unknown;8 headers?: Record<string, string>;9}1011export interface MockFetch {12 fetch: typeof fetch;13 calls: string[];14}1516export function mockFetch(routes: MockRoute[]): MockFetch {17 const calls: string[] = [];18 const f = (async (input: RequestInfo | URL, init?: RequestInit) => {19 const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;20 calls.push(url);21 void init;22 const route = routes.find((r) => (typeof r.match === 'string' ? url.includes(r.match) : r.match.test(url)));23 if (!route) return new Response(JSON.stringify({ detail: 'Not Found' }), { status: 404, headers: { 'content-type': 'application/json' } });24 const status = route.status ?? 200;25 const headers = { 'content-type': 'application/json', ...(route.headers ?? {}) };26 return new Response(typeof route.body === 'string' ? route.body : JSON.stringify(route.body ?? {}), { status, headers });27 }) as typeof fetch;28 return { fetch: f, calls };29}3031export function clientWith(routes: MockRoute[], opts: { apiKey?: string } = {}): { client: HfmdClient; calls: string[] } {32 const m = mockFetch(routes);33 const client = new HfmdClient({ baseUrl: 'https://api.test', apiKey: opts.apiKey ?? '', fetchImpl: m.fetch });34 return { client, calls: m.calls };35}3637export function bars(n: number, start = '2025-01-01', ticker = 'AAPL') {38 const rows = [];39 const d = new Date(start + 'T00:00:00Z');40 for (let i = 0; i < n; i++) {41 const date = new Date(d.getTime() + i * 86_400_000).toISOString().slice(0, 10);42 rows.push({ ticker, datetime: date, open: 100 + i, high: 101 + i, low: 99 + i, close: 100.5 + i, volume: 1000 + i });43 }44 return rows;45}4647export const RATE_HEADERS = {48 'x-ratelimit-limit-requests': '30',49 'x-ratelimit-remaining-requests': '29',50 'x-ratelimit-limit-rows': '100000',51 'x-ratelimit-remaining-rows': '99500',52 'x-ratelimit-reset': '3599',53 'x-row-count': '5',54};5556/** Minimal fake WebSocket emitting canned messages after open. */57export function fakeWebSocketClass(messages: unknown[], opts: { failOpen?: boolean; delayMs?: number } = {}) {58 const instances: FakeWS[] = [];59 class FakeWS {60 url: string;61 sent: string[] = [];62 closed = false;63 private listeners: Record<string, ((ev: any) => void)[]> = {};64 constructor(url: string) {65 this.url = url;66 instances.push(this);67 setTimeout(() => {68 if (opts.failOpen) return this.emit('error', {});69 this.emit('open', {});70 for (const m of messages) setTimeout(() => this.emit('message', { data: typeof m === 'string' ? m : JSON.stringify(m) }), opts.delayMs ?? 1);71 }, 1);72 }73 addEventListener(type: string, cb: (ev: any) => void) {74 (this.listeners[type] ??= []).push(cb);75 }76 emit(type: string, ev: unknown) {77 if (this.closed) return;78 for (const cb of this.listeners[type] ?? []) cb(ev);79 }80 send(data: string) {81 this.sent.push(data);82 }83 close() {84 this.closed = true;85 }86 }87 return { FakeWS: FakeWS as unknown as typeof WebSocket, instances };88}89