spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
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