LCOV - code coverage report
Current view: top level - dashql-app/src/demos/stock_analysis - coinbase_feed.ts Coverage Total Hit
Test: DashQL 915828b Lines: 95.9 % 49 47
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 WebSocketFeedOptions } from './websocket_feed.js';
       3              : 
       4              : /// The public market data feed of Coinbase Exchange, its ticker channel needs no credentials
       5              : export const COINBASE_FEED_URL = 'wss://ws-feed.exchange.coinbase.com';
       6              : 
       7              : /// The pairs of the board
       8            1 : export const COINBASE_PRODUCTS: readonly string[] = [
       9            1 :     'BTC-USD', 'ETH-USD', 'SOL-USD', 'XRP-USD', 'DOGE-USD', 'ADA-USD', 'AVAX-USD', 'LINK-USD',
      10            1 :     'DOT-USD', 'LTC-USD', 'BCH-USD', 'UNI-USD', 'AAVE-USD', 'ATOM-USD', 'XLM-USD', 'ETC-USD',
      11            1 :     'FIL-USD', 'NEAR-USD', 'APT-USD', 'ARB-USD', 'OP-USD', 'SHIB-USD',
      12            1 : ];
      13              : 
      14              : /// The trade of a ticker message, null for other messages, e.g. the confirmation of the subscription
      15            1 : export function parseCoinbaseMessage(data: string): Tick | null {
      16            1 :     let message: any;
      17            1 :     try {
      18            1 :         message = JSON.parse(data);
      19            1 :     } catch {
      20            1 :         return null;
      21            1 :     }
      22            1 :     if (message?.type !== 'ticker' || typeof message.product_id !== 'string') return null;
      23            1 :     const price = parseFeedNumber(message.price);
      24            1 :     const ts = typeof message.time === 'string' ? Date.parse(message.time) : NaN;
      25            1 :     if (price == null || !Number.isFinite(ts)) return null;
      26            1 :     return {
      27            1 :         symbol: message.product_id,
      28            1 :         ts,
      29            1 :         price,
      30            1 :         size: parseFeedNumber(message.last_size) ?? 0,
      31            1 :         bid: parseFeedNumber(message.best_bid),
      32            1 :         ask: parseFeedNumber(message.best_ask),
      33            1 :     };
      34            1 : }
      35              : 
      36              : /// The error of an error message of the feed, null for other messages
      37            1 : export function parseCoinbaseError(data: string): string | null {
      38            1 :     try {
      39            1 :         const message = JSON.parse(data);
      40            1 :         if (message?.type !== 'error') return null;
      41            1 :         return [message.message, message.reason].filter(m => typeof m === 'string' && m.length > 0).join(': ') || 'error';
      42            1 :     } catch {
      43            0 :         return null;
      44            0 :     }
      45            1 : }
      46              : 
      47              : /// The options of the Coinbase feed
      48              : export interface CoinbaseFeedOptions extends WebSocketFeedOptions {
      49              :     /// The url of the feed
      50              :     url?: string;
      51              :     /// The pairs to subscribe to
      52              :     products?: readonly string[];
      53              : }
      54              : 
      55              : /// The trades of crypto pairs on Coinbase, from its public ticker channel.
      56              : /// A ticker message carries the trade together with the best bid and ask.
      57            1 : export class CoinbaseFeed extends WebSocketTickFeed {
      58            1 :     constructor(options: CoinbaseFeedOptions = {}) {
      59            1 :         const products = options.products ?? COINBASE_PRODUCTS;
      60            1 :         super({
      61            1 :             url: options.url ?? COINBASE_FEED_URL,
      62            1 :             subscription: () => JSON.stringify({ type: 'subscribe', product_ids: products, channels: ['ticker'] }),
      63            1 :             read: (data: string): FeedMessage => {
      64            1 :                 const tick = parseCoinbaseMessage(data);
      65            1 :                 if (tick != null) return { tick };
      66            1 :                 const error = parseCoinbaseError(data);
      67            1 :                 return error == null ? null : { error };
      68            1 :             },
      69            1 :         }, options);
      70            1 :     }
      71            1 : }
        

Generated by: LCOV version 2.0-1