Real-time BTC cross-exchange arbitrage detection (Kraken, Bybit, OKX, Binance) with React dashboard, GitHub Actions CI, and documented Fly.io deploy workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
import type { ExchangeId, Level, OrderBook } from "../domain/entities/index.js";
|
|
import type { MarketDataFeed, MarketDataFeedFactory } from "../domain/ports/ports.js";
|
|
|
|
/** NDJSON line shape written by FeedRecorder (replay-compatible). */
|
|
export interface NdjsonBookLine {
|
|
ts: number;
|
|
exchange: ExchangeId;
|
|
bids: Level[];
|
|
asks: Level[];
|
|
}
|
|
|
|
export function lineToOrderBook(line: NdjsonBookLine): OrderBook {
|
|
return {
|
|
exchange: line.exchange,
|
|
bids: line.bids,
|
|
asks: line.asks,
|
|
recvTs: line.ts,
|
|
exchangeTs: line.ts,
|
|
};
|
|
}
|
|
|
|
/** In-memory feed for integration tests — no network or WebSocket. */
|
|
export class FakeMarketDataFeed implements MarketDataFeed {
|
|
private listeners: Array<(book: OrderBook) => void> = [];
|
|
|
|
constructor(private readonly fixtures: OrderBook[]) {}
|
|
|
|
onBook(listener: (book: OrderBook) => void): void {
|
|
this.listeners.push(listener);
|
|
}
|
|
|
|
start(): void {
|
|
for (const fixture of this.fixtures) {
|
|
for (const listener of this.listeners) listener(fixture);
|
|
}
|
|
}
|
|
|
|
stop(): void {
|
|
/* no-op */
|
|
}
|
|
}
|
|
|
|
export class FakeMarketDataFeedFactory implements MarketDataFeedFactory {
|
|
constructor(private readonly fixtures: OrderBook[]) {}
|
|
|
|
create(id: ExchangeId): MarketDataFeed {
|
|
return new FakeMarketDataFeed(this.fixtures.filter((f) => f.exchange === id));
|
|
}
|
|
}
|