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.0 KB · 74 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 } from "../_shared/pairs.js";56const WS_URL = "wss://api.gateio.ws/ws/v4/";7const SYMBOLS = MAJOR_BASES.filter((b) => b !== "PAXG").map((b) => `${b}_USDT`);89/** Gate.io spot WebSocket v4, `spot.tickers` channel. Ping via `spot.ping`. */10export const gateWs = defineConnector({11  metadata: {12    id: "gate-ws",13    name: "Gate — spot tickers (WS v4)",14    version: "1.0.0",15    sourceId: "gate",16    organization: "Gate Technology Inc.",17    sourceType: "WEBSOCKET",18    jurisdiction: null,19    rightsStatus: "PUBLIC_ATTRIBUTED",20    realtimeStatus: "REALTIME",21    expectedLatencyMs: 1000,22    supportsStreaming: true,23    supportsHistorical: false,24    assetClasses: ["CRYPTO"],25    exchanges: ["gate"],26    homepage: "https://www.gate.io/docs/developers/apiv4/ws/en/#tickers-channel",27    description: "Public Gate.io spot ticker channel (last, best bid/ask, 24h high/low/volume) for USDT majors.",28    rightsNotes: "Public market data displayed with attribution to Gate.",29    termsUrl: "https://www.gate.io/user-agreement",30    sourceFamily: "gate",31    enabled: true,32  },33  seeds: SYMBOLS.map((s) => {34    const [b, q] = s.split("_") as [string, string];35    return { symbol: s, hint: planPair(b, q, "gate").hint };36  }),37  defaultSymbols: SYMBOLS,38  async start(ctx) {39    const ws = ctx.openWebSocket(WS_URL, {40      label: "gate",41      staleAfterMs: 60_000,42      heartbeat: { intervalMs: 20_000, message: JSON.stringify({ time: Math.floor(Date.now() / 1000), channel: "spot.ping" }) },43      onOpen: (sock) => sock.send({ time: Math.floor(Date.now() / 1000), channel: "spot.tickers", event: "subscribe", payload: ctx.watchedSymbols() }),44      onMessage: (data) => {45        let msg: any;46        try {47          msg = JSON.parse(data);48        } catch {49          return;50        }51        if (msg?.channel === "spot.tickers" && msg.event === "update" && msg.result) ctx.emit(raw("gate-ws", "gate", "ticker", msg));52        else if (msg?.error) ctx.reportError(new Error(String(msg.error.message ?? "gate error")));53      },54    });55    ws.connect();56  },57  normalize(r): NormalizedBatch {58    const m = r.payload as { time_ms?: number; result?: Record<string, unknown> };59    const d = m.result;60    if (r.kind !== "ticker" || !d || typeof d.currency_pair !== "string") return { observations: [] };61    const [base, quote] = d.currency_pair.split("_");62    if (!base || !quote) return { observations: [] };63    return {64      observations: pairObservations(d.currency_pair, base, quote, "gate", { last: d.last, bid: d.highest_bid, ask: d.lowest_ask, high: d.high_24h, low: d.low_24h, volume: d.base_volume }, {65        sourceTimestamp: parseTimestamp(m.time_ms),66        timestampTrust: "EXCHANGE",67        rightsStatus: "PUBLIC_ATTRIBUTED",68        realtimeStatus: "REALTIME",69      }),70    };71  },72  fixturesDir: "fixtures",73});74