SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
3.4 KB · 80 lines typescript
Raw Blame History
1import { defineConnector, raw, type NormalizedBatch } from "@market-atlas/connector-sdk";2import { parseTimestamp } from "@market-atlas/market-model";3import { MAJOR_BASES } from "../_shared/crypto.js";4import { pairObservations, planPair, splitVenueSymbol } from "../_shared/pairs.js";56const WS_URL = "wss://stream.bybit.com/v5/public/spot";7const SYMBOLS = [...MAJOR_BASES.filter((b) => b !== "PAXG").map((b) => `${b}USDT`), "USDTEUR", "USDCEUR", "USDTBRL", "USDTTRY", "USDTAED"];89/** Bybit v5 public spot stream, `tickers.<symbol>` (snapshot + deltas with the full last price). Ping `{"op":"ping"}` every 20 s. */10export const bybitWs = defineConnector({11  metadata: {12    id: "bybit-ws",13    name: "Bybit — spot tickers (v5)",14    version: "1.0.0",15    sourceId: "bybit",16    organization: "Bybit",17    sourceType: "WEBSOCKET",18    jurisdiction: null,19    rightsStatus: "PUBLIC_ATTRIBUTED",20    realtimeStatus: "REALTIME",21    expectedLatencyMs: 500,22    supportsStreaming: true,23    supportsHistorical: false,24    assetClasses: ["CRYPTO", "FOREX"],25    exchanges: ["bybit"],26    homepage: "https://bybit-exchange.github.io/docs/v5/websocket/public/ticker",27    description: "Public Bybit v5 spot ticker stream (last price, 24h high/low/volume, previous 24h price) for USDT majors and the USDT/EUR, USDC/EUR, USDT/BRL, USDT/TRY, USDT/AED stablecoin markets (live FX proxies).",28    rightsNotes: "Public market data displayed with attribution to Bybit.",29    termsUrl: "https://www.bybit.com/en/terms-service/",30    sourceFamily: "bybit",31    enabled: true,32  },33  seeds: SYMBOLS.map((s) => {34    const [b, q] = splitVenueSymbol(s, null)!;35    return { symbol: s, hint: planPair(b, q, "bybit").hint };36  }),37  defaultSymbols: SYMBOLS,38  async start(ctx) {39    const ws = ctx.openWebSocket(WS_URL, {40      label: "bybit",41      staleAfterMs: 60_000,42      heartbeat: { intervalMs: 20_000, message: JSON.stringify({ op: "ping" }) },43      onOpen: (sock) => {44        // Bybit accepts at most 10 topics per subscribe request.45        const topics = ctx.watchedSymbols().map((s) => `tickers.${s}`);46        for (let i = 0; i < topics.length; i += 10) sock.send({ op: "subscribe", args: topics.slice(i, i + 10) });47      },48      onMessage: (data) => {49        let msg: any;50        try {51          msg = JSON.parse(data);52        } catch {53          return;54        }55        if (typeof msg?.topic === "string" && msg.topic.startsWith("tickers.") && msg.data) ctx.emit(raw("bybit-ws", "bybit", "ticker", msg));56        else if (msg?.success === false) ctx.reportError(new Error(String(msg.ret_msg ?? "bybit error")));57      },58    });59    ws.connect();60  },61  normalize(r): NormalizedBatch {62    const m = r.payload as { ts?: number; data?: Record<string, unknown>; cs?: number };63    const d = m.data;64    if (r.kind !== "ticker" || !d || typeof d.symbol !== "string") return { observations: [] };65    const split = splitVenueSymbol(d.symbol, null);66    if (!split) return { observations: [] };67    const [base, quote] = split;68    return {69      observations: pairObservations(d.symbol, base, quote, "bybit", { last: d.lastPrice, high: d.highPrice24h, low: d.lowPrice24h, volume: d.volume24h, open: d.prevPrice24h }, {70        sourceTimestamp: parseTimestamp(m.ts),71        timestampTrust: "EXCHANGE",72        sequence: typeof m.cs === "number" ? m.cs : null,73        rightsStatus: "PUBLIC_ATTRIBUTED",74        realtimeStatus: "REALTIME",75      }),76    };77  },78  fixturesDir: "fixtures",79});80