SPB Git forge

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)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%
3.2 KB · 89 lines typescript
Raw Blame History
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