LCOV - code coverage report
Current view: top level - dashql-app/src/demos/stock_analysis - binance_feed.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 53 53
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import type { Tick } from './tick_feed.js';
       2              : import { parseFeedNumber, WebSocketTickFeed, type FeedMessage, type FeedProtocol, type WebSocketFeedOptions } from './websocket_feed.js';
       3              : 
       4              : /// The public market data streams of Binance, the host only serves market data and needs no credentials
       5              : export const BINANCE_FEED_URL = 'wss://data-stream.binance.vision/stream';
       6              : 
       7              : /// The pairs of the board, as Binance names their streams
       8            1 : export const BINANCE_PAIRS: readonly string[] = [
       9            1 :     'btcusdt', 'ethusdt', 'bnbusdt', 'solusdt', 'xrpusdt', 'dogeusdt', 'adausdt', 'trxusdt', 'avaxusdt', 'linkusdt',
      10            1 :     'dotusdt', 'ltcusdt', 'bchusdt', 'shibusdt', 'nearusdt', 'aptusdt', 'uniusdt', 'atomusdt', 'xlmusdt', 'etcusdt',
      11            1 : ];
      12              : 
      13              : /// The quote currencies of the pairs, the longest first so that USDT is not read as USD
      14            1 : const QUOTES = ['USDT', 'USDC', 'FDUSD', 'BTC', 'ETH', 'EUR', 'USD'];
      15              : 
      16              : /// The symbol of a pair on the board, e.g. BTCUSDT as BTC-USDT like the pairs of Coinbase
      17            1 : export function binanceSymbol(pair: string): string {
      18            1 :     const upper = pair.toUpperCase();
      19            1 :     const quote = QUOTES.find(q => upper.endsWith(q) && upper.length > q.length);
      20            1 :     return quote == null ? upper : `${upper.slice(0, -quote.length)}-${quote}`;
      21            1 : }
      22              : 
      23              : /// The url of the combined stream of the trades and the best quotes of pairs
      24            1 : export function binanceStreamUrl(url: string, pairs: readonly string[]): string {
      25            1 :     return `${url}?streams=${pairs.flatMap(p => [`${p}@trade`, `${p}@bookTicker`]).join('/')}`;
      26            1 : }
      27              : 
      28              : /// The best bid and ask of a pair
      29              : interface Quote {
      30              :     bid: number | null;
      31              :     ask: number | null;
      32              : }
      33              : 
      34              : /// Reads the combined stream of trades and best quotes.
      35              : /// A trade carries no quote, it gets the latest best bid and ask of its pair.
      36            1 : export class BinanceProtocol implements FeedProtocol {
      37              :     /// The latest quote per pair
      38            1 :     private readonly quotes = new Map<string, Quote>();
      39              : 
      40            1 :     constructor(readonly url: string) { }
      41              : 
      42            1 :     subscription(): string | null {
      43            1 :         return null;
      44            1 :     }
      45              : 
      46            1 :     read(data: string): FeedMessage {
      47            1 :         let message: any;
      48            1 :         try {
      49            1 :             message = JSON.parse(data);
      50            1 :         } catch {
      51            1 :             return null;
      52            1 :         }
      53            1 :         if (message?.error != null) {
      54            1 :             return { error: typeof message.error.msg === 'string' ? message.error.msg : 'error' };
      55              :         }
      56            1 :         const payload = message?.data;
      57            1 :         if (typeof message?.stream !== 'string' || payload == null || typeof payload.s !== 'string') return null;
      58            1 :         if (message.stream.endsWith('@bookTicker')) {
      59            1 :             this.quotes.set(payload.s, { bid: parseFeedNumber(payload.b), ask: parseFeedNumber(payload.a) });
      60            1 :             return null;
      61            1 :         }
      62            1 :         if (payload.e !== 'trade') return null;
      63            1 :         const price = parseFeedNumber(payload.p);
      64            1 :         const ts = parseFeedNumber(payload.T);
      65            1 :         if (price == null || ts == null) return null;
      66            1 :         const quote = this.quotes.get(payload.s);
      67              :         const tick: Tick = {
      68            1 :             symbol: binanceSymbol(payload.s),
      69            1 :             ts,
      70            1 :             price,
      71            1 :             size: parseFeedNumber(payload.q) ?? 0,
      72            1 :             bid: quote?.bid ?? null,
      73            1 :             ask: quote?.ask ?? null,
      74              :         };
      75            1 :         return { tick };
      76            1 :     }
      77            1 : }
      78              : 
      79              : /// The options of the Binance feed
      80              : export interface BinanceFeedOptions extends WebSocketFeedOptions {
      81              :     /// The url of the combined streams
      82              :     url?: string;
      83              :     /// The pairs to subscribe to, as Binance names their streams
      84              :     pairs?: readonly string[];
      85              : }
      86              : 
      87              : /// The trades of crypto pairs on Binance, from its public trade and book ticker streams
      88            1 : export class BinanceFeed extends WebSocketTickFeed {
      89            1 :     constructor(options: BinanceFeedOptions = {}) {
      90            1 :         super(new BinanceProtocol(binanceStreamUrl(options.url ?? BINANCE_FEED_URL, options.pairs ?? BINANCE_PAIRS)), options);
      91            1 :     }
      92            1 : }
        

Generated by: LCOV version 2.0-1