@xapy/orderbook 0.1.6 → 0.1.7

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
@@ -1616,94 +1616,61 @@ async function fetchCoinexOrderbook(opts) {
1616
1616
  }
1617
1617
 
1618
1618
  // src/exchanges/coinex/ws.ts
1619
- var WS_SPOT5 = "wss://socket.coinex.com/v2/spot";
1620
- var WS_FUTURES2 = "wss://socket.coinex.com/v2/futures";
1619
+ var WS_SPOT5 = "wss://socket.coinex.com/";
1620
+ var WS_FUTURES2 = "wss://perpetual.coinex.com/";
1621
1621
  function streamCoinexOrderbook(opts) {
1622
1622
  const market = toCoinexSymbol(opts.symbol);
1623
1623
  const depth = opts.depth ?? 50;
1624
1624
  const merger = new BookMerger();
1625
+ let seq = 0;
1625
1626
  let ws = null;
1626
1627
  const stream = new OrderbookStream(() => ws?.close());
1627
- const subscribeDepth = JSON.stringify({
1628
+ const subscribePayload = JSON.stringify({
1628
1629
  method: "depth.subscribe",
1629
- params: {
1630
- // [market, limit, interval, diff_flag] — diff_flag=true → snapshots only
1631
- // (CoinEx flips the sense of the flag in V2; our handler treats every
1632
- // frame as a snapshot to stay correct under either reading).
1633
- market_list: [[market, depth, "0", true]]
1634
- },
1630
+ params: [market, depth, "0", true],
1635
1631
  id: 1
1636
1632
  });
1637
- const subscribeBbo = JSON.stringify({
1638
- method: "bbo.subscribe",
1639
- params: { market_list: [market] },
1640
- id: 2
1641
- });
1642
- let lastMerged = null;
1643
- let lastTicker = null;
1644
- const emitOverlayed = () => {
1645
- if (!lastMerged) return;
1646
- let bids = lastMerged.bids;
1647
- let asks = lastMerged.asks;
1648
- let timestamp = lastMerged.timestamp;
1649
- let sequence = lastMerged.sequence;
1650
- if (lastTicker) {
1651
- bids = overlayBid2(bids, lastTicker.bid);
1652
- asks = overlayAsk2(asks, lastTicker.ask);
1653
- if (lastTicker.ts > timestamp) {
1654
- timestamp = lastTicker.ts;
1655
- sequence = Math.max(sequence, lastTicker.ts);
1656
- }
1657
- }
1633
+ const pingPayload = () => JSON.stringify({ method: "server.ping", params: [], id: 11 });
1634
+ const emit = (r) => {
1658
1635
  const book = {
1659
1636
  exchange: "coinex",
1660
1637
  symbol: fromCoinexSymbol(market),
1661
1638
  market: opts.market,
1662
- bids,
1663
- asks,
1664
- timestamp,
1665
- sequence
1666
- };
1667
- stream.emit("update", book);
1668
- };
1669
- const handleDepth = (d) => {
1670
- if (d.market !== market) return;
1671
- const inner = d.depth;
1672
- const r = merger.apply({
1673
- kind: "snapshot",
1674
- bids: inner.bids.map(([p, s]) => ({
1675
- price: Number(p),
1676
- size: Number(s)
1677
- })),
1678
- asks: inner.asks.map(([p, s]) => ({
1679
- price: Number(p),
1680
- size: Number(s)
1681
- })),
1682
- sequence: inner.updated_at,
1683
- timestamp: inner.updated_at
1684
- });
1685
- if (!r.ok) return;
1686
- lastMerged = {
1687
1639
  bids: r.bids,
1688
1640
  asks: r.asks,
1689
1641
  timestamp: r.timestamp,
1690
1642
  sequence: r.sequence
1691
1643
  };
1692
- emitOverlayed();
1644
+ stream.emit("update", book);
1693
1645
  };
1694
- const handleBbo = (b) => {
1695
- if (b.market !== market) return;
1696
- const bidPrice = Number(b.best_bid_price);
1697
- const askPrice = Number(b.best_ask_price);
1698
- const bidSize = Number(b.best_bid_size);
1699
- const askSize = Number(b.best_ask_size);
1700
- if (!Number.isFinite(bidPrice) || !Number.isFinite(askPrice)) return;
1701
- lastTicker = {
1702
- bid: { price: bidPrice, size: bidSize },
1703
- ask: { price: askPrice, size: askSize },
1704
- ts: b.updated_at
1646
+ const handleDepthPush = (push) => {
1647
+ const [clean, payload, mkt] = push;
1648
+ if (mkt !== market) return;
1649
+ const bids = parseLevels(payload.bids);
1650
+ const asks = parseLevels(payload.asks);
1651
+ const event = clean ? {
1652
+ kind: "snapshot",
1653
+ bids,
1654
+ asks,
1655
+ sequence: ++seq,
1656
+ timestamp: payload.time
1657
+ } : {
1658
+ kind: "delta",
1659
+ bids,
1660
+ asks,
1661
+ sequence: ++seq,
1662
+ timestamp: payload.time
1705
1663
  };
1706
- emitOverlayed();
1664
+ const r = merger.apply(event);
1665
+ if (r.ok) {
1666
+ emit(r);
1667
+ return;
1668
+ }
1669
+ if (r.reason === "no-snapshot") return;
1670
+ if (r.reason === "gap") {
1671
+ merger.reset();
1672
+ seq = 0;
1673
+ }
1707
1674
  };
1708
1675
  const handlePayload = (text) => {
1709
1676
  let msg;
@@ -1713,32 +1680,31 @@ function streamCoinexOrderbook(opts) {
1713
1680
  return;
1714
1681
  }
1715
1682
  if (msg.id != null && !msg.method) return;
1716
- if (!msg.data || !msg.method) return;
1717
- if (msg.method === "depth.update") {
1718
- handleDepth(msg.data);
1719
- } else if (msg.method === "bbo.update") {
1720
- handleBbo(msg.data);
1721
- }
1722
- };
1723
- const onMessage = async (raw) => {
1724
- if (typeof raw === "string") {
1725
- handlePayload(raw);
1683
+ if (msg.error) {
1684
+ stream.emit(
1685
+ "error",
1686
+ new Error(`coinex: ${msg.error.code} ${msg.error.message}`)
1687
+ );
1726
1688
  return;
1727
1689
  }
1728
- const inflate = await getGzipInflate();
1729
- handlePayload(inflate(raw));
1690
+ if (msg.method === "depth.update" && Array.isArray(msg.params)) {
1691
+ handleDepthPush(msg.params);
1692
+ }
1693
+ };
1694
+ const onMessage = (raw) => {
1695
+ if (typeof raw !== "string") return;
1696
+ handlePayload(raw);
1730
1697
  };
1731
1698
  ws = new WSClient({
1732
1699
  url: opts.market === "spot" ? WS_SPOT5 : WS_FUTURES2,
1733
1700
  onOpen: (sock) => {
1734
1701
  merger.reset();
1735
- lastMerged = null;
1736
- lastTicker = null;
1737
- sock.send(subscribeDepth);
1738
- sock.send(subscribeBbo);
1702
+ seq = 0;
1703
+ sock.send(subscribePayload);
1739
1704
  },
1740
- onMessage
1741
- // Native WS ping/pong frames keep the connection alive — no app-level ping.
1705
+ onMessage,
1706
+ pingIntervalMs: 25e3,
1707
+ pingPayload
1742
1708
  });
1743
1709
  ws.on("open", () => stream.emit("connected"));
1744
1710
  ws.on("close", (r) => stream.emit("disconnected", r));
@@ -1747,31 +1713,11 @@ function streamCoinexOrderbook(opts) {
1747
1713
  ws.connect().catch((e) => stream.emit("error", e));
1748
1714
  return stream;
1749
1715
  }
1750
- function overlayAsk2(asks, top) {
1751
- if (!(top.size > 0)) return asks;
1752
- let i = 0;
1753
- for (; i < asks.length; i++) {
1754
- const level = asks[i];
1755
- if (!level || level.price >= top.price) break;
1756
- }
1757
- const at = asks[i];
1758
- if (at && at.price === top.price) {
1759
- return [{ price: top.price, size: top.size }, ...asks.slice(i + 1)];
1760
- }
1761
- return [{ price: top.price, size: top.size }, ...asks.slice(i)];
1762
- }
1763
- function overlayBid2(bids, top) {
1764
- if (!(top.size > 0)) return bids;
1765
- let i = 0;
1766
- for (; i < bids.length; i++) {
1767
- const level = bids[i];
1768
- if (!level || level.price <= top.price) break;
1769
- }
1770
- const at = bids[i];
1771
- if (at && at.price === top.price) {
1772
- return [{ price: top.price, size: top.size }, ...bids.slice(i + 1)];
1773
- }
1774
- return [{ price: top.price, size: top.size }, ...bids.slice(i)];
1716
+ function parseLevels(rows) {
1717
+ if (!rows) return [];
1718
+ const out = [];
1719
+ for (const [p, s] of rows) out.push({ price: Number(p), size: Number(s) });
1720
+ return out;
1775
1721
  }
1776
1722
 
1777
1723
  // src/exchanges/coinex/index.ts
@@ -2469,8 +2415,8 @@ async function fetchKucoinOrderbook(opts) {
2469
2415
  exchange: "kucoin",
2470
2416
  symbol: fromKucoinSpotSymbol(symbol2),
2471
2417
  market: "spot",
2472
- bids: parseLevels(res2.data.bids),
2473
- asks: parseLevels(res2.data.asks),
2418
+ bids: parseLevels2(res2.data.bids),
2419
+ asks: parseLevels2(res2.data.asks),
2474
2420
  timestamp: res2.data.time,
2475
2421
  sequence: Number(res2.data.sequence)
2476
2422
  };
@@ -2489,13 +2435,13 @@ async function fetchKucoinOrderbook(opts) {
2489
2435
  exchange: "kucoin",
2490
2436
  symbol: fromKucoinFuturesSymbol(symbol),
2491
2437
  market: "perpetual",
2492
- bids: parseLevels(res.data.bids),
2493
- asks: parseLevels(res.data.asks),
2438
+ bids: parseLevels2(res.data.bids),
2439
+ asks: parseLevels2(res.data.asks),
2494
2440
  timestamp: res.data.ts,
2495
2441
  sequence: res.data.sequence
2496
2442
  };
2497
2443
  }
2498
- function parseLevels(rows) {
2444
+ function parseLevels2(rows) {
2499
2445
  return rows.map(([p, s]) => ({ price: Number(p), size: Number(s) }));
2500
2446
  }
2501
2447