@pear-protocol/chart-sdk 0.0.1

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 (47) hide show
  1. package/dist/cache/index.d.ts +17 -0
  2. package/dist/cache/index.js +101 -0
  3. package/dist/chart.d.ts +21 -0
  4. package/dist/chart.js +119 -0
  5. package/dist/collector/binance.d.ts +6 -0
  6. package/dist/collector/binance.js +27 -0
  7. package/dist/collector/bybit.d.ts +6 -0
  8. package/dist/collector/bybit.js +39 -0
  9. package/dist/collector/helpers.d.ts +6 -0
  10. package/dist/collector/helpers.js +21 -0
  11. package/dist/collector/hyperliquid.d.ts +6 -0
  12. package/dist/collector/hyperliquid.js +15 -0
  13. package/dist/collector/index.d.ts +39 -0
  14. package/dist/collector/index.js +211 -0
  15. package/dist/collector/okx.d.ts +6 -0
  16. package/dist/collector/okx.js +38 -0
  17. package/dist/compute/asset.d.ts +6 -0
  18. package/dist/compute/asset.js +13 -0
  19. package/dist/compute/index.d.ts +6 -0
  20. package/dist/compute/index.js +4 -0
  21. package/dist/compute/performance.d.ts +7 -0
  22. package/dist/compute/performance.js +81 -0
  23. package/dist/compute/price-ratio.d.ts +7 -0
  24. package/dist/compute/price-ratio.js +107 -0
  25. package/dist/compute/weighted-ratio.d.ts +7 -0
  26. package/dist/compute/weighted-ratio.js +105 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +1 -0
  29. package/dist/types.d.ts +60 -0
  30. package/dist/types.js +1 -0
  31. package/dist/utils.d.ts +10 -0
  32. package/dist/utils.js +91 -0
  33. package/dist/ws/base-candle.d.ts +26 -0
  34. package/dist/ws/base-candle.js +71 -0
  35. package/dist/ws/base-exchange.d.ts +28 -0
  36. package/dist/ws/base-exchange.js +93 -0
  37. package/dist/ws/binance.d.ts +21 -0
  38. package/dist/ws/binance.js +55 -0
  39. package/dist/ws/bybit.d.ts +20 -0
  40. package/dist/ws/bybit.js +75 -0
  41. package/dist/ws/hyperliquid.d.ts +33 -0
  42. package/dist/ws/hyperliquid.js +52 -0
  43. package/dist/ws/index.d.ts +12 -0
  44. package/dist/ws/index.js +42 -0
  45. package/dist/ws/okx.d.ts +20 -0
  46. package/dist/ws/okx.js +72 -0
  47. package/package.json +35 -0
@@ -0,0 +1,38 @@
1
+ const OKX_URL = "https://www.okx.com/api/v5/market/candles";
2
+ const MAX_LIMIT = 300;
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
+ 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(instId, startTime, endTime, interval) {
24
+ const params = new URLSearchParams({
25
+ instId,
26
+ bar: intervalMap[interval],
27
+ before: String(startTime),
28
+ after: String(endTime),
29
+ limit: String(MAX_LIMIT)
30
+ });
31
+ const response = await fetch(`${OKX_URL}?${params}`);
32
+ if (!response.ok) throw new Error(`OKX API error: ${response.status} ${response.statusText}`);
33
+ const data = await response.json();
34
+ if (data.code !== "0") throw new Error(`OKX API error: ${data.msg}`);
35
+ return data.data.reverse().map(toCandleData);
36
+ }
37
+
38
+ export { fetchHistoricalCandles };
@@ -0,0 +1,6 @@
1
+ import { CandleData, Bar } from '../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare function computeAssetBars(tokenCandles: Record<string, CandleData[]>, symbol: string): Bar[];
5
+
6
+ export { computeAssetBars };
@@ -0,0 +1,13 @@
1
+ function computeAssetBars(tokenCandles, symbol) {
2
+ const candles = tokenCandles[symbol];
3
+ if (!candles) return [];
4
+ return candles.map((c) => ({
5
+ time: c.t,
6
+ open: c.o,
7
+ high: c.h,
8
+ low: c.l,
9
+ close: c.c
10
+ }));
11
+ }
12
+
13
+ export { computeAssetBars };
@@ -0,0 +1,6 @@
1
+ export { computeAssetBars } from './asset.js';
2
+ export { computePerformanceCandles, computeRealtimePerformanceBar } from './performance.js';
3
+ export { computePriceRatioCandles, computeRealtimePriceRatioBar } from './price-ratio.js';
4
+ export { computeRealtimeWeightedRatioBar, computeWeightedRatioCandles } from './weighted-ratio.js';
5
+ import '../types.js';
6
+ import '@pear-protocol/types';
@@ -0,0 +1,4 @@
1
+ export { computeAssetBars } from './asset';
2
+ export { computePerformanceCandles, computeRealtimePerformanceBar } from './performance';
3
+ export { computePriceRatioCandles, computeRealtimePriceRatioBar } from './price-ratio';
4
+ export { computeRealtimeWeightedRatioBar, computeWeightedRatioCandles } from './weighted-ratio';
@@ -0,0 +1,7 @@
1
+ import { TokenSelection, CandleData, PerformanceResult, Bar } from '../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare function computePerformanceCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): PerformanceResult;
5
+ declare function computeRealtimePerformanceBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>, baselinePrices: Record<string, number>): Bar | null;
6
+
7
+ export { computePerformanceCandles, computeRealtimePerformanceBar };
@@ -0,0 +1,81 @@
1
+ import { createCandleLookups, getCompleteTimestamps } from '../utils';
2
+
3
+ function computePerformanceCandles(longTokens, shortTokens, tokenCandles) {
4
+ const empty = { bars: [], baselinePrices: {} };
5
+ const allTokens = [...longTokens, ...shortTokens];
6
+ if (allTokens.length === 0) return empty;
7
+ const candleLookups = createCandleLookups(tokenCandles);
8
+ const allSymbols = allTokens.map((t) => t.symbol);
9
+ const completeTimestamps = getCompleteTimestamps(candleLookups, allSymbols);
10
+ if (completeTimestamps.length === 0) return empty;
11
+ const baselinePrices = {};
12
+ const firstTimestamp = completeTimestamps[0];
13
+ for (const token of allTokens) {
14
+ const candle = candleLookups[token.symbol]?.get(firstTimestamp);
15
+ if (!candle || candle.o <= 0) return empty;
16
+ baselinePrices[token.symbol] = candle.o;
17
+ }
18
+ const initialPortfolioValue = 1e3;
19
+ const longSet = new Set(longTokens.map((t) => t.symbol));
20
+ const bars = [];
21
+ for (const timestamp of completeTimestamps) {
22
+ let portfolioValue = 0;
23
+ let missing = false;
24
+ for (const token of allTokens) {
25
+ const candle = candleLookups[token.symbol]?.get(timestamp);
26
+ if (!candle) {
27
+ missing = true;
28
+ break;
29
+ }
30
+ const baselinePrice = baselinePrices[token.symbol];
31
+ const allocation = token.weight / 100 * initialPortfolioValue;
32
+ const priceRatio = candle.c / baselinePrice;
33
+ if (longSet.has(token.symbol)) {
34
+ portfolioValue += allocation * priceRatio;
35
+ } else {
36
+ portfolioValue += allocation / priceRatio;
37
+ }
38
+ }
39
+ if (missing) continue;
40
+ bars.push({
41
+ time: timestamp,
42
+ open: portfolioValue,
43
+ high: portfolioValue,
44
+ low: portfolioValue,
45
+ close: portfolioValue
46
+ });
47
+ }
48
+ return { bars, baselinePrices };
49
+ }
50
+ function computeRealtimePerformanceBar(longTokens, shortTokens, snapshot, baselinePrices) {
51
+ const allTokens = [...longTokens, ...shortTokens];
52
+ if (allTokens.length === 0) return null;
53
+ const initialPortfolioValue = 1e3;
54
+ const longSet = new Set(longTokens.map((t) => t.symbol));
55
+ let portfolioValue = 0;
56
+ let time = null;
57
+ for (const token of allTokens) {
58
+ const c = snapshot[token.symbol];
59
+ if (!c) return null;
60
+ const baselinePrice = baselinePrices[token.symbol];
61
+ if (!baselinePrice || baselinePrice <= 0) return null;
62
+ const allocation = token.weight / 100 * initialPortfolioValue;
63
+ const priceRatio = c.c / baselinePrice;
64
+ if (longSet.has(token.symbol)) {
65
+ portfolioValue += allocation * priceRatio;
66
+ } else {
67
+ portfolioValue += allocation / priceRatio;
68
+ }
69
+ if (time === null) time = c.t;
70
+ }
71
+ if (time === null) return null;
72
+ return {
73
+ time,
74
+ open: portfolioValue,
75
+ high: portfolioValue,
76
+ low: portfolioValue,
77
+ close: portfolioValue
78
+ };
79
+ }
80
+
81
+ export { computePerformanceCandles, computeRealtimePerformanceBar };
@@ -0,0 +1,7 @@
1
+ import { TokenSelection, CandleData, Bar } from '../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare function computePriceRatioCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): Bar[];
5
+ declare function computeRealtimePriceRatioBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>): Bar | null;
6
+
7
+ export { computePriceRatioCandles, computeRealtimePriceRatioBar };
@@ -0,0 +1,107 @@
1
+ import { createCandleLookups, getCompleteTimestamps } from '../utils';
2
+
3
+ function computePriceRatioCandles(longTokens, shortTokens, tokenCandles) {
4
+ if (longTokens.length === 0 && shortTokens.length === 0) return [];
5
+ const candleLookups = createCandleLookups(tokenCandles);
6
+ const allSymbols = [...longTokens, ...shortTokens].map((t) => t.symbol);
7
+ const completeTimestamps = getCompleteTimestamps(candleLookups, allSymbols);
8
+ const bars = [];
9
+ for (const timestamp of completeTimestamps) {
10
+ let longSumO = 0;
11
+ let longSumH = 0;
12
+ let longSumL = 0;
13
+ let longSumC = 0;
14
+ let shortSumO = 0;
15
+ let shortSumH = 0;
16
+ let shortSumL = 0;
17
+ let shortSumC = 0;
18
+ let missing = false;
19
+ for (const token of longTokens) {
20
+ const candle = candleLookups[token.symbol]?.get(timestamp);
21
+ if (!candle) {
22
+ missing = true;
23
+ break;
24
+ }
25
+ if (!(candle.o > 0 && candle.h > 0 && candle.l > 0 && candle.c > 0)) {
26
+ missing = true;
27
+ break;
28
+ }
29
+ const w = token.weight / 100;
30
+ longSumO += candle.o * w;
31
+ longSumH += candle.h * w;
32
+ longSumL += candle.l * w;
33
+ longSumC += candle.c * w;
34
+ }
35
+ if (missing) continue;
36
+ for (const token of shortTokens) {
37
+ const candle = candleLookups[token.symbol]?.get(timestamp);
38
+ if (!candle) {
39
+ missing = true;
40
+ break;
41
+ }
42
+ if (!(candle.o > 0 && candle.h > 0 && candle.l > 0 && candle.c > 0)) {
43
+ missing = true;
44
+ break;
45
+ }
46
+ const w = token.weight / 100;
47
+ shortSumO += candle.o * w;
48
+ shortSumH += candle.h * w;
49
+ shortSumL += candle.l * w;
50
+ shortSumC += candle.c * w;
51
+ }
52
+ if (missing) continue;
53
+ if (shortSumO <= 0 || shortSumH <= 0 || shortSumL <= 0 || shortSumC <= 0) continue;
54
+ bars.push({
55
+ time: timestamp,
56
+ open: longSumO / shortSumO,
57
+ high: longSumH / shortSumH,
58
+ low: longSumL / shortSumL,
59
+ close: longSumC / shortSumC
60
+ });
61
+ }
62
+ return bars;
63
+ }
64
+ function computeRealtimePriceRatioBar(longTokens, shortTokens, snapshot) {
65
+ let longSumO = 0;
66
+ let longSumH = 0;
67
+ let longSumL = 0;
68
+ let longSumC = 0;
69
+ let shortSumO = 0;
70
+ let shortSumH = 0;
71
+ let shortSumL = 0;
72
+ let shortSumC = 0;
73
+ let time = null;
74
+ for (const token of longTokens) {
75
+ const c = snapshot[token.symbol];
76
+ if (!c) return null;
77
+ if (!(c.o > 0 && c.h > 0 && c.l > 0 && c.c > 0)) return null;
78
+ const w = token.weight / 100;
79
+ longSumO += c.o * w;
80
+ longSumH += c.h * w;
81
+ longSumL += c.l * w;
82
+ longSumC += c.c * w;
83
+ if (time === null) time = c.t;
84
+ }
85
+ for (const token of shortTokens) {
86
+ const c = snapshot[token.symbol];
87
+ if (!c) return null;
88
+ if (!(c.o > 0 && c.h > 0 && c.l > 0 && c.c > 0)) return null;
89
+ const w = token.weight / 100;
90
+ shortSumO += c.o * w;
91
+ shortSumH += c.h * w;
92
+ shortSumL += c.l * w;
93
+ shortSumC += c.c * w;
94
+ if (time === null) time = c.t;
95
+ }
96
+ if (time === null) return null;
97
+ if (shortSumO <= 0 || shortSumH <= 0 || shortSumL <= 0 || shortSumC <= 0) return null;
98
+ return {
99
+ time,
100
+ open: longSumO / shortSumO,
101
+ high: longSumH / shortSumH,
102
+ low: longSumL / shortSumL,
103
+ close: longSumC / shortSumC
104
+ };
105
+ }
106
+
107
+ export { computePriceRatioCandles, computeRealtimePriceRatioBar };
@@ -0,0 +1,7 @@
1
+ import { TokenSelection, CandleData, Bar } from '../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare function computeWeightedRatioCandles(longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>): Bar[];
5
+ declare function computeRealtimeWeightedRatioBar(longTokens: TokenSelection[], shortTokens: TokenSelection[], snapshot: Record<string, CandleData>): Bar | null;
6
+
7
+ export { computeRealtimeWeightedRatioBar, computeWeightedRatioCandles };
@@ -0,0 +1,105 @@
1
+ import { createCandleLookups, getCompleteTimestamps } from '../utils';
2
+
3
+ function computeWeightedRatioCandles(longTokens, shortTokens, tokenCandles) {
4
+ if (longTokens.length === 0 && shortTokens.length === 0) return [];
5
+ const candleLookups = createCandleLookups(tokenCandles);
6
+ const allSymbols = [...longTokens, ...shortTokens].map((t) => t.symbol);
7
+ const completeTimestamps = getCompleteTimestamps(candleLookups, allSymbols);
8
+ const bars = [];
9
+ for (const timestamp of completeTimestamps) {
10
+ let longOpen = 1;
11
+ let longHigh = 1;
12
+ let longLow = 1;
13
+ let longClose = 1;
14
+ let shortOpen = 1;
15
+ let shortHigh = 1;
16
+ let shortLow = 1;
17
+ let shortClose = 1;
18
+ let missing = false;
19
+ for (const token of longTokens) {
20
+ const candle = candleLookups[token.symbol]?.get(timestamp);
21
+ if (!candle) {
22
+ missing = true;
23
+ break;
24
+ }
25
+ const w = token.weight / 100;
26
+ if (!(candle.o > 0 && candle.h > 0 && candle.l > 0 && candle.c > 0)) {
27
+ missing = true;
28
+ break;
29
+ }
30
+ longOpen *= candle.o ** w;
31
+ longHigh *= candle.h ** w;
32
+ longLow *= candle.l ** w;
33
+ longClose *= candle.c ** w;
34
+ }
35
+ if (missing) continue;
36
+ for (const token of shortTokens) {
37
+ const candle = candleLookups[token.symbol]?.get(timestamp);
38
+ if (!candle) {
39
+ missing = true;
40
+ break;
41
+ }
42
+ const w = -(token.weight / 100);
43
+ if (!(candle.o > 0 && candle.h > 0 && candle.l > 0 && candle.c > 0)) {
44
+ missing = true;
45
+ break;
46
+ }
47
+ shortOpen *= candle.o ** w;
48
+ shortHigh *= candle.h ** w;
49
+ shortLow *= candle.l ** w;
50
+ shortClose *= candle.c ** w;
51
+ }
52
+ if (missing) continue;
53
+ bars.push({
54
+ time: timestamp,
55
+ open: longOpen * shortOpen,
56
+ high: longHigh * shortHigh,
57
+ low: longLow * shortLow,
58
+ close: longClose * shortClose
59
+ });
60
+ }
61
+ return bars;
62
+ }
63
+ function computeRealtimeWeightedRatioBar(longTokens, shortTokens, snapshot) {
64
+ let longOpen = 1;
65
+ let longHigh = 1;
66
+ let longLow = 1;
67
+ let longClose = 1;
68
+ let shortOpen = 1;
69
+ let shortHigh = 1;
70
+ let shortLow = 1;
71
+ let shortClose = 1;
72
+ let time = null;
73
+ for (const token of longTokens) {
74
+ const c = snapshot[token.symbol];
75
+ if (!c) return null;
76
+ if (!(c.o > 0 && c.h > 0 && c.l > 0 && c.c > 0)) return null;
77
+ const w = token.weight / 100;
78
+ longOpen *= c.o ** w;
79
+ longHigh *= c.h ** w;
80
+ longLow *= c.l ** w;
81
+ longClose *= c.c ** w;
82
+ if (time === null) time = c.t;
83
+ }
84
+ for (const token of shortTokens) {
85
+ const c = snapshot[token.symbol];
86
+ if (!c) return null;
87
+ if (!(c.o > 0 && c.h > 0 && c.l > 0 && c.c > 0)) return null;
88
+ const w = -(token.weight / 100);
89
+ shortOpen *= c.o ** w;
90
+ shortHigh *= c.h ** w;
91
+ shortLow *= c.l ** w;
92
+ shortClose *= c.c ** w;
93
+ if (time === null) time = c.t;
94
+ }
95
+ if (time === null) return null;
96
+ return {
97
+ time,
98
+ open: longOpen * shortOpen,
99
+ high: longHigh * shortHigh,
100
+ low: longLow * shortLow,
101
+ close: longClose * shortClose
102
+ };
103
+ }
104
+
105
+ export { computeRealtimeWeightedRatioBar, computeWeightedRatioCandles };
@@ -0,0 +1,3 @@
1
+ export { Chart } from './chart.js';
2
+ export { Bar, CandleInterval, ChartType, TokenSelection } from './types.js';
3
+ import '@pear-protocol/types';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { Chart } from './chart';
@@ -0,0 +1,60 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+
3
+ interface TokenSelection {
4
+ symbol: string;
5
+ weight: number;
6
+ }
7
+ type CandleInterval = '1m' | '3m' | '5m' | '15m' | '30m' | '1h' | '2h' | '4h' | '8h' | '12h' | '1d' | '3d' | '1w' | '1M';
8
+ interface CandleData {
9
+ s?: string;
10
+ t: number;
11
+ T: number;
12
+ o: number;
13
+ c: number;
14
+ h: number;
15
+ l: number;
16
+ }
17
+ interface HistoricalRange {
18
+ start: number;
19
+ end: number;
20
+ }
21
+ interface TokenHistoricalPriceData {
22
+ symbol: string;
23
+ interval: CandleInterval;
24
+ candles: CandleData[];
25
+ oldestTime: number | null;
26
+ latestTime: number | null;
27
+ requestedRanges: HistoricalRange[];
28
+ noDataBefore: number | null;
29
+ }
30
+ interface ChartConfig {
31
+ longTokens?: TokenSelection[];
32
+ shortTokens?: TokenSelection[];
33
+ candleInterval?: CandleInterval;
34
+ connector?: Connector;
35
+ }
36
+ interface RealtimeSubscription {
37
+ chartType: ChartType;
38
+ callback: RealtimeBarCallback;
39
+ rawListenerId: string;
40
+ }
41
+ type RealtimeCandleCallback = (symbol: string, candle: CandleData) => void;
42
+ type ChartType = 'weighted-ratio' | 'price-ratio' | 'performance';
43
+ interface Bar {
44
+ time: number;
45
+ open: number;
46
+ high: number;
47
+ low: number;
48
+ close: number;
49
+ }
50
+ type RealtimeBarCallback = (bar: Bar) => void;
51
+ interface PerformanceResult {
52
+ bars: Bar[];
53
+ baselinePrices: Record<string, number>;
54
+ }
55
+ interface WsMessage {
56
+ [key: string]: unknown;
57
+ }
58
+ type MessageListener = (data: string) => void;
59
+
60
+ export type { Bar, CandleData, CandleInterval, ChartConfig, ChartType, HistoricalRange, MessageListener, PerformanceResult, RealtimeBarCallback, RealtimeCandleCallback, RealtimeSubscription, TokenHistoricalPriceData, TokenSelection, WsMessage };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,10 @@
1
+ import { CandleData, CandleInterval, HistoricalRange } from './types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare const createKey: (symbol: string, interval: CandleInterval) => string;
5
+ declare const getIntervalSeconds: (interval: CandleInterval) => number;
6
+ declare const mergeRanges: (ranges: HistoricalRange[], newRange: HistoricalRange) => HistoricalRange[];
7
+ declare const createCandleLookups: (tokenCandles: Record<string, CandleData[]>) => Record<string, Map<number, CandleData>>;
8
+ declare const getCompleteTimestamps: (candleLookups: Record<string, Map<number, CandleData>>, requiredSymbols: string[]) => number[];
9
+
10
+ export { createCandleLookups, createKey, getCompleteTimestamps, getIntervalSeconds, mergeRanges };
package/dist/utils.js ADDED
@@ -0,0 +1,91 @@
1
+ const createKey = (symbol, interval) => `${symbol}-${interval}`;
2
+ const getIntervalSeconds = (interval) => {
3
+ switch (interval) {
4
+ case "1m":
5
+ return 60;
6
+ case "3m":
7
+ return 3 * 60;
8
+ case "5m":
9
+ return 5 * 60;
10
+ case "15m":
11
+ return 15 * 60;
12
+ case "30m":
13
+ return 30 * 60;
14
+ case "1h":
15
+ return 60 * 60;
16
+ case "2h":
17
+ return 2 * 60 * 60;
18
+ case "4h":
19
+ return 4 * 60 * 60;
20
+ case "8h":
21
+ return 8 * 60 * 60;
22
+ case "12h":
23
+ return 12 * 60 * 60;
24
+ case "1d":
25
+ return 24 * 60 * 60;
26
+ case "3d":
27
+ return 3 * 24 * 60 * 60;
28
+ case "1w":
29
+ return 7 * 24 * 60 * 60;
30
+ case "1M":
31
+ return 30 * 24 * 60 * 60;
32
+ }
33
+ };
34
+ const mergeRanges = (ranges, newRange) => {
35
+ const all = [...ranges, newRange].sort((a, b) => a.start - b.start);
36
+ const merged = [];
37
+ for (const r of all) {
38
+ const last = merged[merged.length - 1];
39
+ if (merged.length === 0 || !last || r.start > last.end) {
40
+ merged.push({ start: r.start, end: r.end });
41
+ } else {
42
+ last.end = Math.max(last.end, r.end);
43
+ }
44
+ }
45
+ return merged;
46
+ };
47
+ const createCandleLookups = (tokenCandles) => {
48
+ const lookups = {};
49
+ for (const [symbol, candles] of Object.entries(tokenCandles)) {
50
+ const lookup = /* @__PURE__ */ new Map();
51
+ for (const candle of candles) {
52
+ lookup.set(candle.t, candle);
53
+ }
54
+ lookups[symbol] = lookup;
55
+ }
56
+ return lookups;
57
+ };
58
+ const getCompleteTimestamps = (candleLookups, requiredSymbols) => {
59
+ if (requiredSymbols.length === 0) return [];
60
+ let baseSymbol;
61
+ let baseLookup;
62
+ let baseSize = Infinity;
63
+ for (const symbol of requiredSymbols) {
64
+ const lookup = candleLookups[symbol];
65
+ const size = lookup?.size ?? 0;
66
+ if (size === 0) return [];
67
+ if (size < baseSize) {
68
+ baseSize = size;
69
+ baseSymbol = symbol;
70
+ baseLookup = lookup;
71
+ }
72
+ }
73
+ if (!baseLookup || !baseSymbol) return [];
74
+ const result = [];
75
+ for (const timestamp of Array.from(baseLookup.keys())) {
76
+ let ok = true;
77
+ for (const symbol of requiredSymbols) {
78
+ if (symbol === baseSymbol) continue;
79
+ const lookup = candleLookups[symbol];
80
+ if (!lookup || !lookup.has(timestamp)) {
81
+ ok = false;
82
+ break;
83
+ }
84
+ }
85
+ if (ok) result.push(timestamp);
86
+ }
87
+ result.sort((a, b) => a - b);
88
+ return result;
89
+ };
90
+
91
+ export { createCandleLookups, createKey, getCompleteTimestamps, getIntervalSeconds, mergeRanges };
@@ -0,0 +1,26 @@
1
+ import { CandleInterval, WsMessage, CandleData } from '../types.js';
2
+ import { BaseExchangeWs } from './base-exchange.js';
3
+ import '@pear-protocol/types';
4
+
5
+ type CandleHandler = (symbol: string, candle: CandleData) => void;
6
+ declare abstract class BaseCandleWs {
7
+ protected exchangeWs: BaseExchangeWs;
8
+ private onCandle;
9
+ private messageListenerId;
10
+ private openListenerId;
11
+ private subscribedKeys;
12
+ protected abstract buildSubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
13
+ protected abstract buildUnsubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
14
+ protected abstract parseMessage(data: string): {
15
+ symbol: string;
16
+ candle: CandleData;
17
+ } | null;
18
+ constructor(exchangeWs: BaseExchangeWs, onCandle: CandleHandler);
19
+ start(): void;
20
+ stop(): void;
21
+ get connected(): boolean;
22
+ subscribe(symbol: string, interval: CandleInterval): void;
23
+ unsubscribe(symbol: string, interval: CandleInterval): void;
24
+ }
25
+
26
+ export { BaseCandleWs, type CandleHandler };
@@ -0,0 +1,71 @@
1
+ class BaseCandleWs {
2
+ exchangeWs;
3
+ onCandle;
4
+ messageListenerId = null;
5
+ openListenerId = null;
6
+ subscribedKeys = /* @__PURE__ */ new Set();
7
+ constructor(exchangeWs, onCandle) {
8
+ this.exchangeWs = exchangeWs;
9
+ this.onCandle = onCandle;
10
+ }
11
+ start() {
12
+ if (this.messageListenerId) return;
13
+ this.messageListenerId = this.exchangeWs.addMessageListener((data) => {
14
+ const result = this.parseMessage(data);
15
+ if (result) {
16
+ this.onCandle(result.symbol, result.candle);
17
+ }
18
+ });
19
+ this.openListenerId = this.exchangeWs.addOpenListener(() => {
20
+ for (const key of this.subscribedKeys) {
21
+ const [symbol, interval] = key.split("::");
22
+ this.exchangeWs.send(this.buildSubscribeMessage(symbol, interval));
23
+ }
24
+ });
25
+ if (!this.exchangeWs.connected) {
26
+ this.exchangeWs.connect();
27
+ }
28
+ if (this.exchangeWs.connected) {
29
+ for (const key of this.subscribedKeys) {
30
+ const [symbol, interval] = key.split("::");
31
+ this.exchangeWs.send(this.buildSubscribeMessage(symbol, interval));
32
+ }
33
+ }
34
+ }
35
+ stop() {
36
+ if (this.messageListenerId) {
37
+ this.exchangeWs.removeMessageListener(this.messageListenerId);
38
+ this.messageListenerId = null;
39
+ }
40
+ if (this.openListenerId) {
41
+ this.exchangeWs.removeOpenListener(this.openListenerId);
42
+ this.openListenerId = null;
43
+ }
44
+ for (const key of this.subscribedKeys) {
45
+ const [symbol, interval] = key.split("::");
46
+ this.exchangeWs.send(this.buildUnsubscribeMessage(symbol, interval));
47
+ }
48
+ this.subscribedKeys.clear();
49
+ }
50
+ get connected() {
51
+ return this.exchangeWs.connected;
52
+ }
53
+ subscribe(symbol, interval) {
54
+ const key = `${symbol}::${interval}`;
55
+ if (this.subscribedKeys.has(key)) return;
56
+ this.subscribedKeys.add(key);
57
+ if (this.exchangeWs.connected) {
58
+ this.exchangeWs.send(this.buildSubscribeMessage(symbol, interval));
59
+ }
60
+ }
61
+ unsubscribe(symbol, interval) {
62
+ const key = `${symbol}::${interval}`;
63
+ if (!this.subscribedKeys.has(key)) return;
64
+ this.subscribedKeys.delete(key);
65
+ if (this.exchangeWs.connected) {
66
+ this.exchangeWs.send(this.buildUnsubscribeMessage(symbol, interval));
67
+ }
68
+ }
69
+ }
70
+
71
+ export { BaseCandleWs };