@xapy/orderbook 0.1.22 → 0.1.24
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 +15 -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 +21 -6
- package/dist/exchanges/coinex/index.cjs.map +1 -1
- package/dist/exchanges/coinex/index.d.cts +1 -1
- package/dist/exchanges/coinex/index.d.ts +1 -1
- package/dist/exchanges/coinex/index.js +21 -6
- package/dist/exchanges/coinex/index.js.map +1 -1
- package/dist/exchanges/deribit/index.cjs +625 -0
- 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/deribit/index.js +621 -0
- 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/{bitmart → grvt}/index.cjs +175 -147
- package/dist/exchanges/grvt/index.cjs.map +1 -0
- package/dist/exchanges/grvt/index.d.cts +47 -0
- package/dist/exchanges/grvt/index.d.ts +47 -0
- package/dist/exchanges/{bitmart → grvt}/index.js +173 -145
- package/dist/exchanges/grvt/index.js.map +1 -0
- 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 +409 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +408 -115
- package/dist/index.js.map +1 -1
- package/dist/{stream-L9MJjYbE.d.cts → stream-Con47OAp.d.cts} +1 -1
- package/dist/{stream-L9MJjYbE.d.ts → stream-Con47OAp.d.ts} +1 -1
- package/package.json +18 -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
|
}
|
|
@@ -2620,98 +2647,323 @@ var KucoinClient = class {
|
|
|
2620
2647
|
}
|
|
2621
2648
|
};
|
|
2622
2649
|
|
|
2623
|
-
// src/exchanges/
|
|
2624
|
-
|
|
2625
|
-
function toBitmartSymbol(symbol) {
|
|
2650
|
+
// src/exchanges/deribit/symbols.ts
|
|
2651
|
+
function toDeribitSymbol(symbol, market) {
|
|
2626
2652
|
const [base, quote] = symbol.split("/");
|
|
2627
2653
|
if (!base || !quote) {
|
|
2628
2654
|
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
2629
2655
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
return `${upper.slice(0, -q.length)}/${q}`;
|
|
2637
|
-
}
|
|
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
|
+
);
|
|
2638
2662
|
}
|
|
2639
|
-
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;
|
|
2640
2670
|
}
|
|
2641
2671
|
|
|
2642
|
-
// src/exchanges/
|
|
2643
|
-
var BASE8 = "https://
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
const
|
|
2652
|
-
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}`;
|
|
2653
2682
|
const res = await httpJson({
|
|
2654
2683
|
url,
|
|
2655
2684
|
timeoutMs: opts.timeoutMs,
|
|
2656
2685
|
transformUrl: opts.transformUrl
|
|
2657
2686
|
});
|
|
2658
|
-
if (res.
|
|
2659
|
-
throw new ExchangeError(
|
|
2687
|
+
if (res.error) {
|
|
2688
|
+
throw new ExchangeError(
|
|
2689
|
+
"deribit",
|
|
2690
|
+
`${res.error.code}: ${res.error.message}`
|
|
2691
|
+
);
|
|
2660
2692
|
}
|
|
2693
|
+
const r = res.result;
|
|
2694
|
+
if (!r) throw new ExchangeError("deribit", "empty orderbook response");
|
|
2661
2695
|
return {
|
|
2662
|
-
exchange: "
|
|
2663
|
-
symbol:
|
|
2664
|
-
market:
|
|
2665
|
-
bids:
|
|
2666
|
-
asks:
|
|
2667
|
-
timestamp:
|
|
2668
|
-
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
|
|
2669
2703
|
};
|
|
2670
2704
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2705
|
+
|
|
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"}`;
|
|
2712
|
+
const merger = new BookMerger();
|
|
2713
|
+
let ws = null;
|
|
2714
|
+
let sock = null;
|
|
2715
|
+
let recovering = false;
|
|
2716
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
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) => {
|
|
2728
|
+
const book = {
|
|
2729
|
+
exchange: "deribit",
|
|
2730
|
+
symbol: fromDeribitSymbol(instrument),
|
|
2731
|
+
market: opts.market,
|
|
2732
|
+
bids: r.bids,
|
|
2733
|
+
asks: r.asks,
|
|
2734
|
+
timestamp: r.timestamp,
|
|
2735
|
+
sequence: r.sequence
|
|
2736
|
+
};
|
|
2737
|
+
stream.emit("update", book);
|
|
2738
|
+
};
|
|
2739
|
+
const resync = () => {
|
|
2740
|
+
if (recovering) return;
|
|
2741
|
+
recovering = true;
|
|
2742
|
+
merger.reset();
|
|
2743
|
+
rpc("public/unsubscribe", { channels: [channel] });
|
|
2744
|
+
subscribe();
|
|
2745
|
+
};
|
|
2746
|
+
const toLevels2 = (entries) => entries.map(([action, price, amount]) => ({
|
|
2747
|
+
price,
|
|
2748
|
+
size: action === "delete" ? 0 : amount
|
|
2749
|
+
}));
|
|
2750
|
+
const buildEvent2 = (d) => {
|
|
2751
|
+
const bids = toLevels2(d.bids);
|
|
2752
|
+
const asks = toLevels2(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;
|
|
2783
|
+
let msg;
|
|
2784
|
+
try {
|
|
2785
|
+
msg = JSON.parse(raw);
|
|
2786
|
+
} catch {
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
if (msg.method === "heartbeat") {
|
|
2790
|
+
if (msg.params?.type === "test_request") rpc("public/test", {});
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
if (msg.method === "subscription") {
|
|
2794
|
+
if (msg.params?.channel !== channel || !msg.params.data) return;
|
|
2795
|
+
handleData(msg.params.data);
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
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
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
ws = new WSClient({
|
|
2824
|
+
url: WS_PUBLIC3,
|
|
2825
|
+
onOpen: (handle) => {
|
|
2826
|
+
sock = handle;
|
|
2827
|
+
merger.reset();
|
|
2828
|
+
recovering = false;
|
|
2829
|
+
subscribeIds.clear();
|
|
2830
|
+
rpc("public/set_heartbeat", { interval: HEARTBEAT_SECONDS });
|
|
2831
|
+
subscribe();
|
|
2832
|
+
},
|
|
2833
|
+
onMessage
|
|
2834
|
+
});
|
|
2835
|
+
ws.on("open", () => stream.emit("connected"));
|
|
2836
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
2837
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
2838
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
2839
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
2840
|
+
return stream;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
// src/exchanges/deribit/index.ts
|
|
2844
|
+
var DeribitClient = class {
|
|
2845
|
+
exchange = "deribit";
|
|
2846
|
+
transformUrl;
|
|
2847
|
+
timeoutMs;
|
|
2848
|
+
constructor(opts = {}) {
|
|
2849
|
+
this.transformUrl = opts.transformUrl;
|
|
2850
|
+
this.timeoutMs = opts.timeoutMs;
|
|
2675
2851
|
}
|
|
2676
|
-
|
|
2852
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
2853
|
+
const { pair, market } = parseSymbol(symbol);
|
|
2854
|
+
return fetchDeribitOrderbook({
|
|
2855
|
+
symbol: pair,
|
|
2856
|
+
market,
|
|
2857
|
+
depth: opts.depth,
|
|
2858
|
+
timeoutMs: this.timeoutMs,
|
|
2859
|
+
transformUrl: this.transformUrl
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
streamOrderbook(symbol, opts = {}) {
|
|
2863
|
+
const { pair, market } = parseSymbol(symbol);
|
|
2864
|
+
return streamDeribitOrderbook({
|
|
2865
|
+
symbol: pair,
|
|
2866
|
+
market,
|
|
2867
|
+
depth: opts.depth,
|
|
2868
|
+
transformUrl: this.transformUrl,
|
|
2869
|
+
timeoutMs: this.timeoutMs
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
|
|
2874
|
+
// src/exchanges/grvt/symbols.ts
|
|
2875
|
+
function toGrvtSymbol(symbol, market) {
|
|
2876
|
+
const [base, quote] = symbol.split("/");
|
|
2877
|
+
if (!base || !quote) {
|
|
2878
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
2879
|
+
}
|
|
2880
|
+
if (market !== "perpetual") {
|
|
2881
|
+
throw new Error(
|
|
2882
|
+
`grvt only lists perpetuals; use "${base.toUpperCase()}/${quote.toUpperCase()}:${quote.toUpperCase()}"`
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
return `${base.toUpperCase()}_${quote.toUpperCase()}_Perp`;
|
|
2886
|
+
}
|
|
2887
|
+
function fromGrvtSymbol(instrument) {
|
|
2888
|
+
const parts = instrument.split("_");
|
|
2889
|
+
const [base, quote] = parts;
|
|
2890
|
+
return base && quote ? `${base.toUpperCase()}/${quote.toUpperCase()}` : instrument;
|
|
2677
2891
|
}
|
|
2678
2892
|
|
|
2679
|
-
// src/exchanges/
|
|
2680
|
-
var
|
|
2681
|
-
|
|
2682
|
-
|
|
2893
|
+
// src/exchanges/grvt/rest.ts
|
|
2894
|
+
var BASE9 = "https://market-data.grvt.io";
|
|
2895
|
+
var ALLOWED_DEPTHS2 = [10, 50, 100, 500];
|
|
2896
|
+
function snapDepth(depth) {
|
|
2897
|
+
return ALLOWED_DEPTHS2.find((d) => d >= depth) ?? ALLOWED_DEPTHS2[ALLOWED_DEPTHS2.length - 1];
|
|
2898
|
+
}
|
|
2899
|
+
function nsToMs(ns) {
|
|
2900
|
+
const n = Number(ns);
|
|
2901
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n / 1e6) : Date.now();
|
|
2902
|
+
}
|
|
2903
|
+
var toLevels = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
|
|
2904
|
+
async function fetchGrvtOrderbook(opts) {
|
|
2905
|
+
const instrument = toGrvtSymbol(opts.symbol, opts.market);
|
|
2906
|
+
const depth = snapDepth(opts.depth ?? 50);
|
|
2907
|
+
const res = await httpJson({
|
|
2908
|
+
url: `${BASE9}/full/v1/book`,
|
|
2909
|
+
method: "POST",
|
|
2910
|
+
headers: { "Content-Type": "application/json" },
|
|
2911
|
+
body: JSON.stringify({ instrument, depth }),
|
|
2912
|
+
timeoutMs: opts.timeoutMs,
|
|
2913
|
+
transformUrl: opts.transformUrl
|
|
2914
|
+
});
|
|
2915
|
+
if (!res.result) {
|
|
2683
2916
|
throw new ExchangeError(
|
|
2684
|
-
"
|
|
2685
|
-
|
|
2917
|
+
"grvt",
|
|
2918
|
+
res.code ? `${res.code}: ${res.message}` : "empty orderbook response"
|
|
2686
2919
|
);
|
|
2687
2920
|
}
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2921
|
+
const ts = nsToMs(res.result.event_time);
|
|
2922
|
+
return {
|
|
2923
|
+
exchange: "grvt",
|
|
2924
|
+
symbol: fromGrvtSymbol(res.result.instrument ?? instrument),
|
|
2925
|
+
market: opts.market,
|
|
2926
|
+
bids: toLevels(res.result.bids),
|
|
2927
|
+
asks: toLevels(res.result.asks),
|
|
2928
|
+
timestamp: ts,
|
|
2929
|
+
sequence: ts
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// src/exchanges/grvt/ws.ts
|
|
2934
|
+
var WS_FULL = "wss://market-data.grvt.io/ws/full";
|
|
2935
|
+
var STREAM = "v1.book.d";
|
|
2936
|
+
function streamGrvtOrderbook(opts) {
|
|
2937
|
+
const instrument = toGrvtSymbol(opts.symbol, opts.market);
|
|
2938
|
+
const selector = `${instrument}@${opts.rate ?? 500}`;
|
|
2692
2939
|
const merger = new BookMerger();
|
|
2693
2940
|
let ws = null;
|
|
2941
|
+
let sock = null;
|
|
2942
|
+
let recovering = false;
|
|
2943
|
+
let awaitingFirstDelta = false;
|
|
2944
|
+
let snapshotSequence = 0;
|
|
2945
|
+
let ackSequence = null;
|
|
2694
2946
|
const stream = new OrderbookStream(() => ws?.close());
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2947
|
+
let nextId = 1;
|
|
2948
|
+
const subscribeIds = /* @__PURE__ */ new Set();
|
|
2949
|
+
const rpc = (method) => {
|
|
2950
|
+
const id = nextId++;
|
|
2951
|
+
sock?.send(
|
|
2952
|
+
JSON.stringify({
|
|
2953
|
+
jsonrpc: "2.0",
|
|
2954
|
+
method,
|
|
2955
|
+
params: { stream: STREAM, selectors: [selector] },
|
|
2956
|
+
id
|
|
2957
|
+
})
|
|
2958
|
+
);
|
|
2959
|
+
return id;
|
|
2960
|
+
};
|
|
2961
|
+
const subscribe = () => subscribeIds.add(rpc("subscribe"));
|
|
2962
|
+
const emit = (r) => {
|
|
2711
2963
|
const book = {
|
|
2712
|
-
exchange: "
|
|
2713
|
-
symbol:
|
|
2714
|
-
market:
|
|
2964
|
+
exchange: "grvt",
|
|
2965
|
+
symbol: fromGrvtSymbol(instrument),
|
|
2966
|
+
market: opts.market,
|
|
2715
2967
|
bids: r.bids,
|
|
2716
2968
|
asks: r.asks,
|
|
2717
2969
|
timestamp: r.timestamp,
|
|
@@ -2719,41 +2971,92 @@ function streamBitmartOrderbook(opts) {
|
|
|
2719
2971
|
};
|
|
2720
2972
|
stream.emit("update", book);
|
|
2721
2973
|
};
|
|
2722
|
-
const
|
|
2974
|
+
const resync = () => {
|
|
2975
|
+
if (recovering) return;
|
|
2976
|
+
recovering = true;
|
|
2977
|
+
merger.reset();
|
|
2978
|
+
rpc("unsubscribe");
|
|
2979
|
+
subscribe();
|
|
2980
|
+
};
|
|
2981
|
+
const buildEvent2 = (feed, seq) => {
|
|
2982
|
+
const bids = toLevels(feed.bids);
|
|
2983
|
+
const asks = toLevels(feed.asks);
|
|
2984
|
+
const timestamp = nsToMs(feed.event_time);
|
|
2985
|
+
if (seq === 0) {
|
|
2986
|
+
snapshotSequence = ackSequence ?? 0;
|
|
2987
|
+
awaitingFirstDelta = true;
|
|
2988
|
+
return {
|
|
2989
|
+
kind: "snapshot",
|
|
2990
|
+
bids,
|
|
2991
|
+
asks,
|
|
2992
|
+
sequence: snapshotSequence,
|
|
2993
|
+
timestamp
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
const prevSequence = awaitingFirstDelta ? snapshotSequence : seq - 1;
|
|
2997
|
+
awaitingFirstDelta = false;
|
|
2998
|
+
return { kind: "delta", bids, asks, sequence: seq, prevSequence, timestamp };
|
|
2999
|
+
};
|
|
3000
|
+
const handleFeed = (msg) => {
|
|
3001
|
+
if (!msg.feed) return;
|
|
3002
|
+
const seq = Number(msg.sequence_number ?? 0);
|
|
3003
|
+
if (!Number.isFinite(seq)) return;
|
|
3004
|
+
if (seq === 0) recovering = false;
|
|
3005
|
+
else if (recovering) return;
|
|
3006
|
+
const r = merger.apply(buildEvent2(msg.feed, seq));
|
|
3007
|
+
if (r.ok) {
|
|
3008
|
+
emit(r);
|
|
3009
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3010
|
+
resync();
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
3013
|
+
const onMessage = (raw) => {
|
|
3014
|
+
if (typeof raw !== "string") return;
|
|
2723
3015
|
let msg;
|
|
2724
3016
|
try {
|
|
2725
|
-
msg = JSON.parse(
|
|
3017
|
+
msg = JSON.parse(raw);
|
|
2726
3018
|
} catch {
|
|
2727
3019
|
return;
|
|
2728
3020
|
}
|
|
2729
|
-
if (msg.
|
|
2730
|
-
|
|
3021
|
+
if (msg.feed) {
|
|
3022
|
+
if (msg.selector && msg.selector !== selector) return;
|
|
3023
|
+
handleFeed(msg);
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
if (msg.id === void 0) return;
|
|
3027
|
+
if (msg.error) {
|
|
2731
3028
|
stream.emit(
|
|
2732
3029
|
"error",
|
|
2733
|
-
new
|
|
3030
|
+
new ExchangeError("grvt", `${msg.error.code}: ${msg.error.message}`)
|
|
2734
3031
|
);
|
|
2735
3032
|
return;
|
|
2736
3033
|
}
|
|
2737
|
-
if (
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3034
|
+
if (subscribeIds.delete(msg.id)) {
|
|
3035
|
+
const subs = msg.result?.subs ?? [];
|
|
3036
|
+
const latest = Number(msg.result?.latest_sequence_number?.[0]);
|
|
3037
|
+
ackSequence = Number.isFinite(latest) ? latest : null;
|
|
3038
|
+
if (!subs.includes(selector)) {
|
|
3039
|
+
stream.emit(
|
|
3040
|
+
"error",
|
|
3041
|
+
new ExchangeError(
|
|
3042
|
+
"grvt",
|
|
3043
|
+
`subscribe rejected for "${STREAM}" selector "${selector}"`
|
|
3044
|
+
)
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
2744
3047
|
}
|
|
2745
|
-
const inflate = await getGzipInflate();
|
|
2746
|
-
handlePayload(inflate(raw));
|
|
2747
3048
|
};
|
|
2748
3049
|
ws = new WSClient({
|
|
2749
|
-
url:
|
|
2750
|
-
onOpen: (
|
|
3050
|
+
url: WS_FULL,
|
|
3051
|
+
onOpen: (handle) => {
|
|
3052
|
+
sock = handle;
|
|
2751
3053
|
merger.reset();
|
|
2752
|
-
|
|
3054
|
+
recovering = false;
|
|
3055
|
+
awaitingFirstDelta = false;
|
|
3056
|
+
subscribeIds.clear();
|
|
3057
|
+
subscribe();
|
|
2753
3058
|
},
|
|
2754
|
-
onMessage
|
|
2755
|
-
pingIntervalMs: 15e3,
|
|
2756
|
-
pingPayload
|
|
3059
|
+
onMessage
|
|
2757
3060
|
});
|
|
2758
3061
|
ws.on("open", () => stream.emit("connected"));
|
|
2759
3062
|
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
@@ -2762,21 +3065,10 @@ function streamBitmartOrderbook(opts) {
|
|
|
2762
3065
|
ws.connect().catch((e) => stream.emit("error", e));
|
|
2763
3066
|
return stream;
|
|
2764
3067
|
}
|
|
2765
|
-
function snapLevel(depth) {
|
|
2766
|
-
if (depth === 5) return 5;
|
|
2767
|
-
if (depth === 20) return 20;
|
|
2768
|
-
return 50;
|
|
2769
|
-
}
|
|
2770
|
-
function parseLevels3(rows) {
|
|
2771
|
-
if (!rows) return [];
|
|
2772
|
-
const out = [];
|
|
2773
|
-
for (const r of rows) out.push({ price: Number(r.price), size: Number(r.vol) });
|
|
2774
|
-
return out;
|
|
2775
|
-
}
|
|
2776
3068
|
|
|
2777
|
-
// src/exchanges/
|
|
2778
|
-
var
|
|
2779
|
-
exchange = "
|
|
3069
|
+
// src/exchanges/grvt/index.ts
|
|
3070
|
+
var GrvtClient = class {
|
|
3071
|
+
exchange = "grvt";
|
|
2780
3072
|
transformUrl;
|
|
2781
3073
|
timeoutMs;
|
|
2782
3074
|
constructor(opts = {}) {
|
|
@@ -2785,7 +3077,7 @@ var BitmartClient = class {
|
|
|
2785
3077
|
}
|
|
2786
3078
|
fetchOrderbook(symbol, opts = {}) {
|
|
2787
3079
|
const { pair, market } = parseSymbol(symbol);
|
|
2788
|
-
return
|
|
3080
|
+
return fetchGrvtOrderbook({
|
|
2789
3081
|
symbol: pair,
|
|
2790
3082
|
market,
|
|
2791
3083
|
depth: opts.depth,
|
|
@@ -2793,17 +3085,18 @@ var BitmartClient = class {
|
|
|
2793
3085
|
transformUrl: this.transformUrl
|
|
2794
3086
|
});
|
|
2795
3087
|
}
|
|
2796
|
-
streamOrderbook(symbol,
|
|
3088
|
+
streamOrderbook(symbol, opts = {}) {
|
|
2797
3089
|
const { pair, market } = parseSymbol(symbol);
|
|
2798
|
-
return
|
|
3090
|
+
return streamGrvtOrderbook({
|
|
2799
3091
|
symbol: pair,
|
|
2800
3092
|
market,
|
|
3093
|
+
depth: opts.depth,
|
|
2801
3094
|
transformUrl: this.transformUrl,
|
|
2802
3095
|
timeoutMs: this.timeoutMs
|
|
2803
3096
|
});
|
|
2804
3097
|
}
|
|
2805
3098
|
};
|
|
2806
3099
|
|
|
2807
|
-
export { Backoff, BinanceClient, BingxClient, BitgetClient,
|
|
3100
|
+
export { Backoff, BinanceClient, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, DeribitClient, ExchangeError, GateClient, GrvtClient, HuobiClient, HyperliquidClient, KucoinClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, parseSymbol };
|
|
2808
3101
|
//# sourceMappingURL=index.js.map
|
|
2809
3102
|
//# sourceMappingURL=index.js.map
|