@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
package/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # @pear-protocol/market-sdk
2
+
3
+ SDK for real-time market data across cryptocurrency derivative exchanges. Provides charting with multiple visualization modes (weighted ratio, price ratio, performance) and aggregated order book depth through a unified, exchange-agnostic interface with automatic WebSocket streaming.
4
+
5
+ ## Supported Exchanges
6
+
7
+ | Exchange | Connector Name |
8
+ |----------|---------------|
9
+ | Binance | `binance` |
10
+ | Bybit | `bybit` |
11
+ | Hyperliquid | `hyperliquid` |
12
+ | OKX | `okx` |
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @pear-protocol/market-sdk
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Chart, Orderbook, CreateTransport } from '@pear-protocol/market-sdk';
24
+
25
+ const transport = CreateTransport('hyperliquid');
26
+
27
+ // Chart
28
+ const chart = new Chart({
29
+ transport,
30
+ longTokens: [{ symbol: 'BTC', weight: 50 }, { symbol: 'ETH', weight: 50 }],
31
+ shortTokens: [{ symbol: 'SOL', weight: 100 }],
32
+ candleInterval: '1h',
33
+ });
34
+
35
+ const bars = await chart.getBars('weighted-ratio', startTime, endTime);
36
+
37
+ const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
38
+ console.log(bar.time, bar.open, bar.high, bar.low, bar.close);
39
+ });
40
+
41
+ // Order book
42
+ const orderbook = new Orderbook({
43
+ transport,
44
+ symbol: 'BTC',
45
+ aggregation: 10,
46
+ depth: 15,
47
+ });
48
+
49
+ orderbook.subscribe((snapshot) => {
50
+ console.log('bids:', snapshot.bids, 'asks:', snapshot.asks);
51
+ });
52
+
53
+ // Cleanup
54
+ chart.unsubscribeRealtimeBars(subId);
55
+ chart.destroy();
56
+ orderbook.destroy();
57
+ transport.destroy();
58
+ ```
59
+
60
+ ## Chart
61
+
62
+ ### Initialization
63
+
64
+ ```typescript
65
+ import { Chart, CreateTransport } from '@pear-protocol/market-sdk';
66
+
67
+ const transport = CreateTransport('binance');
68
+
69
+ const chart = new Chart({
70
+ transport,
71
+ longTokens: [{ symbol: 'BTC', weight: 60 }, { symbol: 'ETH', weight: 40 }],
72
+ shortTokens: [{ symbol: 'SOL', weight: 100 }],
73
+ candleInterval: '1h',
74
+ });
75
+ ```
76
+
77
+ #### `ChartConfig`
78
+
79
+ | Field | Type | Default | Description |
80
+ |-------|------|---------|-------------|
81
+ | `transport` | `Transport` | — | WebSocket transport instance |
82
+ | `longTokens` | `TokenSelection[]` | `[]` | Tokens on the long side with weights |
83
+ | `shortTokens` | `TokenSelection[]` | `[]` | Tokens on the short side with weights |
84
+ | `candleInterval` | `CandleInterval` | `'1h'` | Candle timeframe |
85
+
86
+ #### `TokenSelection`
87
+
88
+ ```typescript
89
+ { symbol: string; weight: number }
90
+ ```
91
+
92
+ #### `CandleInterval`
93
+
94
+ ```typescript
95
+ '1m' | '3m' | '5m' | '15m' | '30m' | '1h' | '2h' | '4h' | '8h' | '12h' | '1d' | '3d' | '1w' | '1M'
96
+ ```
97
+
98
+ ### Updating Tokens and Interval
99
+
100
+ Change the tokens or candle interval at any time. Both clear cached data and baseline prices.
101
+
102
+ ```typescript
103
+ chart.setTokens(
104
+ [{ symbol: 'BTC', weight: 100 }],
105
+ [{ symbol: 'ETH', weight: 100 }],
106
+ );
107
+
108
+ chart.setCandleInterval('15m');
109
+ ```
110
+
111
+ ### Fetching Historical Bars
112
+
113
+ Fetch historical OHLC bars for a given chart type and time range.
114
+
115
+ ```typescript
116
+ const bars = await chart.getBars('weighted-ratio', startTime, endTime);
117
+
118
+ for (const bar of bars) {
119
+ bar.time; // timestamp (ms)
120
+ bar.open; // open price
121
+ bar.high; // high price
122
+ bar.low; // low price
123
+ bar.close; // close price
124
+ }
125
+ ```
126
+
127
+ #### Chart Types
128
+
129
+ | Type | Description |
130
+ |------|-------------|
131
+ | `'weighted-ratio'` | Geometric mean ratio using token weights as exponents |
132
+ | `'price-ratio'` | Weighted sum of long prices divided by weighted sum of short prices |
133
+ | `'performance'` | Percentage performance relative to the earliest bar in the range |
134
+
135
+ #### Single-Asset Bars
136
+
137
+ Fetch bars for one specific symbol in the configured token set.
138
+
139
+ ```typescript
140
+ const bars = await chart.getAssetBars('BTC', startTime, endTime);
141
+ ```
142
+
143
+ ### Subscribing to Real-Time Bars
144
+
145
+ Subscribe to live bar updates. The WebSocket connection is established automatically on the first subscription.
146
+
147
+ ```typescript
148
+ const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
149
+ console.log(bar.time, bar.open, bar.high, bar.low, bar.close);
150
+ });
151
+
152
+ // Single-asset real-time bars
153
+ const assetSubId = chart.subscribeRealtimeAssetBars('ETH', (bar) => {
154
+ console.log(bar.time, bar.close);
155
+ });
156
+
157
+ // Stop receiving updates
158
+ chart.unsubscribeRealtimeBars(subId);
159
+ chart.unsubscribeRealtimeBars(assetSubId);
160
+ ```
161
+
162
+ ### Data Boundary
163
+
164
+ Get the earliest timestamp for which historical data is available.
165
+
166
+ ```typescript
167
+ const boundary = chart.getEffectiveDataBoundary(); // number | null
168
+ ```
169
+
170
+ ### Cache Management
171
+
172
+ ```typescript
173
+ // Clear all cached historical data and baseline prices
174
+ chart.clearCache();
175
+ ```
176
+
177
+ ### Cleanup
178
+
179
+ ```typescript
180
+ chart.destroy();
181
+ ```
182
+
183
+ ## Order Book
184
+
185
+ ### Initialization
186
+
187
+ The `Orderbook` connects to the exchange WebSocket immediately on construction and begins receiving depth updates.
188
+
189
+ ```typescript
190
+ import { Orderbook, CreateTransport } from '@pear-protocol/market-sdk';
191
+
192
+ const transport = CreateTransport('hyperliquid');
193
+
194
+ const orderbook = new Orderbook({
195
+ transport,
196
+ symbol: 'BTC',
197
+ aggregation: 10,
198
+ depth: 15,
199
+ });
200
+ ```
201
+
202
+ #### `OrderbookConfig`
203
+
204
+ | Field | Type | Default | Description |
205
+ |-------|------|---------|-------------|
206
+ | `transport` | `Transport` | — | WebSocket transport instance |
207
+ | `symbol` | `string` | — | Trading pair symbol |
208
+ | `aggregation` | `number` | `0` | Bucket size for price aggregation (0 = no aggregation) |
209
+ | `depth` | `number` | `10` | Number of price levels per side |
210
+ | `snapshottedPrice` | `number` | — | Current mid-price for server-side aggregation params |
211
+
212
+ ### Subscribing to Updates
213
+
214
+ Subscribe to real-time order book snapshots. The callback fires on every depth update from the exchange.
215
+
216
+ ```typescript
217
+ const subId = orderbook.subscribe((snapshot) => {
218
+ snapshot.symbol; // "BTC"
219
+ snapshot.aggregation; // active bucket size
220
+ snapshot.ts; // last update timestamp (ms)
221
+
222
+ for (const bid of snapshot.bids) { // price-descending
223
+ bid.price; // price level
224
+ bid.size; // base-asset quantity
225
+ }
226
+
227
+ for (const ask of snapshot.asks) { // price-ascending
228
+ ask.price;
229
+ ask.size;
230
+ }
231
+ });
232
+
233
+ // Stop receiving updates
234
+ orderbook.unsubscribe(subId);
235
+ ```
236
+
237
+ ### Reading the Current Snapshot
238
+
239
+ Access the latest order book state without subscribing. Returns `null` if no data has been received yet.
240
+
241
+ ```typescript
242
+ const snapshot = orderbook.getSnapshot();
243
+ if (snapshot) {
244
+ console.log(snapshot.bids, snapshot.asks);
245
+ }
246
+ ```
247
+
248
+ ### Changing Aggregation
249
+
250
+ Change the price aggregation bucket size at any time. Listeners are notified immediately with a re-aggregated snapshot.
251
+
252
+ ```typescript
253
+ orderbook.setAggregation(100); // aggregate to $100 buckets
254
+ orderbook.setAggregation(0); // disable aggregation
255
+
256
+ const current = orderbook.getAggregation();
257
+ ```
258
+
259
+ ### Best Bid / Offer (BBO)
260
+
261
+ Access the current best bid, best ask, spread, and spread percentage. Returns `null` values when the book has not loaded yet.
262
+
263
+ ```typescript
264
+ const { bestBid, bestAsk, spread, spreadPct } = orderbook.bbo;
265
+ // bestBid: "65000.5" | null
266
+ // bestAsk: "65001.0" | null
267
+ // spread: "0.5" | null
268
+ // spreadPct: "0.00077" | null
269
+ ```
270
+
271
+ ### Available Aggregation Levels
272
+
273
+ Compute recommended aggregation bucket sizes for a given connector and price level.
274
+
275
+ ```typescript
276
+ import { getAvailableAggregations } from '@pear-protocol/market-sdk';
277
+
278
+ // CEX exchanges (Binance, Bybit, OKX) — uses tick size
279
+ const levels = getAvailableAggregations('binance', { tickSize: 0.1, midPrice: 65000 });
280
+ // e.g. [0.1, 1, 10, 100, 1000]
281
+
282
+ // Hyperliquid — uses max decimal places
283
+ const levels = getAvailableAggregations('hyperliquid', { maxDecimals: 2, midPrice: 65000 });
284
+ ```
285
+
286
+ ### Updating Snapshotted Price
287
+
288
+ Update the mid-price used for server-side aggregation parameters (e.g. Hyperliquid).
289
+
290
+ ```typescript
291
+ orderbook.setSnapshottedPrice(65250.5);
292
+ ```
293
+
294
+ ### Cleanup
295
+
296
+ ```typescript
297
+ orderbook.destroy();
298
+ ```
299
+
300
+ The underlying transport is owned by the caller and must be destroyed separately.
301
+
302
+ ## Transport
303
+
304
+ Both `Chart` and `Orderbook` require a `Transport` instance for WebSocket communication. A single transport can be shared across multiple consumers on the same exchange.
305
+
306
+ ```typescript
307
+ import { CreateTransport } from '@pear-protocol/market-sdk';
308
+
309
+ const transport = CreateTransport('hyperliquid');
310
+
311
+ // Use with Chart and Orderbook
312
+ const chart = new Chart({ transport, /* ... */ });
313
+ const orderbook = new Orderbook({ transport, symbol: 'BTC' });
314
+
315
+ // Cleanup — destroy transport after all consumers
316
+ chart.destroy();
317
+ orderbook.destroy();
318
+ transport.destroy();
319
+ ```
@@ -0,0 +1,21 @@
1
+ import { CandleInterval, CandleData, HistoricalRange } 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
+ declare class CandleCache {
9
+ private historicalPriceData;
10
+ private loadingTokens;
11
+ addLoadingToken(symbol: string): void;
12
+ removeLoadingToken(symbol: string): void;
13
+ addHistoricalPriceData(symbol: string, interval: CandleInterval, candles: CandleData[], range: HistoricalRange): void;
14
+ hasData(symbol: string, interval: CandleInterval, startTime: number, endTime: number): boolean;
15
+ getData(symbol: string, interval: CandleInterval, startTime: number, endTime: number): CandleData[];
16
+ getEffectiveBoundary(symbols: string[], interval: CandleInterval): number | null;
17
+ removeToken(symbol: string, interval: CandleInterval): void;
18
+ clear(): void;
19
+ }
20
+
21
+ export { CandleCache };
@@ -0,0 +1,101 @@
1
+ import { createKey, mergeRanges, getIntervalSeconds } from '../utils';
2
+
3
+ class CandleCache {
4
+ historicalPriceData = {};
5
+ loadingTokens = /* @__PURE__ */ new Set();
6
+ addLoadingToken(symbol) {
7
+ this.loadingTokens.add(symbol);
8
+ }
9
+ removeLoadingToken(symbol) {
10
+ this.loadingTokens.delete(symbol);
11
+ }
12
+ addHistoricalPriceData(symbol, interval, candles, range) {
13
+ const key = createKey(symbol, interval);
14
+ const existing = this.historicalPriceData[key];
15
+ if (!existing) {
16
+ const sortedCandles = [...candles].sort((a, b) => a.t - b.t);
17
+ const noDataBefore2 = sortedCandles.length === 0 ? range.end : null;
18
+ this.historicalPriceData[key] = {
19
+ symbol,
20
+ interval,
21
+ candles: sortedCandles,
22
+ oldestTime: sortedCandles.length > 0 ? sortedCandles[0]?.t ?? null : null,
23
+ latestTime: sortedCandles.length > 0 ? sortedCandles[sortedCandles.length - 1]?.t ?? null : null,
24
+ requestedRanges: [range],
25
+ noDataBefore: noDataBefore2
26
+ };
27
+ return;
28
+ }
29
+ const existingTimes = new Set(existing.candles.map((c) => c.t));
30
+ const newCandles = candles.filter((c) => !existingTimes.has(c.t));
31
+ const mergedCandles = [...existing.candles, ...newCandles].sort((a, b) => a.t - b.t);
32
+ const oldestTime = mergedCandles.length > 0 ? mergedCandles[0]?.t ?? null : null;
33
+ const latestTime = mergedCandles.length > 0 ? mergedCandles[mergedCandles.length - 1]?.t ?? null : null;
34
+ const mergedRanges = mergeRanges(existing.requestedRanges, range);
35
+ let noDataBefore = existing.noDataBefore;
36
+ if (candles.length === 0 && existing.oldestTime !== null && range.end <= existing.oldestTime) {
37
+ noDataBefore = existing.oldestTime;
38
+ }
39
+ this.historicalPriceData[key] = {
40
+ ...existing,
41
+ candles: mergedCandles,
42
+ oldestTime,
43
+ latestTime,
44
+ requestedRanges: mergedRanges,
45
+ noDataBefore
46
+ };
47
+ }
48
+ hasData(symbol, interval, startTime, endTime) {
49
+ const key = createKey(symbol, interval);
50
+ const tokenData = this.historicalPriceData[key];
51
+ if (!tokenData) return false;
52
+ if (tokenData.noDataBefore !== null && endTime <= tokenData.noDataBefore) {
53
+ return true;
54
+ }
55
+ for (const range of tokenData.requestedRanges) {
56
+ if (range.start <= startTime && range.end >= endTime) {
57
+ return true;
58
+ }
59
+ }
60
+ if (tokenData.oldestTime === null || tokenData.latestTime === null) {
61
+ return false;
62
+ }
63
+ const intervalMs = getIntervalSeconds(interval) * 1e3;
64
+ const hasStartCoverage = tokenData.oldestTime <= startTime;
65
+ const hasEndCoverage = tokenData.latestTime >= endTime || tokenData.latestTime + intervalMs >= endTime;
66
+ return hasStartCoverage && hasEndCoverage;
67
+ }
68
+ getData(symbol, interval, startTime, endTime) {
69
+ const key = createKey(symbol, interval);
70
+ const tokenData = this.historicalPriceData[key];
71
+ if (!tokenData) return [];
72
+ return tokenData.candles.filter((candle) => candle.t >= startTime && candle.t < endTime);
73
+ }
74
+ getEffectiveBoundary(symbols, interval) {
75
+ if (symbols.length === 0) return null;
76
+ let maxBoundary = null;
77
+ for (const symbol of symbols) {
78
+ const key = createKey(symbol, interval);
79
+ const tokenData = this.historicalPriceData[key];
80
+ if (!tokenData) continue;
81
+ const boundary = tokenData.noDataBefore ?? tokenData.oldestTime;
82
+ if (boundary !== null) {
83
+ if (maxBoundary === null || boundary > maxBoundary) {
84
+ maxBoundary = boundary;
85
+ }
86
+ }
87
+ }
88
+ return maxBoundary;
89
+ }
90
+ removeToken(symbol, interval) {
91
+ const key = createKey(symbol, interval);
92
+ delete this.historicalPriceData[key];
93
+ this.loadingTokens.delete(symbol);
94
+ }
95
+ clear() {
96
+ this.historicalPriceData = {};
97
+ this.loadingTokens = /* @__PURE__ */ new Set();
98
+ }
99
+ }
100
+
101
+ export { CandleCache };
@@ -0,0 +1,25 @@
1
+ import { ChartConfig, TokenSelection, CandleInterval, ChartType, Bar, RealtimeBarCallback } 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
+ declare class Chart {
9
+ private collector;
10
+ private baselinePrices;
11
+ constructor(config: ChartConfig);
12
+ setTokens(longTokens: TokenSelection[], shortTokens: TokenSelection[]): void;
13
+ setCandleInterval(interval: CandleInterval): void;
14
+ getBars(chartType: ChartType, startTime: number, endTime: number): Promise<Bar[]>;
15
+ getAssetBars(symbol: string, startTime: number, endTime: number): Promise<Bar[]>;
16
+ getEffectiveDataBoundary(): number | null;
17
+ subscribeRealtimeBars(chartType: ChartType, callback: RealtimeBarCallback): string;
18
+ subscribeRealtimeAssetBars(symbol: string, callback: RealtimeBarCallback): string;
19
+ unsubscribeRealtimeBars(id: string): void;
20
+ private assertTokenExists;
21
+ clearCache(): void;
22
+ destroy(): void;
23
+ }
24
+
25
+ export { Chart };
@@ -0,0 +1,141 @@
1
+ import { DataCollector } from './collector';
2
+ import { computePerformanceCandles, computePriceRatioCandles, computeWeightedRatioCandles, computeAssetBars, computeRealtimePerformanceBar, computeRealtimePriceRatioBar, computeRealtimeWeightedRatioBar, computeRealtimeAssetBar } from './compute/index';
3
+
4
+ class Chart {
5
+ collector;
6
+ baselinePrices = {};
7
+ constructor(config) {
8
+ this.collector = new DataCollector(config);
9
+ }
10
+ setTokens(longTokens, shortTokens) {
11
+ this.collector.setTokens(longTokens, shortTokens);
12
+ this.baselinePrices = {};
13
+ }
14
+ setCandleInterval(interval) {
15
+ this.collector.setCandleInterval(interval);
16
+ this.baselinePrices = {};
17
+ }
18
+ async getBars(chartType, startTime, endTime) {
19
+ const interval = this.collector.getCandleInterval();
20
+ const tokenCandles = await this.collector.fetchHistoricalPriceData(startTime, endTime, interval);
21
+ switch (chartType) {
22
+ case "weighted-ratio": {
23
+ return computeWeightedRatioCandles(
24
+ this.collector.getLongTokens(),
25
+ this.collector.getShortTokens(),
26
+ tokenCandles
27
+ );
28
+ }
29
+ case "price-ratio": {
30
+ return computePriceRatioCandles(this.collector.getLongTokens(), this.collector.getShortTokens(), tokenCandles);
31
+ }
32
+ case "performance": {
33
+ const result = computePerformanceCandles(
34
+ this.collector.getLongTokens(),
35
+ this.collector.getShortTokens(),
36
+ tokenCandles
37
+ );
38
+ if (result.bars.length > 0) {
39
+ this.baselinePrices = result.baselinePrices;
40
+ }
41
+ return result.bars;
42
+ }
43
+ default:
44
+ throw new Error(`Unsupported chart type: ${chartType}`);
45
+ }
46
+ }
47
+ async getAssetBars(symbol, startTime, endTime) {
48
+ this.assertTokenExists(symbol);
49
+ const interval = this.collector.getCandleInterval();
50
+ const tokenCandles = await this.collector.fetchHistoricalPriceData(startTime, endTime, interval);
51
+ return computeAssetBars(tokenCandles, symbol);
52
+ }
53
+ getEffectiveDataBoundary() {
54
+ return this.collector.getEffectiveDataBoundary(this.collector.getCandleInterval());
55
+ }
56
+ subscribeRealtimeBars(chartType, callback) {
57
+ if (!this.collector.isWsAttached) {
58
+ this.collector.attachCandleWs();
59
+ }
60
+ const formatTokens = (tokens) => tokens.map((t) => `${t.symbol}:${t.weight}`).join(",");
61
+ const id = `${chartType}-${formatTokens(this.collector.getLongTokens())}-${formatTokens(this.collector.getShortTokens())}`;
62
+ this.collector.addRealtimeListener(id, () => {
63
+ const snapshot = this.collector.getLatestCandles();
64
+ if (!snapshot) return;
65
+ let bar = null;
66
+ switch (chartType) {
67
+ case "weighted-ratio":
68
+ bar = computeRealtimeWeightedRatioBar(
69
+ this.collector.getLongTokens(),
70
+ this.collector.getShortTokens(),
71
+ snapshot
72
+ );
73
+ break;
74
+ case "price-ratio":
75
+ bar = computeRealtimePriceRatioBar(this.collector.getLongTokens(), this.collector.getShortTokens(), snapshot);
76
+ break;
77
+ case "performance":
78
+ if (Object.keys(this.baselinePrices).length === 0) break;
79
+ bar = computeRealtimePerformanceBar(
80
+ this.collector.getLongTokens(),
81
+ this.collector.getShortTokens(),
82
+ snapshot,
83
+ this.baselinePrices
84
+ );
85
+ break;
86
+ }
87
+ if (bar) {
88
+ try {
89
+ callback(bar);
90
+ } catch (error) {
91
+ console.warn("Error in realtime bar callback, skipping this update", {
92
+ chartType,
93
+ error
94
+ });
95
+ }
96
+ }
97
+ });
98
+ return id;
99
+ }
100
+ subscribeRealtimeAssetBars(symbol, callback) {
101
+ this.assertTokenExists(symbol);
102
+ if (!this.collector.isWsAttached) {
103
+ this.collector.attachCandleWs();
104
+ }
105
+ const id = `price-${symbol}`;
106
+ this.collector.addRealtimeListener(id, () => {
107
+ const snapshot = this.collector.getLatestCandles();
108
+ if (!snapshot) return;
109
+ const bar = computeRealtimeAssetBar(snapshot, symbol);
110
+ if (bar) {
111
+ try {
112
+ callback(bar);
113
+ } catch (error) {
114
+ console.warn("Error in realtime asset bar callback, skipping this update", {
115
+ symbol,
116
+ error
117
+ });
118
+ }
119
+ }
120
+ });
121
+ return id;
122
+ }
123
+ unsubscribeRealtimeBars(id) {
124
+ this.collector.removeRealtimeListener(id);
125
+ }
126
+ assertTokenExists(symbol) {
127
+ const allTokens = [...this.collector.getLongTokens(), ...this.collector.getShortTokens()];
128
+ if (!allTokens.some((t) => t.symbol === symbol)) {
129
+ throw new Error(`Symbol "${symbol}" is not part of the configured long or short tokens`);
130
+ }
131
+ }
132
+ clearCache() {
133
+ this.collector.clearCache();
134
+ this.baselinePrices = {};
135
+ }
136
+ destroy() {
137
+ this.collector.destroy();
138
+ }
139
+ }
140
+
141
+ export { Chart };
@@ -0,0 +1,10 @@
1
+ import { CandleInterval, CandleData } 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
+ declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
+
10
+ export { fetchHistoricalCandles };
@@ -0,0 +1,27 @@
1
+ const BINANCE_FUTURES_URL = "https://fapi.binance.com/fapi/v1/klines";
2
+ const MAX_LIMIT = 1e3;
3
+ function toCandleData(kline) {
4
+ return {
5
+ t: kline[0],
6
+ T: kline[6],
7
+ o: Number(kline[1]),
8
+ h: Number(kline[2]),
9
+ l: Number(kline[3]),
10
+ c: Number(kline[4])
11
+ };
12
+ }
13
+ async function fetchHistoricalCandles(symbol, startTime, endTime, interval) {
14
+ const params = new URLSearchParams({
15
+ symbol,
16
+ interval,
17
+ startTime: String(startTime),
18
+ endTime: String(endTime),
19
+ limit: String(MAX_LIMIT)
20
+ });
21
+ const response = await fetch(`${BINANCE_FUTURES_URL}?${params}`);
22
+ if (!response.ok) throw new Error(`Binance API error: ${response.status} ${response.statusText}`);
23
+ const data = await response.json();
24
+ return data.map(toCandleData);
25
+ }
26
+
27
+ export { fetchHistoricalCandles };
@@ -0,0 +1,10 @@
1
+ import { CandleInterval, CandleData } 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
+ declare function fetchHistoricalCandles(symbol: string, startTime: number, endTime: number, interval: CandleInterval): Promise<CandleData[]>;
9
+
10
+ export { fetchHistoricalCandles };
@@ -0,0 +1,39 @@
1
+ const BYBIT_URL = "https://api.bybit.com/v5/market/kline";
2
+ const MAX_LIMIT = 1e3;
3
+ const intervalMap = {
4
+ "1m": "1",
5
+ "3m": "3",
6
+ "5m": "5",
7
+ "15m": "15",
8
+ "30m": "30",
9
+ "1h": "60",
10
+ "2h": "120",
11
+ "4h": "240",
12
+ "8h": "480",
13
+ "12h": "720",
14
+ "1d": "D",
15
+ "3d": "D",
16
+ "1w": "W",
17
+ "1M": "M"
18
+ };
19
+ function toCandleData(item) {
20
+ const startMs = Number(item[0]);
21
+ return { t: startMs, T: startMs, o: Number(item[1]), h: Number(item[2]), l: Number(item[3]), c: Number(item[4]) };
22
+ }
23
+ async function fetchHistoricalCandles(symbol, startTime, endTime, interval) {
24
+ const params = new URLSearchParams({
25
+ category: "linear",
26
+ symbol,
27
+ interval: intervalMap[interval],
28
+ start: String(startTime),
29
+ end: String(endTime),
30
+ limit: String(MAX_LIMIT)
31
+ });
32
+ const response = await fetch(`${BYBIT_URL}?${params}`);
33
+ if (!response.ok) throw new Error(`Bybit API error: ${response.status} ${response.statusText}`);
34
+ const data = await response.json();
35
+ if (data.retCode !== 0) throw new Error(`Bybit API error: ${data.retMsg}`);
36
+ return data.result.list.reverse().map((item) => toCandleData(item));
37
+ }
38
+
39
+ export { fetchHistoricalCandles };