@pear-protocol/market-sdk 0.0.1-preview.0

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.
Files changed (82) hide show
  1. package/README.md +319 -0
  2. package/dist/chart/cache/index.d.ts +21 -0
  3. package/dist/chart/cache/index.js +101 -0
  4. package/dist/chart/chart.d.ts +25 -0
  5. package/dist/chart/chart.js +141 -0
  6. package/dist/chart/collector/binance.d.ts +10 -0
  7. package/dist/chart/collector/binance.js +27 -0
  8. package/dist/chart/collector/bybit.d.ts +10 -0
  9. package/dist/chart/collector/bybit.js +39 -0
  10. package/dist/chart/collector/helpers.d.ts +10 -0
  11. package/dist/chart/collector/helpers.js +21 -0
  12. package/dist/chart/collector/hyperliquid.d.ts +10 -0
  13. package/dist/chart/collector/hyperliquid.js +15 -0
  14. package/dist/chart/collector/index.d.ts +41 -0
  15. package/dist/chart/collector/index.js +202 -0
  16. package/dist/chart/collector/okx.d.ts +10 -0
  17. package/dist/chart/collector/okx.js +38 -0
  18. package/dist/chart/compute/asset.d.ts +11 -0
  19. package/dist/chart/compute/asset.js +24 -0
  20. package/dist/chart/compute/index.d.ts +10 -0
  21. package/dist/chart/compute/index.js +4 -0
  22. package/dist/chart/compute/performance.d.ts +11 -0
  23. package/dist/chart/compute/performance.js +81 -0
  24. package/dist/chart/compute/price-ratio.d.ts +11 -0
  25. package/dist/chart/compute/price-ratio.js +107 -0
  26. package/dist/chart/compute/weighted-ratio.d.ts +11 -0
  27. package/dist/chart/compute/weighted-ratio.js +109 -0
  28. package/dist/chart/types.d.ts +55 -0
  29. package/dist/chart/types.js +1 -0
  30. package/dist/chart/utils.d.ts +14 -0
  31. package/dist/chart/utils.js +91 -0
  32. package/dist/chart/ws/base-candle.d.ts +29 -0
  33. package/dist/chart/ws/base-candle.js +71 -0
  34. package/dist/chart/ws/binance.d.ts +19 -0
  35. package/dist/chart/ws/binance.js +43 -0
  36. package/dist/chart/ws/bybit.d.ts +18 -0
  37. package/dist/chart/ws/bybit.js +63 -0
  38. package/dist/chart/ws/hyperliquid.d.ts +31 -0
  39. package/dist/chart/ws/hyperliquid.js +40 -0
  40. package/dist/chart/ws/index.d.ts +11 -0
  41. package/dist/chart/ws/index.js +24 -0
  42. package/dist/chart/ws/okx.d.ts +18 -0
  43. package/dist/chart/ws/okx.js +60 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +4 -0
  46. package/dist/orderbook/book/aggregate.d.ts +26 -0
  47. package/dist/orderbook/book/aggregate.js +38 -0
  48. package/dist/orderbook/book/local-book.d.ts +37 -0
  49. package/dist/orderbook/book/local-book.js +90 -0
  50. package/dist/orderbook/orderbook.d.ts +48 -0
  51. package/dist/orderbook/orderbook.js +111 -0
  52. package/dist/orderbook/types.d.ts +67 -0
  53. package/dist/orderbook/types.js +4 -0
  54. package/dist/orderbook/utils.d.ts +12 -0
  55. package/dist/orderbook/utils.js +35 -0
  56. package/dist/orderbook/ws/base-depth.d.ts +41 -0
  57. package/dist/orderbook/ws/base-depth.js +89 -0
  58. package/dist/orderbook/ws/binance.d.ts +23 -0
  59. package/dist/orderbook/ws/binance.js +126 -0
  60. package/dist/orderbook/ws/bybit.d.ts +15 -0
  61. package/dist/orderbook/ws/bybit.js +40 -0
  62. package/dist/orderbook/ws/hyperliquid.d.ts +20 -0
  63. package/dist/orderbook/ws/hyperliquid.js +87 -0
  64. package/dist/orderbook/ws/index.d.ts +11 -0
  65. package/dist/orderbook/ws/index.js +24 -0
  66. package/dist/orderbook/ws/okx.d.ts +15 -0
  67. package/dist/orderbook/ws/okx.js +33 -0
  68. package/dist/shared/types.d.ts +6 -0
  69. package/dist/shared/types.js +1 -0
  70. package/dist/transport/base-transport.d.ts +28 -0
  71. package/dist/transport/base-transport.js +95 -0
  72. package/dist/transport/binance.d.ts +13 -0
  73. package/dist/transport/binance.js +16 -0
  74. package/dist/transport/bybit.d.ts +13 -0
  75. package/dist/transport/bybit.js +16 -0
  76. package/dist/transport/hyperliquid.d.ts +13 -0
  77. package/dist/transport/hyperliquid.js +16 -0
  78. package/dist/transport/index.d.ts +10 -0
  79. package/dist/transport/index.js +24 -0
  80. package/dist/transport/okx.d.ts +13 -0
  81. package/dist/transport/okx.js +16 -0
  82. package/package.json +37 -0
@@ -0,0 +1,60 @@
1
+ import { BaseCandleWs } from './base-candle';
2
+
3
+ const intervalMap = {
4
+ "1m": "1m",
5
+ "3m": "3m",
6
+ "5m": "5m",
7
+ "15m": "15m",
8
+ "30m": "30m",
9
+ "1h": "1H",
10
+ "2h": "2H",
11
+ "4h": "4H",
12
+ "8h": "8H",
13
+ "12h": "12H",
14
+ "1d": "1D",
15
+ "3d": "3D",
16
+ "1w": "1W",
17
+ "1M": "1M"
18
+ };
19
+ const toChannel = (interval) => `candle${intervalMap[interval]}`;
20
+ class OkxCandleWs extends BaseCandleWs {
21
+ buildSubscribeMessage(symbol, interval) {
22
+ return {
23
+ op: "subscribe",
24
+ args: [{ channel: toChannel(interval), instId: symbol }]
25
+ };
26
+ }
27
+ buildUnsubscribeMessage(symbol, interval) {
28
+ return {
29
+ op: "unsubscribe",
30
+ args: [{ channel: toChannel(interval), instId: symbol }]
31
+ };
32
+ }
33
+ parseMessage(data) {
34
+ if (data === "pong") return null;
35
+ let msg;
36
+ try {
37
+ msg = JSON.parse(data);
38
+ } catch {
39
+ return null;
40
+ }
41
+ const arg = msg.arg;
42
+ const candleData = msg.data;
43
+ if (!arg || !candleData || !Array.isArray(candleData) || candleData.length === 0) return null;
44
+ if (!arg.channel.startsWith("candle")) return null;
45
+ const d = candleData[0];
46
+ return {
47
+ symbol: arg.instId,
48
+ candle: {
49
+ t: Number(d[0]),
50
+ T: Number(d[0]),
51
+ o: Number(d[1]),
52
+ h: Number(d[2]),
53
+ l: Number(d[3]),
54
+ c: Number(d[4])
55
+ }
56
+ };
57
+ }
58
+ }
59
+
60
+ export { OkxCandleWs };
@@ -0,0 +1,10 @@
1
+ export { Chart } from './chart/chart.js';
2
+ export { Bar, CandleData, CandleInterval, ChartConfig, ChartType, RealtimeBarCallback, RealtimeCandleCallback, TokenSelection } from './chart/types.js';
3
+ export { Orderbook } from './orderbook/orderbook.js';
4
+ export { AggregationConfig, BBO, CexAggregationConfig, HyperliquidAggregationConfig, OrderbookCallback, OrderbookConfig, OrderbookLevel, OrderbookSnapshot } from './orderbook/types.js';
5
+ export { getAvailableAggregations } from './orderbook/utils.js';
6
+ export { CreateTransport, Transport } from './transport/index.js';
7
+ export { BaseTransport } from './transport/base-transport.js';
8
+ import '@pear-protocol/types';
9
+ import 'partysocket';
10
+ import './shared/types.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Chart } from './chart/chart';
2
+ export { Orderbook } from './orderbook/orderbook';
3
+ export { getAvailableAggregations } from './orderbook/utils';
4
+ export { BaseTransport, CreateTransport } from './transport';
@@ -0,0 +1,26 @@
1
+ import { OrderbookLevel } from '../types.js';
2
+ import { LocalBook } from './local-book.js';
3
+ import '../../transport/index.js';
4
+ import '@pear-protocol/types';
5
+ import '../../transport/base-transport.js';
6
+ import 'partysocket';
7
+ import '../../shared/types.js';
8
+
9
+ /**
10
+ * Pure aggregation of a LocalBook into top-N bid/ask buckets at the given
11
+ * bucket size. Bids round *down* to the bucket boundary and asks round *up*
12
+ * — this ensures that any given raw price lands in the correct side's bucket
13
+ * without overlap at exact boundaries.
14
+ *
15
+ * Uses BigNumber internally to avoid floating-point artifacts in bucket
16
+ * boundaries and size accumulation.
17
+ *
18
+ * @param bucketSize - The price increment to aggregate into (e.g. 0.1, 1, 10).
19
+ * @param depth - Number of aggregated levels to return per side.
20
+ */
21
+ declare function aggregateBook(localBook: LocalBook, bucketSize: number, depth: number): {
22
+ bids: OrderbookLevel[];
23
+ asks: OrderbookLevel[];
24
+ };
25
+
26
+ export { aggregateBook };
@@ -0,0 +1,38 @@
1
+ import BigNumber from 'bignumber.js';
2
+
3
+ function aggregateBook(localBook, bucketSize, depth) {
4
+ const bucket = new BigNumber(bucketSize);
5
+ if (!bucket.isFinite() || bucket.lte(0)) return { bids: [], asks: [] };
6
+ return {
7
+ bids: walk(localBook.sortedBids(), bucket, "bid", depth),
8
+ asks: walk(localBook.sortedAsks(), bucket, "ask", depth)
9
+ };
10
+ }
11
+ function walk(sorted, bucket, side, depth) {
12
+ const result = [];
13
+ let currentBoundary = null;
14
+ let currentSize = new BigNumber(0);
15
+ for (let i = 0; i < sorted.length; i++) {
16
+ const entry = sorted[i];
17
+ if (!entry) continue;
18
+ const [price, size] = entry;
19
+ if (size <= 0) continue;
20
+ const p = new BigNumber(price);
21
+ const boundary = side === "bid" ? p.div(bucket).integerValue(BigNumber.ROUND_FLOOR).times(bucket) : p.div(bucket).integerValue(BigNumber.ROUND_CEIL).times(bucket);
22
+ if (currentBoundary === null || !boundary.eq(currentBoundary)) {
23
+ if (currentBoundary !== null) {
24
+ result.push({ price: currentBoundary.toNumber(), size: currentSize.toNumber() });
25
+ if (result.length >= depth) return result;
26
+ }
27
+ currentBoundary = boundary;
28
+ currentSize = new BigNumber(0);
29
+ }
30
+ currentSize = currentSize.plus(size);
31
+ }
32
+ if (currentBoundary !== null && result.length < depth) {
33
+ result.push({ price: currentBoundary.toNumber(), size: currentSize.toNumber() });
34
+ }
35
+ return result;
36
+ }
37
+
38
+ export { aggregateBook };
@@ -0,0 +1,37 @@
1
+ import { BBO } from '../types.js';
2
+ import '../../transport/index.js';
3
+ import '@pear-protocol/types';
4
+ import '../../transport/base-transport.js';
5
+ import 'partysocket';
6
+ import '../../shared/types.js';
7
+
8
+ type Level = readonly [price: string, size: number];
9
+ declare class LocalBook {
10
+ private _bids;
11
+ private _asks;
12
+ lastUpdateTs: number;
13
+ isLoaded: boolean;
14
+ /** Replace all state with a full snapshot. */
15
+ applySnapshot(bids: [string, number][], asks: [string, number][], ts: number): void;
16
+ /** Incrementally apply level changes. size === 0 means remove. */
17
+ applyDelta(bids: [string, number][], asks: [string, number][], ts: number): void;
18
+ /** Full sorted bids, price-descending. */
19
+ sortedBids(): readonly Level[];
20
+ /** Full sorted asks, price-ascending. */
21
+ sortedAsks(): readonly Level[];
22
+ /** Top N bids, price-descending. */
23
+ topBids(n: number): readonly Level[];
24
+ /** Top N asks, price-ascending. */
25
+ topAsks(n: number): readonly Level[];
26
+ /** Best bid, best ask, spread, and spread percentage. */
27
+ bbo(): BBO;
28
+ /** Reset the book to its initial empty state. */
29
+ clear(): void;
30
+ /**
31
+ * Insert, update, or remove a level in a sorted array.
32
+ * @param dir -1 for descending (bids), 1 for ascending (asks)
33
+ */
34
+ private upsert;
35
+ }
36
+
37
+ export { type Level, LocalBook };
@@ -0,0 +1,90 @@
1
+ import BigNumber from 'bignumber.js';
2
+
3
+ class LocalBook {
4
+ _bids = [];
5
+ _asks = [];
6
+ lastUpdateTs = 0;
7
+ isLoaded = false;
8
+ /** Replace all state with a full snapshot. */
9
+ applySnapshot(bids, asks, ts) {
10
+ this._bids = bids.filter(([, s]) => s > 0).sort((a, b) => Number(b[0]) - Number(a[0]));
11
+ this._asks = asks.filter(([, s]) => s > 0).sort((a, b) => Number(a[0]) - Number(b[0]));
12
+ this.lastUpdateTs = ts;
13
+ this.isLoaded = true;
14
+ }
15
+ /** Incrementally apply level changes. size === 0 means remove. */
16
+ applyDelta(bids, asks, ts) {
17
+ for (const [price, size] of bids) {
18
+ this.upsert(this._bids, price, size, -1);
19
+ }
20
+ for (const [price, size] of asks) {
21
+ this.upsert(this._asks, price, size, 1);
22
+ }
23
+ this.lastUpdateTs = ts;
24
+ }
25
+ /** Full sorted bids, price-descending. */
26
+ sortedBids() {
27
+ return this._bids;
28
+ }
29
+ /** Full sorted asks, price-ascending. */
30
+ sortedAsks() {
31
+ return this._asks;
32
+ }
33
+ /** Top N bids, price-descending. */
34
+ topBids(n) {
35
+ return this._bids.slice(0, n);
36
+ }
37
+ /** Top N asks, price-ascending. */
38
+ topAsks(n) {
39
+ return this._asks.slice(0, n);
40
+ }
41
+ /** Best bid, best ask, spread, and spread percentage. */
42
+ bbo() {
43
+ const bestBid = this._bids[0] ? new BigNumber(this._bids[0][0]) : null;
44
+ const bestAsk = this._asks[0] ? new BigNumber(this._asks[0][0]) : null;
45
+ const spread = bestBid !== null && bestAsk !== null ? bestAsk.minus(bestBid) : null;
46
+ const midPrice = bestBid !== null && bestAsk !== null ? bestAsk.plus(bestBid).div(2) : null;
47
+ const spreadPct = spread !== null && midPrice !== null && !midPrice.isZero() ? spread.div(midPrice).times(100) : null;
48
+ return {
49
+ bestBid: bestBid?.toFixed() ?? null,
50
+ bestAsk: bestAsk?.toFixed() ?? null,
51
+ spread: spread?.toFixed() ?? null,
52
+ spreadPct: spreadPct?.toFixed() ?? null
53
+ };
54
+ }
55
+ /** Reset the book to its initial empty state. */
56
+ clear() {
57
+ this._bids = [];
58
+ this._asks = [];
59
+ this.lastUpdateTs = 0;
60
+ this.isLoaded = false;
61
+ }
62
+ /**
63
+ * Insert, update, or remove a level in a sorted array.
64
+ * @param dir -1 for descending (bids), 1 for ascending (asks)
65
+ */
66
+ upsert(levels, price, size, dir) {
67
+ const numPrice = Number(price);
68
+ let lo = 0;
69
+ let hi = levels.length;
70
+ while (lo < hi) {
71
+ const mid = lo + hi >>> 1;
72
+ const cmp = (Number(levels[mid]?.[0]) - numPrice) * dir;
73
+ if (cmp < 0) lo = mid + 1;
74
+ else hi = mid;
75
+ }
76
+ if (lo < levels.length && levels[lo]?.[0] === price) {
77
+ if (size === 0) {
78
+ levels.splice(lo, 1);
79
+ } else {
80
+ levels[lo] = [price, size];
81
+ }
82
+ return;
83
+ }
84
+ if (size > 0) {
85
+ levels.splice(lo, 0, [price, size]);
86
+ }
87
+ }
88
+ }
89
+
90
+ export { LocalBook };
@@ -0,0 +1,48 @@
1
+ import { OrderbookConfig, OrderbookCallback, OrderbookSnapshot, BBO } from './types.js';
2
+ import '../transport/index.js';
3
+ import '@pear-protocol/types';
4
+ import '../transport/base-transport.js';
5
+ import 'partysocket';
6
+ import '../shared/types.js';
7
+
8
+ /**
9
+ * Orderbook exposes an aggregated top-of-book view for a single symbol.
10
+ * The underlying transport is owned by the caller and must be destroyed
11
+ * separately after the Orderbook itself is destroyed.
12
+ */
13
+ declare class Orderbook {
14
+ private depthWs;
15
+ private symbol;
16
+ private book;
17
+ private listener;
18
+ private aggregation;
19
+ private depth;
20
+ constructor(config: OrderbookConfig);
21
+ /**
22
+ * Subscribe to orderbook updates. Returns the symbol as the subscription ID.
23
+ */
24
+ subscribe(cb: OrderbookCallback): string;
25
+ /**
26
+ * Unsubscribe from orderbook updates.
27
+ */
28
+ unsubscribe(id: string): void;
29
+ /**
30
+ * Set the aggregation bucket size (e.g. 0.1, 1, 10, 100).
31
+ * Set to 0 to disable aggregation.
32
+ */
33
+ setAggregation(aggregation: number): void;
34
+ getAggregation(): number;
35
+ /**
36
+ * Update the snapshotted mid-price used for server-side aggregation params
37
+ * (e.g. Hyperliquid's nSigFigs/mantissa).
38
+ */
39
+ setSnapshottedPrice(price: number): void;
40
+ getSnapshot(): OrderbookSnapshot | null;
41
+ get bbo(): BBO;
42
+ destroy(): void;
43
+ private handleUpdate;
44
+ private buildSnapshot;
45
+ private dispatch;
46
+ }
47
+
48
+ export { Orderbook };
@@ -0,0 +1,111 @@
1
+ import { aggregateBook } from './book/aggregate';
2
+ import { LocalBook } from './book/local-book';
3
+ import { DEFAULT_DEPTH } from './types';
4
+ import { createDepthWs } from './ws';
5
+
6
+ class Orderbook {
7
+ depthWs;
8
+ symbol;
9
+ book = new LocalBook();
10
+ listener = null;
11
+ aggregation;
12
+ depth;
13
+ constructor(config) {
14
+ this.symbol = config.symbol;
15
+ this.aggregation = config.aggregation ?? 0;
16
+ this.depth = config.depth ?? DEFAULT_DEPTH;
17
+ this.depthWs = createDepthWs(config.transport, config.transport.connector, (update) => this.handleUpdate(update));
18
+ if (config.snapshottedPrice !== void 0) {
19
+ this.depthWs.setSnapshottedPrice(config.snapshottedPrice);
20
+ }
21
+ this.depthWs.start();
22
+ this.depthWs.subscribe(this.symbol);
23
+ }
24
+ /**
25
+ * Subscribe to orderbook updates. Returns the symbol as the subscription ID.
26
+ */
27
+ subscribe(cb) {
28
+ this.listener = cb;
29
+ return this.symbol;
30
+ }
31
+ /**
32
+ * Unsubscribe from orderbook updates.
33
+ */
34
+ unsubscribe(id) {
35
+ if (id !== this.symbol) throw new Error(`Subscription "${id}" not found`);
36
+ this.listener = null;
37
+ }
38
+ /**
39
+ * Set the aggregation bucket size (e.g. 0.1, 1, 10, 100).
40
+ * Set to 0 to disable aggregation.
41
+ */
42
+ setAggregation(aggregation) {
43
+ if (this.aggregation === aggregation) return;
44
+ this.aggregation = aggregation;
45
+ this.depthWs.onAggregationChange(aggregation);
46
+ if (this.book.isLoaded) {
47
+ this.dispatch(this.buildSnapshot());
48
+ }
49
+ }
50
+ getAggregation() {
51
+ return this.aggregation;
52
+ }
53
+ /**
54
+ * Update the snapshotted mid-price used for server-side aggregation params
55
+ * (e.g. Hyperliquid's nSigFigs/mantissa).
56
+ */
57
+ setSnapshottedPrice(price) {
58
+ this.depthWs.setSnapshottedPrice(price);
59
+ }
60
+ getSnapshot() {
61
+ if (!this.book.isLoaded) return null;
62
+ return this.buildSnapshot();
63
+ }
64
+ get bbo() {
65
+ if (!this.book.isLoaded) return { bestBid: null, bestAsk: null, spread: null, spreadPct: null };
66
+ return this.book.bbo();
67
+ }
68
+ destroy() {
69
+ this.depthWs.stop();
70
+ this.listener = null;
71
+ }
72
+ handleUpdate(update) {
73
+ if (update.symbol !== this.symbol) return;
74
+ if (update.type === "snapshot") {
75
+ this.book.applySnapshot(update.bids, update.asks, update.ts);
76
+ } else {
77
+ if (!this.book.isLoaded) return;
78
+ this.book.applyDelta(update.bids, update.asks, update.ts);
79
+ }
80
+ this.dispatch(this.buildSnapshot());
81
+ }
82
+ buildSnapshot() {
83
+ let bids;
84
+ let asks;
85
+ if (!this.depthWs.serverAggregated && this.aggregation > 0) {
86
+ const aggregated = aggregateBook(this.book, this.aggregation, this.depth);
87
+ bids = aggregated.bids;
88
+ asks = aggregated.asks;
89
+ } else {
90
+ bids = this.book.topBids(this.depth).map(([price, size]) => ({ price: Number(price), size }));
91
+ asks = this.book.topAsks(this.depth).map(([price, size]) => ({ price: Number(price), size }));
92
+ }
93
+ return {
94
+ symbol: this.symbol,
95
+ bids,
96
+ asks,
97
+ aggregation: this.aggregation,
98
+ ts: this.book.lastUpdateTs
99
+ };
100
+ }
101
+ dispatch(snapshot) {
102
+ if (!this.listener) return;
103
+ try {
104
+ this.listener(snapshot);
105
+ } catch (error) {
106
+ console.warn("Error in orderbook stream callback", error);
107
+ }
108
+ }
109
+ }
110
+
111
+ export { Orderbook };
@@ -0,0 +1,67 @@
1
+ import { Transport } from '../transport/index.js';
2
+ import '@pear-protocol/types';
3
+ import '../transport/base-transport.js';
4
+ import 'partysocket';
5
+ import '../shared/types.js';
6
+
7
+ declare const DEFAULT_DEPTH = 10;
8
+ declare const DEFAULT_MULTIPLIERS: number[];
9
+ type SigFigs = 2 | 3 | 4 | 5 | null;
10
+ interface HyperliquidAggregationConfig {
11
+ maxDecimals: number;
12
+ midPrice: number;
13
+ }
14
+ interface CexAggregationConfig {
15
+ tickSize: number;
16
+ midPrice: number;
17
+ }
18
+ type AggregationConfig = HyperliquidAggregationConfig | CexAggregationConfig;
19
+ interface OrderbookConfig {
20
+ transport: Transport;
21
+ /** The symbol to subscribe to. */
22
+ symbol: string;
23
+ /** Bucket size for price aggregation (e.g. 0.1, 1, 10, 100). Default: no aggregation. */
24
+ aggregation?: number;
25
+ /** Number of price levels per side in snapshots. Default: 10. */
26
+ depth?: number;
27
+ /** Snapshotted price, used for server-side aggregation params & deciding the aggregation options. */
28
+ snapshottedPrice?: number;
29
+ }
30
+ interface OrderbookLevel {
31
+ price: number;
32
+ /** Base-asset units. */
33
+ size: number;
34
+ }
35
+ interface OrderbookSnapshot {
36
+ symbol: string;
37
+ /** Price-descending. */
38
+ bids: OrderbookLevel[];
39
+ /** Price-ascending. */
40
+ asks: OrderbookLevel[];
41
+ /** Active bucket size for aggregation. */
42
+ aggregation: number;
43
+ /** ms since epoch of the last applied update. */
44
+ ts: number;
45
+ }
46
+ interface BBO {
47
+ bestBid: string | null;
48
+ bestAsk: string | null;
49
+ spread: string | null;
50
+ spreadPct: string | null;
51
+ }
52
+ type OrderbookCallback = (snapshot: OrderbookSnapshot) => void;
53
+ interface DepthUpdate {
54
+ symbol: string;
55
+ type: 'snapshot' | 'delta';
56
+ bids: [price: string, size: number][];
57
+ asks: [price: string, size: number][];
58
+ ts: number;
59
+ /** Binance diff stream: first update ID in this event. */
60
+ firstUpdateId?: number;
61
+ /** Binance diff stream: final update ID in this event. */
62
+ finalUpdateId?: number;
63
+ /** Binance diff stream: previous event's final update ID. */
64
+ prevFinalUpdateId?: number;
65
+ }
66
+
67
+ export { type AggregationConfig, type BBO, type CexAggregationConfig, DEFAULT_DEPTH, DEFAULT_MULTIPLIERS, type DepthUpdate, type HyperliquidAggregationConfig, type OrderbookCallback, type OrderbookConfig, type OrderbookLevel, type OrderbookSnapshot, type SigFigs };
@@ -0,0 +1,4 @@
1
+ const DEFAULT_DEPTH = 10;
2
+ const DEFAULT_MULTIPLIERS = [1, 10, 100, 1e3, 1e4];
3
+
4
+ export { DEFAULT_DEPTH, DEFAULT_MULTIPLIERS };
@@ -0,0 +1,12 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { AggregationConfig } from './types.js';
3
+ import '../transport/index.js';
4
+ import '../transport/base-transport.js';
5
+ import 'partysocket';
6
+ import '../shared/types.js';
7
+
8
+ declare function getCexAvailableAggregations(tickSize: number, midPrice: number, multipliers?: number[]): number[];
9
+ declare function getHyperliquidAvailableAggregations(maxDecimals: number, midPrice: number, multipliers?: number[]): number[];
10
+ declare function getAvailableAggregations(connector: Connector, config: AggregationConfig, multipliers?: number[]): number[];
11
+
12
+ export { getAvailableAggregations, getCexAvailableAggregations, getHyperliquidAvailableAggregations };
@@ -0,0 +1,35 @@
1
+ import BigNumber from 'bignumber.js';
2
+ import { DEFAULT_MULTIPLIERS } from './types';
3
+
4
+ function getCexAvailableAggregations(tickSize, midPrice, multipliers = DEFAULT_MULTIPLIERS) {
5
+ if (tickSize <= 0 || midPrice <= 0) return [];
6
+ const tick = new BigNumber(tickSize);
7
+ const priceMag = Math.floor(Math.log10(midPrice));
8
+ const maxBucketMag = priceMag - 1;
9
+ return multipliers.map((m) => tick.times(m).toNumber()).filter((bucket) => {
10
+ const bucketMag = Math.floor(Math.log10(bucket));
11
+ return bucket < midPrice && bucketMag <= maxBucketMag;
12
+ });
13
+ }
14
+ function getHyperliquidAvailableAggregations(maxDecimals, midPrice, multipliers = DEFAULT_MULTIPLIERS) {
15
+ if (midPrice <= 0) return [];
16
+ const decimals = Math.max(0, maxDecimals);
17
+ const baseBucket = new BigNumber(10).pow(-decimals);
18
+ const priceMag = Math.floor(Math.log10(midPrice));
19
+ const minBucketMag = priceMag - 4;
20
+ const maxBucketMag = priceMag - 1;
21
+ return multipliers.map((m) => baseBucket.times(m).toNumber()).filter((bucket) => {
22
+ const bucketMag = Math.floor(Math.log10(bucket));
23
+ return bucket < midPrice && bucketMag >= minBucketMag && bucketMag <= maxBucketMag;
24
+ });
25
+ }
26
+ function getAvailableAggregations(connector, config, multipliers = DEFAULT_MULTIPLIERS) {
27
+ if (connector === "hyperliquid") {
28
+ const { maxDecimals, midPrice: midPrice2 } = config;
29
+ return getHyperliquidAvailableAggregations(maxDecimals, midPrice2, multipliers);
30
+ }
31
+ const { tickSize, midPrice } = config;
32
+ return getCexAvailableAggregations(tickSize, midPrice, multipliers);
33
+ }
34
+
35
+ export { getAvailableAggregations, getCexAvailableAggregations, getHyperliquidAvailableAggregations };
@@ -0,0 +1,41 @@
1
+ import { WsMessage } from '../../shared/types.js';
2
+ import { BaseTransport } from '../../transport/base-transport.js';
3
+ import { DepthUpdate } from '../types.js';
4
+ import '@pear-protocol/types';
5
+ import 'partysocket';
6
+ import '../../transport/index.js';
7
+
8
+ type DepthHandler = (update: DepthUpdate) => void;
9
+ /**
10
+ * Convert string-encoded L2 levels into numeric tuples. Accepts any tuple
11
+ * arity where the first two slots are `[price, size]` — exchanges sometimes
12
+ * pad trailing metadata (OKX adds liquidated-order counts, for example) that
13
+ * the book layer ignores.
14
+ */
15
+ declare function parseStringLevels(levels: ReadonlyArray<ReadonlyArray<string>> | undefined): [string, number][];
16
+ declare abstract class BaseDepthWs {
17
+ protected transport: BaseTransport;
18
+ protected onUpdate: DepthHandler;
19
+ protected snapshottedPrice: number;
20
+ private messageListenerId;
21
+ private openListenerId;
22
+ private subscribedSymbols;
23
+ protected abstract buildSubscribeMessage(symbol: string): WsMessage;
24
+ protected abstract buildUnsubscribeMessage(symbol: string): WsMessage;
25
+ protected abstract parseMessage(data: string): DepthUpdate | null;
26
+ constructor(transport: BaseTransport, onUpdate: DepthHandler);
27
+ start(): void;
28
+ stop(): void;
29
+ get connected(): boolean;
30
+ subscribe(symbol: string): void;
31
+ unsubscribe(symbol: string): void;
32
+ /** Whether this adapter handles aggregation server-side. */
33
+ get serverAggregated(): boolean;
34
+ /** Called when aggregation changes. Override to resubscribe with new params. */
35
+ onAggregationChange(_aggregation: number): void;
36
+ /** Update the snapshotted mid-price used for server-side aggregation params. */
37
+ setSnapshottedPrice(price: number): void;
38
+ protected get symbols(): ReadonlySet<string>;
39
+ }
40
+
41
+ export { BaseDepthWs, type DepthHandler, parseStringLevels };
@@ -0,0 +1,89 @@
1
+ function parseStringLevels(levels) {
2
+ if (!levels) return [];
3
+ const out = new Array(levels.length);
4
+ for (let i = 0; i < levels.length; i++) {
5
+ const entry = levels[i];
6
+ if (!entry) continue;
7
+ out[i] = [entry[0], Number(entry[1])];
8
+ }
9
+ return out;
10
+ }
11
+ class BaseDepthWs {
12
+ transport;
13
+ onUpdate;
14
+ snapshottedPrice = 0;
15
+ messageListenerId = null;
16
+ openListenerId = null;
17
+ subscribedSymbols = /* @__PURE__ */ new Set();
18
+ constructor(transport, onUpdate) {
19
+ this.transport = transport;
20
+ this.onUpdate = onUpdate;
21
+ }
22
+ start() {
23
+ if (this.messageListenerId) return;
24
+ this.messageListenerId = this.transport.addMessageListener((data) => {
25
+ const update = this.parseMessage(data);
26
+ if (update) this.onUpdate(update);
27
+ });
28
+ this.openListenerId = this.transport.addOpenListener(() => {
29
+ for (const symbol of this.subscribedSymbols) {
30
+ this.transport.send(this.buildSubscribeMessage(symbol));
31
+ }
32
+ });
33
+ if (!this.transport.connected) {
34
+ this.transport.connect();
35
+ }
36
+ if (this.transport.connected) {
37
+ for (const symbol of this.subscribedSymbols) {
38
+ this.transport.send(this.buildSubscribeMessage(symbol));
39
+ }
40
+ }
41
+ }
42
+ stop() {
43
+ if (this.messageListenerId) {
44
+ this.transport.removeMessageListener(this.messageListenerId);
45
+ this.messageListenerId = null;
46
+ }
47
+ if (this.openListenerId) {
48
+ this.transport.removeOpenListener(this.openListenerId);
49
+ this.openListenerId = null;
50
+ }
51
+ for (const symbol of this.subscribedSymbols) {
52
+ this.transport.send(this.buildUnsubscribeMessage(symbol));
53
+ }
54
+ this.subscribedSymbols.clear();
55
+ }
56
+ get connected() {
57
+ return this.transport.connected;
58
+ }
59
+ subscribe(symbol) {
60
+ if (this.subscribedSymbols.has(symbol)) return;
61
+ this.subscribedSymbols.add(symbol);
62
+ if (this.transport.connected) {
63
+ this.transport.send(this.buildSubscribeMessage(symbol));
64
+ }
65
+ }
66
+ unsubscribe(symbol) {
67
+ if (!this.subscribedSymbols.has(symbol)) return;
68
+ this.subscribedSymbols.delete(symbol);
69
+ if (this.transport.connected) {
70
+ this.transport.send(this.buildUnsubscribeMessage(symbol));
71
+ }
72
+ }
73
+ /** Whether this adapter handles aggregation server-side. */
74
+ get serverAggregated() {
75
+ return false;
76
+ }
77
+ /** Called when aggregation changes. Override to resubscribe with new params. */
78
+ onAggregationChange(_aggregation) {
79
+ }
80
+ /** Update the snapshotted mid-price used for server-side aggregation params. */
81
+ setSnapshottedPrice(price) {
82
+ this.snapshottedPrice = price;
83
+ }
84
+ get symbols() {
85
+ return this.subscribedSymbols;
86
+ }
87
+ }
88
+
89
+ export { BaseDepthWs, parseStringLevels };