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