@xapy/orderbook 0.1.5 → 0.1.6

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
@@ -1624,44 +1624,101 @@ function streamCoinexOrderbook(opts) {
1624
1624
  const merger = new BookMerger();
1625
1625
  let ws = null;
1626
1626
  const stream = new OrderbookStream(() => ws?.close());
1627
- const subscribePayload = JSON.stringify({
1627
+ const subscribeDepth = JSON.stringify({
1628
1628
  method: "depth.subscribe",
1629
1629
  params: {
1630
- // [market, limit, interval, diff_flag] — diff_flag=falsefull snapshots
1630
+ // [market, limit, interval, diff_flag] — diff_flag=truesnapshots 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).
1631
1633
  market_list: [[market, depth, "0", true]]
1632
1634
  },
1633
1635
  id: 1
1634
1636
  });
1635
- const handlePayload = (text) => {
1636
- let msg;
1637
- try {
1638
- msg = JSON.parse(text);
1639
- } catch {
1640
- return;
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
+ }
1641
1657
  }
1642
- if (msg.id != null && !msg.method) return;
1643
- if (msg.method !== "depth.update" || !msg.data) return;
1644
- if (msg.data.market !== market) return;
1645
- const d = msg.data.depth;
1646
- const event = {
1647
- kind: "snapshot",
1648
- bids: d.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1649
- asks: d.asks.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1650
- sequence: d.updated_at,
1651
- timestamp: d.updated_at
1652
- };
1653
- const r = merger.apply(event);
1654
- if (!r.ok) return;
1655
1658
  const book = {
1656
1659
  exchange: "coinex",
1657
1660
  symbol: fromCoinexSymbol(market),
1658
1661
  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 = {
1659
1687
  bids: r.bids,
1660
1688
  asks: r.asks,
1661
1689
  timestamp: r.timestamp,
1662
1690
  sequence: r.sequence
1663
1691
  };
1664
- stream.emit("update", book);
1692
+ emitOverlayed();
1693
+ };
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
1705
+ };
1706
+ emitOverlayed();
1707
+ };
1708
+ const handlePayload = (text) => {
1709
+ let msg;
1710
+ try {
1711
+ msg = JSON.parse(text);
1712
+ } catch {
1713
+ return;
1714
+ }
1715
+ 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
+ }
1665
1722
  };
1666
1723
  const onMessage = async (raw) => {
1667
1724
  if (typeof raw === "string") {
@@ -1675,7 +1732,10 @@ function streamCoinexOrderbook(opts) {
1675
1732
  url: opts.market === "spot" ? WS_SPOT5 : WS_FUTURES2,
1676
1733
  onOpen: (sock) => {
1677
1734
  merger.reset();
1678
- sock.send(subscribePayload);
1735
+ lastMerged = null;
1736
+ lastTicker = null;
1737
+ sock.send(subscribeDepth);
1738
+ sock.send(subscribeBbo);
1679
1739
  },
1680
1740
  onMessage
1681
1741
  // Native WS ping/pong frames keep the connection alive — no app-level ping.
@@ -1687,6 +1747,32 @@ function streamCoinexOrderbook(opts) {
1687
1747
  ws.connect().catch((e) => stream.emit("error", e));
1688
1748
  return stream;
1689
1749
  }
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)];
1775
+ }
1690
1776
 
1691
1777
  // src/exchanges/coinex/index.ts
1692
1778
  var CoinexClient = class {