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