@xapy/orderbook 0.1.21 → 0.1.23
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/README.md +14 -9
- package/dist/exchanges/binance/index.cjs +21 -6
- package/dist/exchanges/binance/index.cjs.map +1 -1
- package/dist/exchanges/binance/index.d.cts +1 -1
- package/dist/exchanges/binance/index.d.ts +1 -1
- package/dist/exchanges/binance/index.js +21 -6
- package/dist/exchanges/binance/index.js.map +1 -1
- package/dist/exchanges/bingx/index.cjs +38 -11
- package/dist/exchanges/bingx/index.cjs.map +1 -1
- package/dist/exchanges/bingx/index.d.cts +1 -1
- package/dist/exchanges/bingx/index.d.ts +1 -1
- package/dist/exchanges/bingx/index.js +38 -11
- package/dist/exchanges/bingx/index.js.map +1 -1
- package/dist/exchanges/bitget/index.cjs +21 -6
- package/dist/exchanges/bitget/index.cjs.map +1 -1
- package/dist/exchanges/bitget/index.d.cts +1 -1
- package/dist/exchanges/bitget/index.d.ts +1 -1
- package/dist/exchanges/bitget/index.js +21 -6
- package/dist/exchanges/bitget/index.js.map +1 -1
- package/dist/exchanges/bybit/index.cjs +21 -6
- package/dist/exchanges/bybit/index.cjs.map +1 -1
- package/dist/exchanges/bybit/index.d.cts +1 -1
- package/dist/exchanges/bybit/index.d.ts +1 -1
- package/dist/exchanges/bybit/index.js +21 -6
- package/dist/exchanges/bybit/index.js.map +1 -1
- package/dist/exchanges/coinex/index.cjs +160 -63
- package/dist/exchanges/coinex/index.cjs.map +1 -1
- package/dist/exchanges/coinex/index.d.cts +11 -17
- package/dist/exchanges/coinex/index.d.ts +11 -17
- package/dist/exchanges/coinex/index.js +160 -63
- package/dist/exchanges/coinex/index.js.map +1 -1
- package/dist/exchanges/{bitmart → deribit}/index.cjs +174 -148
- package/dist/exchanges/deribit/index.cjs.map +1 -0
- package/dist/exchanges/deribit/index.d.cts +48 -0
- package/dist/exchanges/deribit/index.d.ts +48 -0
- package/dist/exchanges/{bitmart → deribit}/index.js +172 -146
- package/dist/exchanges/deribit/index.js.map +1 -0
- package/dist/exchanges/gate/index.cjs +21 -6
- package/dist/exchanges/gate/index.cjs.map +1 -1
- package/dist/exchanges/gate/index.d.cts +1 -1
- package/dist/exchanges/gate/index.d.ts +1 -1
- package/dist/exchanges/gate/index.js +21 -6
- package/dist/exchanges/gate/index.js.map +1 -1
- package/dist/exchanges/huobi/index.cjs +21 -6
- package/dist/exchanges/huobi/index.cjs.map +1 -1
- package/dist/exchanges/huobi/index.d.cts +1 -1
- package/dist/exchanges/huobi/index.d.ts +1 -1
- package/dist/exchanges/huobi/index.js +21 -6
- package/dist/exchanges/huobi/index.js.map +1 -1
- package/dist/exchanges/hyperliquid/index.cjs +21 -6
- package/dist/exchanges/hyperliquid/index.cjs.map +1 -1
- package/dist/exchanges/hyperliquid/index.d.cts +1 -1
- package/dist/exchanges/hyperliquid/index.d.ts +1 -1
- package/dist/exchanges/hyperliquid/index.js +21 -6
- package/dist/exchanges/hyperliquid/index.js.map +1 -1
- package/dist/exchanges/kucoin/index.cjs +21 -6
- package/dist/exchanges/kucoin/index.cjs.map +1 -1
- package/dist/exchanges/kucoin/index.d.cts +1 -1
- package/dist/exchanges/kucoin/index.d.ts +1 -1
- package/dist/exchanges/kucoin/index.js +21 -6
- package/dist/exchanges/kucoin/index.js.map +1 -1
- package/dist/exchanges/okx/index.cjs +21 -6
- package/dist/exchanges/okx/index.cjs.map +1 -1
- package/dist/exchanges/okx/index.d.cts +1 -1
- package/dist/exchanges/okx/index.d.ts +1 -1
- package/dist/exchanges/okx/index.js +21 -6
- package/dist/exchanges/okx/index.js.map +1 -1
- package/dist/index.cjs +304 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +304 -184
- package/dist/index.js.map +1 -1
- package/dist/{stream-L9MJjYbE.d.cts → stream-CO3LD9jf.d.cts} +1 -1
- package/dist/{stream-L9MJjYbE.d.ts → stream-CO3LD9jf.d.ts} +1 -1
- package/package.json +7 -6
- package/dist/exchanges/bitmart/index.cjs.map +0 -1
- package/dist/exchanges/bitmart/index.d.cts +0 -68
- package/dist/exchanges/bitmart/index.d.ts +0 -68
- package/dist/exchanges/bitmart/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -325,6 +325,21 @@ async function fetchBybitOrderbook(opts) {
|
|
|
325
325
|
var hasNativeWebSocket = typeof globalThis.WebSocket !== "undefined";
|
|
326
326
|
|
|
327
327
|
// src/transport/ws.ts
|
|
328
|
+
function toError(value, fallback = "unknown error") {
|
|
329
|
+
if (value instanceof Error) {
|
|
330
|
+
return value.message ? value : new Error(`${value.name}: ${fallback}`);
|
|
331
|
+
}
|
|
332
|
+
if (typeof value === "string" && value) return new Error(value);
|
|
333
|
+
return new Error(fallback);
|
|
334
|
+
}
|
|
335
|
+
function normalizeWsError(ev) {
|
|
336
|
+
const detail = ev?.message || (ev?.error instanceof Error ? ev.error.message : void 0) || (typeof ev?.error === "string" ? ev.error : void 0);
|
|
337
|
+
return new Error(detail ? `WebSocket error: ${detail}` : "WebSocket error");
|
|
338
|
+
}
|
|
339
|
+
function describeClose(ev) {
|
|
340
|
+
if (ev?.reason) return ev.reason;
|
|
341
|
+
return ev?.code === void 0 ? "closed" : `closed (code ${ev.code})`;
|
|
342
|
+
}
|
|
328
343
|
var cachedCtor = null;
|
|
329
344
|
async function getWebSocketCtor() {
|
|
330
345
|
if (cachedCtor) return cachedCtor;
|
|
@@ -372,7 +387,7 @@ var WSClient = class extends TypedEmitter {
|
|
|
372
387
|
close: () => this.close()
|
|
373
388
|
});
|
|
374
389
|
} catch (err) {
|
|
375
|
-
this.emit("error", err);
|
|
390
|
+
this.emit("error", toError(err, "onOpen handler failed"));
|
|
376
391
|
}
|
|
377
392
|
};
|
|
378
393
|
ws.onmessage = (e) => {
|
|
@@ -380,21 +395,21 @@ var WSClient = class extends TypedEmitter {
|
|
|
380
395
|
try {
|
|
381
396
|
result = this.cfg.onMessage(e.data);
|
|
382
397
|
} catch (err) {
|
|
383
|
-
this.emit("error", err);
|
|
398
|
+
this.emit("error", toError(err, "onMessage handler failed"));
|
|
384
399
|
return;
|
|
385
400
|
}
|
|
386
401
|
if (result && typeof result.catch === "function") {
|
|
387
402
|
result.catch(
|
|
388
|
-
(err) => this.emit("error", err)
|
|
403
|
+
(err) => this.emit("error", toError(err, "onMessage handler failed"))
|
|
389
404
|
);
|
|
390
405
|
}
|
|
391
406
|
};
|
|
392
407
|
ws.onerror = (e) => {
|
|
393
|
-
this.emit("error",
|
|
408
|
+
this.emit("error", normalizeWsError(e));
|
|
394
409
|
};
|
|
395
410
|
ws.onclose = (e) => {
|
|
396
411
|
this.stopPing();
|
|
397
|
-
const reason = e
|
|
412
|
+
const reason = describeClose(e);
|
|
398
413
|
this.emit("close", reason);
|
|
399
414
|
if (!this.closed) this.scheduleReconnect();
|
|
400
415
|
};
|
|
@@ -434,7 +449,7 @@ var WSClient = class extends TypedEmitter {
|
|
|
434
449
|
}
|
|
435
450
|
this.emit("reconnecting", next.attempt, next.wait);
|
|
436
451
|
setTimeout(() => {
|
|
437
|
-
this.connect().catch((e) => this.emit("error", e));
|
|
452
|
+
this.connect().catch((e) => this.emit("error", toError(e, "reconnect failed")));
|
|
438
453
|
}, next.wait);
|
|
439
454
|
}
|
|
440
455
|
};
|
|
@@ -1234,7 +1249,7 @@ function streamBingxOrderbook(opts) {
|
|
|
1234
1249
|
const wsSymbol = opts.symbol.toUpperCase().replace("/", "-");
|
|
1235
1250
|
const level = opts.depth ?? 20;
|
|
1236
1251
|
const interval = opts.interval ?? "500ms";
|
|
1237
|
-
const depthDataType = `${wsSymbol}@depth${level}@${interval}`;
|
|
1252
|
+
const depthDataType = isSpot ? `${wsSymbol}@depth${level}` : `${wsSymbol}@depth${level}@${interval}`;
|
|
1238
1253
|
const tickerDataType = `${wsSymbol}@bookTicker`;
|
|
1239
1254
|
const merger = new BookMerger();
|
|
1240
1255
|
let ws = null;
|
|
@@ -1277,8 +1292,8 @@ function streamBingxOrderbook(opts) {
|
|
|
1277
1292
|
};
|
|
1278
1293
|
stream.emit("update", book);
|
|
1279
1294
|
};
|
|
1280
|
-
const handleDepth = (d) => {
|
|
1281
|
-
const ts = d.T ?? d.ts ?? Date.now();
|
|
1295
|
+
const handleDepth = (d, frameTs) => {
|
|
1296
|
+
const ts = d.T ?? d.ts ?? frameTs ?? Date.now();
|
|
1282
1297
|
const event = {
|
|
1283
1298
|
kind: "snapshot",
|
|
1284
1299
|
bids: d.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
|
|
@@ -1320,10 +1335,22 @@ function streamBingxOrderbook(opts) {
|
|
|
1320
1335
|
} catch {
|
|
1321
1336
|
return;
|
|
1322
1337
|
}
|
|
1323
|
-
if (msg.id && !msg.data)
|
|
1338
|
+
if (msg.id && !msg.data) {
|
|
1339
|
+
if (msg.code) {
|
|
1340
|
+
stream.emit(
|
|
1341
|
+
"error",
|
|
1342
|
+
new ExchangeError(
|
|
1343
|
+
"bingx",
|
|
1344
|
+
`subscribe failed (${msg.id}): ${msg.code}${msg.msg ? ` ${msg.msg}` : ""}`
|
|
1345
|
+
)
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1324
1350
|
if (!msg.dataType || !msg.data) return;
|
|
1351
|
+
const frameTs = msg.ts ?? msg.timestamp;
|
|
1325
1352
|
if (msg.dataType === depthDataType) {
|
|
1326
|
-
handleDepth(msg.data);
|
|
1353
|
+
handleDepth(msg.data, frameTs);
|
|
1327
1354
|
} else if (msg.dataType === tickerDataType) {
|
|
1328
1355
|
handleTicker(msg.data);
|
|
1329
1356
|
}
|
|
@@ -1618,61 +1645,93 @@ async function fetchCoinexOrderbook(opts) {
|
|
|
1618
1645
|
}
|
|
1619
1646
|
|
|
1620
1647
|
// src/exchanges/coinex/ws.ts
|
|
1621
|
-
var WS_SPOT5 = "wss://socket.coinex.com/";
|
|
1622
|
-
var WS_FUTURES2 = "wss://
|
|
1648
|
+
var WS_SPOT5 = "wss://socket.coinex.com/v2/spot";
|
|
1649
|
+
var WS_FUTURES2 = "wss://socket.coinex.com/v2/futures";
|
|
1623
1650
|
function streamCoinexOrderbook(opts) {
|
|
1624
1651
|
const market = toCoinexSymbol(opts.symbol);
|
|
1625
1652
|
const depth = opts.depth ?? 50;
|
|
1626
1653
|
const merger = new BookMerger();
|
|
1627
|
-
let seq = 0;
|
|
1628
1654
|
let ws = null;
|
|
1629
1655
|
const stream = new OrderbookStream(() => ws?.close());
|
|
1630
|
-
const
|
|
1656
|
+
const subscribeDepth = JSON.stringify({
|
|
1631
1657
|
method: "depth.subscribe",
|
|
1632
|
-
params:
|
|
1658
|
+
params: {
|
|
1659
|
+
// [market, limit, interval, if_full] — `true` = every push is a full
|
|
1660
|
+
// snapshot; the merger revalidates monotonic ordering by updated_at.
|
|
1661
|
+
market_list: [[market, depth, "0", true]]
|
|
1662
|
+
},
|
|
1633
1663
|
id: 1
|
|
1634
1664
|
});
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1665
|
+
const subscribeBbo = JSON.stringify({
|
|
1666
|
+
method: "bbo.subscribe",
|
|
1667
|
+
params: { market_list: [market] },
|
|
1668
|
+
id: 2
|
|
1669
|
+
});
|
|
1670
|
+
let lastMerged = null;
|
|
1671
|
+
let lastTicker = null;
|
|
1672
|
+
const emitOverlayed = () => {
|
|
1673
|
+
if (!lastMerged) return;
|
|
1674
|
+
let bids = lastMerged.bids;
|
|
1675
|
+
let asks = lastMerged.asks;
|
|
1676
|
+
let timestamp = lastMerged.timestamp;
|
|
1677
|
+
let sequence = lastMerged.sequence;
|
|
1678
|
+
if (lastTicker) {
|
|
1679
|
+
bids = overlayBid2(bids, lastTicker.bid);
|
|
1680
|
+
asks = overlayAsk2(asks, lastTicker.ask);
|
|
1681
|
+
if (lastTicker.ts > timestamp) {
|
|
1682
|
+
timestamp = lastTicker.ts;
|
|
1683
|
+
sequence = Math.max(sequence, lastTicker.ts);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1637
1686
|
const book = {
|
|
1638
1687
|
exchange: "coinex",
|
|
1639
1688
|
symbol: fromCoinexSymbol(market),
|
|
1640
1689
|
market: opts.market,
|
|
1690
|
+
bids,
|
|
1691
|
+
asks,
|
|
1692
|
+
timestamp,
|
|
1693
|
+
sequence
|
|
1694
|
+
};
|
|
1695
|
+
stream.emit("update", book);
|
|
1696
|
+
};
|
|
1697
|
+
const handleDepth = (d) => {
|
|
1698
|
+
if (d.market !== market) return;
|
|
1699
|
+
const inner = d.depth;
|
|
1700
|
+
const r = merger.apply({
|
|
1701
|
+
kind: "snapshot",
|
|
1702
|
+
bids: inner.bids.map(([p, s]) => ({
|
|
1703
|
+
price: Number(p),
|
|
1704
|
+
size: Number(s)
|
|
1705
|
+
})),
|
|
1706
|
+
asks: inner.asks.map(([p, s]) => ({
|
|
1707
|
+
price: Number(p),
|
|
1708
|
+
size: Number(s)
|
|
1709
|
+
})),
|
|
1710
|
+
sequence: inner.updated_at,
|
|
1711
|
+
timestamp: inner.updated_at
|
|
1712
|
+
});
|
|
1713
|
+
if (!r.ok) return;
|
|
1714
|
+
lastMerged = {
|
|
1641
1715
|
bids: r.bids,
|
|
1642
1716
|
asks: r.asks,
|
|
1643
1717
|
timestamp: r.timestamp,
|
|
1644
1718
|
sequence: r.sequence
|
|
1645
1719
|
};
|
|
1646
|
-
|
|
1720
|
+
emitOverlayed();
|
|
1647
1721
|
};
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
} : {
|
|
1660
|
-
kind: "delta",
|
|
1661
|
-
bids,
|
|
1662
|
-
asks,
|
|
1663
|
-
sequence: ++seq,
|
|
1664
|
-
timestamp: payload.time
|
|
1722
|
+
const handleBbo = (b) => {
|
|
1723
|
+
if (b.market !== market) return;
|
|
1724
|
+
const bidPrice = Number(b.best_bid_price);
|
|
1725
|
+
const askPrice = Number(b.best_ask_price);
|
|
1726
|
+
const bidSize = Number(b.best_bid_size);
|
|
1727
|
+
const askSize = Number(b.best_ask_size);
|
|
1728
|
+
if (!Number.isFinite(bidPrice) || !Number.isFinite(askPrice)) return;
|
|
1729
|
+
lastTicker = {
|
|
1730
|
+
bid: { price: bidPrice, size: bidSize },
|
|
1731
|
+
ask: { price: askPrice, size: askSize },
|
|
1732
|
+
ts: b.updated_at
|
|
1665
1733
|
};
|
|
1666
|
-
|
|
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
|
-
}
|
|
1734
|
+
emitOverlayed();
|
|
1676
1735
|
};
|
|
1677
1736
|
const handlePayload = (text) => {
|
|
1678
1737
|
let msg;
|
|
@@ -1682,31 +1741,32 @@ function streamCoinexOrderbook(opts) {
|
|
|
1682
1741
|
return;
|
|
1683
1742
|
}
|
|
1684
1743
|
if (msg.id != null && !msg.method) return;
|
|
1685
|
-
if (msg.
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
);
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1692
|
-
if (msg.method === "depth.update" && Array.isArray(msg.params)) {
|
|
1693
|
-
handleDepthPush(msg.params);
|
|
1744
|
+
if (!msg.data || !msg.method) return;
|
|
1745
|
+
if (msg.method === "depth.update") {
|
|
1746
|
+
handleDepth(msg.data);
|
|
1747
|
+
} else if (msg.method === "bbo.update") {
|
|
1748
|
+
handleBbo(msg.data);
|
|
1694
1749
|
}
|
|
1695
1750
|
};
|
|
1696
|
-
const onMessage = (raw) => {
|
|
1697
|
-
if (typeof raw
|
|
1698
|
-
|
|
1751
|
+
const onMessage = async (raw) => {
|
|
1752
|
+
if (typeof raw === "string") {
|
|
1753
|
+
handlePayload(raw);
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const inflate = await getGzipInflate();
|
|
1757
|
+
handlePayload(inflate(raw));
|
|
1699
1758
|
};
|
|
1700
1759
|
ws = new WSClient({
|
|
1701
1760
|
url: opts.market === "spot" ? WS_SPOT5 : WS_FUTURES2,
|
|
1702
1761
|
onOpen: (sock) => {
|
|
1703
1762
|
merger.reset();
|
|
1704
|
-
|
|
1705
|
-
|
|
1763
|
+
lastMerged = null;
|
|
1764
|
+
lastTicker = null;
|
|
1765
|
+
sock.send(subscribeDepth);
|
|
1766
|
+
sock.send(subscribeBbo);
|
|
1706
1767
|
},
|
|
1707
|
-
onMessage
|
|
1708
|
-
|
|
1709
|
-
pingPayload
|
|
1768
|
+
onMessage
|
|
1769
|
+
// Native WS ping/pong frames keep the connection alive — no app-level ping.
|
|
1710
1770
|
});
|
|
1711
1771
|
ws.on("open", () => stream.emit("connected"));
|
|
1712
1772
|
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
@@ -1715,11 +1775,31 @@ function streamCoinexOrderbook(opts) {
|
|
|
1715
1775
|
ws.connect().catch((e) => stream.emit("error", e));
|
|
1716
1776
|
return stream;
|
|
1717
1777
|
}
|
|
1718
|
-
function
|
|
1719
|
-
if (!
|
|
1720
|
-
|
|
1721
|
-
for (
|
|
1722
|
-
|
|
1778
|
+
function overlayAsk2(asks, top) {
|
|
1779
|
+
if (!(top.size > 0)) return asks;
|
|
1780
|
+
let i = 0;
|
|
1781
|
+
for (; i < asks.length; i++) {
|
|
1782
|
+
const level = asks[i];
|
|
1783
|
+
if (!level || level.price >= top.price) break;
|
|
1784
|
+
}
|
|
1785
|
+
const at = asks[i];
|
|
1786
|
+
if (at && at.price === top.price) {
|
|
1787
|
+
return [{ price: top.price, size: top.size }, ...asks.slice(i + 1)];
|
|
1788
|
+
}
|
|
1789
|
+
return [{ price: top.price, size: top.size }, ...asks.slice(i)];
|
|
1790
|
+
}
|
|
1791
|
+
function overlayBid2(bids, top) {
|
|
1792
|
+
if (!(top.size > 0)) return bids;
|
|
1793
|
+
let i = 0;
|
|
1794
|
+
for (; i < bids.length; i++) {
|
|
1795
|
+
const level = bids[i];
|
|
1796
|
+
if (!level || level.price <= top.price) break;
|
|
1797
|
+
}
|
|
1798
|
+
const at = bids[i];
|
|
1799
|
+
if (at && at.price === top.price) {
|
|
1800
|
+
return [{ price: top.price, size: top.size }, ...bids.slice(i + 1)];
|
|
1801
|
+
}
|
|
1802
|
+
return [{ price: top.price, size: top.size }, ...bids.slice(i)];
|
|
1723
1803
|
}
|
|
1724
1804
|
|
|
1725
1805
|
// src/exchanges/coinex/index.ts
|
|
@@ -2417,8 +2497,8 @@ async function fetchKucoinOrderbook(opts) {
|
|
|
2417
2497
|
exchange: "kucoin",
|
|
2418
2498
|
symbol: fromKucoinSpotSymbol(symbol2),
|
|
2419
2499
|
market: "spot",
|
|
2420
|
-
bids:
|
|
2421
|
-
asks:
|
|
2500
|
+
bids: parseLevels(res2.data.bids),
|
|
2501
|
+
asks: parseLevels(res2.data.asks),
|
|
2422
2502
|
timestamp: res2.data.time,
|
|
2423
2503
|
sequence: Number(res2.data.sequence)
|
|
2424
2504
|
};
|
|
@@ -2437,13 +2517,13 @@ async function fetchKucoinOrderbook(opts) {
|
|
|
2437
2517
|
exchange: "kucoin",
|
|
2438
2518
|
symbol: fromKucoinFuturesSymbol(symbol),
|
|
2439
2519
|
market: "perpetual",
|
|
2440
|
-
bids:
|
|
2441
|
-
asks:
|
|
2520
|
+
bids: parseLevels(res.data.bids),
|
|
2521
|
+
asks: parseLevels(res.data.asks),
|
|
2442
2522
|
timestamp: res.data.ts,
|
|
2443
2523
|
sequence: res.data.sequence
|
|
2444
2524
|
};
|
|
2445
2525
|
}
|
|
2446
|
-
function
|
|
2526
|
+
function parseLevels(rows) {
|
|
2447
2527
|
return rows.map(([p, s]) => ({ price: Number(p), size: Number(s) }));
|
|
2448
2528
|
}
|
|
2449
2529
|
|
|
@@ -2569,98 +2649,88 @@ var KucoinClient = class {
|
|
|
2569
2649
|
}
|
|
2570
2650
|
};
|
|
2571
2651
|
|
|
2572
|
-
// src/exchanges/
|
|
2573
|
-
|
|
2574
|
-
function toBitmartSymbol(symbol) {
|
|
2652
|
+
// src/exchanges/deribit/symbols.ts
|
|
2653
|
+
function toDeribitSymbol(symbol, market) {
|
|
2575
2654
|
const [base, quote] = symbol.split("/");
|
|
2576
2655
|
if (!base || !quote) {
|
|
2577
2656
|
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
2578
2657
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
return `${upper.slice(0, -q.length)}/${q}`;
|
|
2586
|
-
}
|
|
2658
|
+
const pair = `${base}_${quote}`.toUpperCase();
|
|
2659
|
+
if (market !== "perpetual") return pair;
|
|
2660
|
+
if (quote.toUpperCase() !== "USDC") {
|
|
2661
|
+
throw new Error(
|
|
2662
|
+
`deribit linear perpetuals are USDC-margined; got "${quote}" (inverse contracts like BTC-PERPETUAL are unsupported)`
|
|
2663
|
+
);
|
|
2587
2664
|
}
|
|
2588
|
-
return
|
|
2665
|
+
return `${pair}-PERPETUAL`;
|
|
2666
|
+
}
|
|
2667
|
+
function fromDeribitSymbol(instrument) {
|
|
2668
|
+
let s = instrument.toUpperCase();
|
|
2669
|
+
if (s.endsWith("-PERPETUAL")) s = s.slice(0, -"-PERPETUAL".length);
|
|
2670
|
+
const [base, quote] = s.split("_");
|
|
2671
|
+
return base && quote ? `${base}/${quote}` : s;
|
|
2589
2672
|
}
|
|
2590
2673
|
|
|
2591
|
-
// src/exchanges/
|
|
2592
|
-
var BASE8 = "https://
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
const
|
|
2601
|
-
const url = `${BASE8}/contract/public/depth?symbol=${symbol}`;
|
|
2674
|
+
// src/exchanges/deribit/rest.ts
|
|
2675
|
+
var BASE8 = "https://www.deribit.com";
|
|
2676
|
+
var ALLOWED_DEPTHS = [1, 5, 10, 20, 50, 100, 1e3, 1e4];
|
|
2677
|
+
function clampDepth(depth) {
|
|
2678
|
+
return ALLOWED_DEPTHS.find((d) => d >= depth) ?? ALLOWED_DEPTHS[ALLOWED_DEPTHS.length - 1];
|
|
2679
|
+
}
|
|
2680
|
+
async function fetchDeribitOrderbook(opts) {
|
|
2681
|
+
const instrument = toDeribitSymbol(opts.symbol, opts.market);
|
|
2682
|
+
const depth = clampDepth(opts.depth ?? 50);
|
|
2683
|
+
const url = `${BASE8}/api/v2/public/get_order_book?instrument_name=${instrument}&depth=${depth}`;
|
|
2602
2684
|
const res = await httpJson({
|
|
2603
2685
|
url,
|
|
2604
2686
|
timeoutMs: opts.timeoutMs,
|
|
2605
2687
|
transformUrl: opts.transformUrl
|
|
2606
2688
|
});
|
|
2607
|
-
if (res.
|
|
2608
|
-
throw new ExchangeError(
|
|
2689
|
+
if (res.error) {
|
|
2690
|
+
throw new ExchangeError(
|
|
2691
|
+
"deribit",
|
|
2692
|
+
`${res.error.code}: ${res.error.message}`
|
|
2693
|
+
);
|
|
2609
2694
|
}
|
|
2695
|
+
const r = res.result;
|
|
2696
|
+
if (!r) throw new ExchangeError("deribit", "empty orderbook response");
|
|
2610
2697
|
return {
|
|
2611
|
-
exchange: "
|
|
2612
|
-
symbol:
|
|
2613
|
-
market:
|
|
2614
|
-
bids:
|
|
2615
|
-
asks:
|
|
2616
|
-
timestamp:
|
|
2617
|
-
sequence:
|
|
2698
|
+
exchange: "deribit",
|
|
2699
|
+
symbol: fromDeribitSymbol(r.instrument_name ?? instrument),
|
|
2700
|
+
market: opts.market,
|
|
2701
|
+
bids: r.bids.map(([price, size]) => ({ price, size })),
|
|
2702
|
+
asks: r.asks.map(([price, size]) => ({ price, size })),
|
|
2703
|
+
timestamp: r.timestamp,
|
|
2704
|
+
sequence: r.change_id
|
|
2618
2705
|
};
|
|
2619
2706
|
}
|
|
2620
|
-
function parseLevels3(rows) {
|
|
2621
|
-
const out = [];
|
|
2622
|
-
for (const row of rows) {
|
|
2623
|
-
out.push({ price: Number(row[0]), size: Number(row[1]) });
|
|
2624
|
-
}
|
|
2625
|
-
return out;
|
|
2626
|
-
}
|
|
2627
2707
|
|
|
2628
|
-
// src/exchanges/
|
|
2629
|
-
var
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
"spot is not supported \u2014 only USDT-M perpetual"
|
|
2635
|
-
);
|
|
2636
|
-
}
|
|
2637
|
-
const symbol = toBitmartSymbol(opts.symbol);
|
|
2638
|
-
const level = snapLevel(opts.depth);
|
|
2639
|
-
const interval = opts.interval ?? "200ms";
|
|
2640
|
-
const channelArg = `futures/depthAll${level}:${symbol}@${interval}`;
|
|
2708
|
+
// src/exchanges/deribit/ws.ts
|
|
2709
|
+
var WS_PUBLIC3 = "wss://www.deribit.com/ws/api/v2";
|
|
2710
|
+
var HEARTBEAT_SECONDS = 30;
|
|
2711
|
+
function streamDeribitOrderbook(opts) {
|
|
2712
|
+
const instrument = toDeribitSymbol(opts.symbol, opts.market);
|
|
2713
|
+
const channel = `book.${instrument}.${opts.interval ?? "100ms"}`;
|
|
2641
2714
|
const merger = new BookMerger();
|
|
2642
2715
|
let ws = null;
|
|
2716
|
+
let sock = null;
|
|
2717
|
+
let recovering = false;
|
|
2643
2718
|
const stream = new OrderbookStream(() => ws?.close());
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
sequence: frame.ms_t,
|
|
2656
|
-
timestamp: frame.ms_t
|
|
2657
|
-
};
|
|
2658
|
-
const r = merger.apply(event);
|
|
2659
|
-
if (!r.ok) return;
|
|
2719
|
+
let nextId = 1;
|
|
2720
|
+
const subscribeIds = /* @__PURE__ */ new Set();
|
|
2721
|
+
const rpc = (method, params) => {
|
|
2722
|
+
const id = nextId++;
|
|
2723
|
+
sock?.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
2724
|
+
return id;
|
|
2725
|
+
};
|
|
2726
|
+
const subscribe = () => {
|
|
2727
|
+
subscribeIds.add(rpc("public/subscribe", { channels: [channel] }));
|
|
2728
|
+
};
|
|
2729
|
+
const emit = (r) => {
|
|
2660
2730
|
const book = {
|
|
2661
|
-
exchange: "
|
|
2662
|
-
symbol:
|
|
2663
|
-
market:
|
|
2731
|
+
exchange: "deribit",
|
|
2732
|
+
symbol: fromDeribitSymbol(instrument),
|
|
2733
|
+
market: opts.market,
|
|
2664
2734
|
bids: r.bids,
|
|
2665
2735
|
asks: r.asks,
|
|
2666
2736
|
timestamp: r.timestamp,
|
|
@@ -2668,41 +2738,101 @@ function streamBitmartOrderbook(opts) {
|
|
|
2668
2738
|
};
|
|
2669
2739
|
stream.emit("update", book);
|
|
2670
2740
|
};
|
|
2671
|
-
const
|
|
2741
|
+
const resync = () => {
|
|
2742
|
+
if (recovering) return;
|
|
2743
|
+
recovering = true;
|
|
2744
|
+
merger.reset();
|
|
2745
|
+
rpc("public/unsubscribe", { channels: [channel] });
|
|
2746
|
+
subscribe();
|
|
2747
|
+
};
|
|
2748
|
+
const toLevels = (entries) => entries.map(([action, price, amount]) => ({
|
|
2749
|
+
price,
|
|
2750
|
+
size: action === "delete" ? 0 : amount
|
|
2751
|
+
}));
|
|
2752
|
+
const buildEvent2 = (d) => {
|
|
2753
|
+
const bids = toLevels(d.bids);
|
|
2754
|
+
const asks = toLevels(d.asks);
|
|
2755
|
+
if (d.type === "snapshot") {
|
|
2756
|
+
return {
|
|
2757
|
+
kind: "snapshot",
|
|
2758
|
+
bids,
|
|
2759
|
+
asks,
|
|
2760
|
+
sequence: d.change_id,
|
|
2761
|
+
timestamp: d.timestamp
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
return {
|
|
2765
|
+
kind: "delta",
|
|
2766
|
+
bids,
|
|
2767
|
+
asks,
|
|
2768
|
+
sequence: d.change_id,
|
|
2769
|
+
prevSequence: d.prev_change_id,
|
|
2770
|
+
timestamp: d.timestamp
|
|
2771
|
+
};
|
|
2772
|
+
};
|
|
2773
|
+
const handleData = (d) => {
|
|
2774
|
+
if (d.type === "snapshot") recovering = false;
|
|
2775
|
+
else if (recovering) return;
|
|
2776
|
+
const r = merger.apply(buildEvent2(d));
|
|
2777
|
+
if (r.ok) {
|
|
2778
|
+
emit(r);
|
|
2779
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
2780
|
+
resync();
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
const onMessage = (raw) => {
|
|
2784
|
+
if (typeof raw !== "string") return;
|
|
2672
2785
|
let msg;
|
|
2673
2786
|
try {
|
|
2674
|
-
msg = JSON.parse(
|
|
2787
|
+
msg = JSON.parse(raw);
|
|
2675
2788
|
} catch {
|
|
2676
2789
|
return;
|
|
2677
2790
|
}
|
|
2678
|
-
if (msg.
|
|
2679
|
-
|
|
2680
|
-
stream.emit(
|
|
2681
|
-
"error",
|
|
2682
|
-
new Error(`bitmart: ${msg.errorCode} ${msg.errorMessage ?? ""}`)
|
|
2683
|
-
);
|
|
2791
|
+
if (msg.method === "heartbeat") {
|
|
2792
|
+
if (msg.params?.type === "test_request") rpc("public/test", {});
|
|
2684
2793
|
return;
|
|
2685
2794
|
}
|
|
2686
|
-
if (msg.
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
const onMessage = async (raw) => {
|
|
2690
|
-
if (typeof raw === "string") {
|
|
2691
|
-
handlePayload(raw);
|
|
2795
|
+
if (msg.method === "subscription") {
|
|
2796
|
+
if (msg.params?.channel !== channel || !msg.params.data) return;
|
|
2797
|
+
handleData(msg.params.data);
|
|
2692
2798
|
return;
|
|
2693
2799
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2800
|
+
if (msg.id !== void 0) {
|
|
2801
|
+
if (msg.error) {
|
|
2802
|
+
stream.emit(
|
|
2803
|
+
"error",
|
|
2804
|
+
new ExchangeError(
|
|
2805
|
+
"deribit",
|
|
2806
|
+
`${msg.error.code}: ${msg.error.message}`
|
|
2807
|
+
)
|
|
2808
|
+
);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
if (subscribeIds.delete(msg.id)) {
|
|
2812
|
+
const channels = Array.isArray(msg.result) ? msg.result : [];
|
|
2813
|
+
if (!channels.includes(channel)) {
|
|
2814
|
+
stream.emit(
|
|
2815
|
+
"error",
|
|
2816
|
+
new ExchangeError(
|
|
2817
|
+
"deribit",
|
|
2818
|
+
`subscribe rejected for "${channel}" \u2014 unknown instrument or interval`
|
|
2819
|
+
)
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2696
2824
|
};
|
|
2697
2825
|
ws = new WSClient({
|
|
2698
|
-
url:
|
|
2699
|
-
onOpen: (
|
|
2826
|
+
url: WS_PUBLIC3,
|
|
2827
|
+
onOpen: (handle) => {
|
|
2828
|
+
sock = handle;
|
|
2700
2829
|
merger.reset();
|
|
2701
|
-
|
|
2830
|
+
recovering = false;
|
|
2831
|
+
subscribeIds.clear();
|
|
2832
|
+
rpc("public/set_heartbeat", { interval: HEARTBEAT_SECONDS });
|
|
2833
|
+
subscribe();
|
|
2702
2834
|
},
|
|
2703
|
-
onMessage
|
|
2704
|
-
pingIntervalMs: 15e3,
|
|
2705
|
-
pingPayload
|
|
2835
|
+
onMessage
|
|
2706
2836
|
});
|
|
2707
2837
|
ws.on("open", () => stream.emit("connected"));
|
|
2708
2838
|
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
@@ -2711,21 +2841,10 @@ function streamBitmartOrderbook(opts) {
|
|
|
2711
2841
|
ws.connect().catch((e) => stream.emit("error", e));
|
|
2712
2842
|
return stream;
|
|
2713
2843
|
}
|
|
2714
|
-
function snapLevel(depth) {
|
|
2715
|
-
if (depth === 5) return 5;
|
|
2716
|
-
if (depth === 20) return 20;
|
|
2717
|
-
return 50;
|
|
2718
|
-
}
|
|
2719
|
-
function parseLevels4(rows) {
|
|
2720
|
-
if (!rows) return [];
|
|
2721
|
-
const out = [];
|
|
2722
|
-
for (const r of rows) out.push({ price: Number(r.price), size: Number(r.vol) });
|
|
2723
|
-
return out;
|
|
2724
|
-
}
|
|
2725
2844
|
|
|
2726
|
-
// src/exchanges/
|
|
2727
|
-
var
|
|
2728
|
-
exchange = "
|
|
2845
|
+
// src/exchanges/deribit/index.ts
|
|
2846
|
+
var DeribitClient = class {
|
|
2847
|
+
exchange = "deribit";
|
|
2729
2848
|
transformUrl;
|
|
2730
2849
|
timeoutMs;
|
|
2731
2850
|
constructor(opts = {}) {
|
|
@@ -2734,7 +2853,7 @@ var BitmartClient = class {
|
|
|
2734
2853
|
}
|
|
2735
2854
|
fetchOrderbook(symbol, opts = {}) {
|
|
2736
2855
|
const { pair, market } = parseSymbol(symbol);
|
|
2737
|
-
return
|
|
2856
|
+
return fetchDeribitOrderbook({
|
|
2738
2857
|
symbol: pair,
|
|
2739
2858
|
market,
|
|
2740
2859
|
depth: opts.depth,
|
|
@@ -2742,11 +2861,12 @@ var BitmartClient = class {
|
|
|
2742
2861
|
transformUrl: this.transformUrl
|
|
2743
2862
|
});
|
|
2744
2863
|
}
|
|
2745
|
-
streamOrderbook(symbol,
|
|
2864
|
+
streamOrderbook(symbol, opts = {}) {
|
|
2746
2865
|
const { pair, market } = parseSymbol(symbol);
|
|
2747
|
-
return
|
|
2866
|
+
return streamDeribitOrderbook({
|
|
2748
2867
|
symbol: pair,
|
|
2749
2868
|
market,
|
|
2869
|
+
depth: opts.depth,
|
|
2750
2870
|
transformUrl: this.transformUrl,
|
|
2751
2871
|
timeoutMs: this.timeoutMs
|
|
2752
2872
|
});
|
|
@@ -2757,10 +2877,10 @@ exports.Backoff = Backoff;
|
|
|
2757
2877
|
exports.BinanceClient = BinanceClient;
|
|
2758
2878
|
exports.BingxClient = BingxClient;
|
|
2759
2879
|
exports.BitgetClient = BitgetClient;
|
|
2760
|
-
exports.BitmartClient = BitmartClient;
|
|
2761
2880
|
exports.BookMerger = BookMerger;
|
|
2762
2881
|
exports.BybitClient = BybitClient;
|
|
2763
2882
|
exports.CoinexClient = CoinexClient;
|
|
2883
|
+
exports.DeribitClient = DeribitClient;
|
|
2764
2884
|
exports.ExchangeError = ExchangeError;
|
|
2765
2885
|
exports.GateClient = GateClient;
|
|
2766
2886
|
exports.HuobiClient = HuobiClient;
|