@xapy/orderbook 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -847,9 +847,131 @@ function parseObjectLevels(rows) {
847
847
  var WS_SPOT2 = "wss://api.gateio.ws/ws/v4/";
848
848
  var WS_FUTURES = "wss://fx-ws.gateio.ws/v4/ws/usdt";
849
849
  function streamGateOrderbook(opts) {
850
- const isFutures = opts.market === "perpetual";
850
+ return opts.market === "perpetual" ? streamGateFuturesObu(opts) : streamGateSpot(opts);
851
+ }
852
+ function streamGateFuturesObu(opts) {
853
+ const gateSymbol = toGateSymbol(opts.symbol);
854
+ const level = snapFuturesLevel(opts.depth ?? 50);
855
+ const streamName = `ob.${gateSymbol}.${level}`;
856
+ const merger = new BookMerger();
857
+ let lastU = -1;
858
+ let ws = null;
859
+ const stream = new OrderbookStream(() => ws?.close());
860
+ const wsMsg = (event) => JSON.stringify({
861
+ time: Math.floor(Date.now() / 1e3),
862
+ channel: "futures.obu",
863
+ event,
864
+ payload: [streamName]
865
+ });
866
+ const pingMsg = () => JSON.stringify({
867
+ time: Math.floor(Date.now() / 1e3),
868
+ channel: "futures.ping"
869
+ });
870
+ const emitBook = (bids, asks, sequence, timestamp) => {
871
+ const book = {
872
+ exchange: "gate",
873
+ symbol: fromGateSymbol(gateSymbol),
874
+ market: "perpetual",
875
+ bids,
876
+ asks,
877
+ timestamp,
878
+ sequence
879
+ };
880
+ stream.emit("update", book);
881
+ };
882
+ const resubscribe = () => {
883
+ if (!ws) return;
884
+ merger.reset();
885
+ lastU = -1;
886
+ try {
887
+ ws.send(wsMsg("unsubscribe"));
888
+ ws.send(wsMsg("subscribe"));
889
+ } catch (err) {
890
+ stream.emit("error", err);
891
+ }
892
+ };
893
+ const processFrame = (frame) => {
894
+ const bids = parseObuLevels(frame.b);
895
+ const asks = parseObuLevels(frame.a);
896
+ if (frame.full) {
897
+ const r2 = merger.apply({
898
+ kind: "snapshot",
899
+ bids,
900
+ asks,
901
+ sequence: frame.u,
902
+ timestamp: frame.t
903
+ });
904
+ lastU = frame.u;
905
+ if (r2.ok) emitBook(r2.bids, r2.asks, r2.sequence, r2.timestamp);
906
+ return;
907
+ }
908
+ if (!merger.isReady() || lastU < 0 || frame.U === void 0) return;
909
+ if (frame.u <= lastU) return;
910
+ if (frame.U !== lastU + 1) {
911
+ resubscribe();
912
+ return;
913
+ }
914
+ const r = merger.apply({
915
+ kind: "delta",
916
+ bids,
917
+ asks,
918
+ sequence: frame.u,
919
+ prevSequence: lastU,
920
+ timestamp: frame.t
921
+ });
922
+ if (r.ok) {
923
+ lastU = frame.u;
924
+ emitBook(r.bids, r.asks, r.sequence, r.timestamp);
925
+ } else if (r.reason === "gap") {
926
+ resubscribe();
927
+ }
928
+ };
929
+ const onMessage = (raw) => {
930
+ if (typeof raw !== "string") return;
931
+ let msg;
932
+ try {
933
+ msg = JSON.parse(raw);
934
+ } catch {
935
+ return;
936
+ }
937
+ if (msg.error) {
938
+ stream.emit("error", new Error(`gate: ${msg.error.message}`));
939
+ return;
940
+ }
941
+ if (msg.channel !== "futures.obu") return;
942
+ if (msg.event === "subscribe" || msg.event === "unsubscribe") return;
943
+ if (msg.event !== "update" || !msg.result) return;
944
+ processFrame(msg.result);
945
+ };
946
+ ws = new WSClient({
947
+ url: WS_FUTURES,
948
+ onOpen: (sock) => {
949
+ merger.reset();
950
+ lastU = -1;
951
+ sock.send(wsMsg("subscribe"));
952
+ },
953
+ onMessage,
954
+ pingIntervalMs: 15e3,
955
+ pingPayload: pingMsg
956
+ });
957
+ ws.on("open", () => stream.emit("connected"));
958
+ ws.on("close", (r) => stream.emit("disconnected", r));
959
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
960
+ ws.on("error", (e) => stream.emit("error", e));
961
+ ws.connect().catch((e) => stream.emit("error", e));
962
+ return stream;
963
+ }
964
+ function snapFuturesLevel(depth) {
965
+ return depth <= 50 ? 50 : 400;
966
+ }
967
+ function parseObuLevels(rows) {
968
+ if (!rows) return [];
969
+ const out = [];
970
+ for (const [p, s] of rows) out.push({ price: Number(p), size: Number(s) });
971
+ return out;
972
+ }
973
+ function streamGateSpot(opts) {
851
974
  const gateSymbol = toGateSymbol(opts.symbol);
852
- const channel = isFutures ? "futures.order_book_update" : "spot.order_book_update";
853
975
  const interval = `${opts.intervalMs ?? 100}ms`;
854
976
  const depth = String(opts.depth ?? 100);
855
977
  const merger = new BookMerger();
@@ -861,22 +983,21 @@ function streamGateOrderbook(opts) {
861
983
  let recovering = false;
862
984
  let ws = null;
863
985
  const stream = new OrderbookStream(() => ws?.close());
864
- const payload = isFutures ? [gateSymbol, interval, depth] : [gateSymbol, depth, interval];
865
986
  const subscribeMsg = () => JSON.stringify({
866
987
  time: Math.floor(Date.now() / 1e3),
867
- channel,
988
+ channel: "spot.order_book_update",
868
989
  event: "subscribe",
869
- payload
990
+ payload: [gateSymbol, depth, interval]
870
991
  });
871
992
  const pingMsg = () => JSON.stringify({
872
993
  time: Math.floor(Date.now() / 1e3),
873
- channel: isFutures ? "futures.ping" : "spot.ping"
994
+ channel: "spot.ping"
874
995
  });
875
996
  const emitBook = (bids, asks, sequence, timestamp) => {
876
997
  const book = {
877
998
  exchange: "gate",
878
999
  symbol: fromGateSymbol(gateSymbol),
879
- market: opts.market,
1000
+ market: "spot",
880
1001
  bids,
881
1002
  asks,
882
1003
  timestamp,
@@ -889,7 +1010,7 @@ function streamGateOrderbook(opts) {
889
1010
  snapshotInFlight = true;
890
1011
  fetchGateOrderbook({
891
1012
  symbol: opts.symbol,
892
- market: opts.market,
1013
+ market: "spot",
893
1014
  depth: opts.depth ?? 100,
894
1015
  timeoutMs: opts.timeoutMs,
895
1016
  transformUrl: opts.transformUrl
@@ -965,9 +1086,16 @@ function streamGateOrderbook(opts) {
965
1086
  stream.emit("error", new Error(`gate: ${msg.error.message}`));
966
1087
  return;
967
1088
  }
968
- if (msg.channel !== channel || !msg.result) return;
1089
+ if (msg.channel !== "spot.order_book_update" || !msg.result) return;
969
1090
  if (msg.event !== "update") return;
970
- const ev = normalize(msg.result, isFutures);
1091
+ const r = msg.result;
1092
+ const ev = {
1093
+ U: r.U,
1094
+ u: r.u,
1095
+ t: r.t,
1096
+ bids: r.b.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1097
+ asks: r.a.map(([p, s]) => ({ price: Number(p), size: Number(s) }))
1098
+ };
971
1099
  if (!snapshotDone) {
972
1100
  buffered.push(ev);
973
1101
  if (!snapshotInFlight) triggerSnapshot();
@@ -976,7 +1104,7 @@ function streamGateOrderbook(opts) {
976
1104
  processDelta(ev);
977
1105
  };
978
1106
  ws = new WSClient({
979
- url: isFutures ? WS_FUTURES : WS_SPOT2,
1107
+ url: WS_SPOT2,
980
1108
  onOpen: (sock) => {
981
1109
  merger.reset();
982
1110
  snapshotSequence = -1;
@@ -996,26 +1124,6 @@ function streamGateOrderbook(opts) {
996
1124
  ws.connect().catch((e) => stream.emit("error", e));
997
1125
  return stream;
998
1126
  }
999
- function normalize(ev, isFutures) {
1000
- if (isFutures) {
1001
- const f = ev;
1002
- return {
1003
- U: f.U,
1004
- u: f.u,
1005
- t: f.t,
1006
- bids: f.b.map((row) => ({ price: Number(row.p), size: Number(row.s) })),
1007
- asks: f.a.map((row) => ({ price: Number(row.p), size: Number(row.s) }))
1008
- };
1009
- }
1010
- const s = ev;
1011
- return {
1012
- U: s.U,
1013
- u: s.u,
1014
- t: s.t,
1015
- bids: s.b.map(([p, sz]) => ({ price: Number(p), size: Number(sz) })),
1016
- asks: s.a.map(([p, sz]) => ({ price: Number(p), size: Number(sz) }))
1017
- };
1018
- }
1019
1127
 
1020
1128
  // src/exchanges/gate/index.ts
1021
1129
  var GateClient = class {
@@ -1056,10 +1164,6 @@ function toBingxSymbol(symbol) {
1056
1164
  }
1057
1165
  return `${base}_${quote}`.toUpperCase();
1058
1166
  }
1059
- function toBingxTopic(symbol, market, depth = 0.1) {
1060
- const prefix = market === "spot" ? "market-depth" : "swap-depth";
1061
- return `${prefix}.${toBingxSymbol(symbol)}.${depth}`;
1062
- }
1063
1167
  function fromBingxSymbol(s) {
1064
1168
  return s.toUpperCase().replace("_", "/");
1065
1169
  }
@@ -1124,9 +1228,10 @@ var WS_SPOT3 = "wss://open-api-ws.bingx.com/market";
1124
1228
  var WS_SWAP = "wss://open-api-swap.bingx.com/swap-market";
1125
1229
  function streamBingxOrderbook(opts) {
1126
1230
  const isSpot = opts.market === "spot";
1127
- const bingxSymbol = toBingxSymbol(opts.symbol);
1128
- const depth = opts.depth ?? 0.1;
1129
- const dataType = toBingxTopic(opts.symbol, opts.market, depth);
1231
+ const wsSymbol = opts.symbol.toUpperCase().replace("/", "-");
1232
+ const level = opts.depth ?? 20;
1233
+ const interval = opts.interval ?? "500ms";
1234
+ const dataType = `${wsSymbol}@depth${level}@${interval}`;
1130
1235
  const merger = new BookMerger();
1131
1236
  let ws = null;
1132
1237
  const stream = new OrderbookStream(() => ws?.close());
@@ -1161,7 +1266,7 @@ function streamBingxOrderbook(opts) {
1161
1266
  if (!r.ok) return;
1162
1267
  const book = {
1163
1268
  exchange: "bingx",
1164
- symbol: fromBingxSymbol(bingxSymbol),
1269
+ symbol: opts.symbol,
1165
1270
  market: opts.market,
1166
1271
  bids: r.bids,
1167
1272
  asks: r.asks,