@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,28 @@
1
+ import { WsMessage, MessageListener } from '../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare abstract class BaseExchangeWs {
5
+ protected ws: WebSocket | null;
6
+ protected reconnectAttempts: number;
7
+ protected reconnectTimer: ReturnType<typeof setTimeout> | null;
8
+ protected pingTimer: ReturnType<typeof setInterval> | null;
9
+ protected manualClose: boolean;
10
+ private messageListeners;
11
+ private openListeners;
12
+ protected abstract getWsUrl(): string;
13
+ protected abstract getPingIntervalMs(): number;
14
+ protected abstract buildPingMessage(): WsMessage | string | null;
15
+ connect(): void;
16
+ disconnect(): void;
17
+ get connected(): boolean;
18
+ addMessageListener(listener: MessageListener): string;
19
+ removeMessageListener(id: string): void;
20
+ send(msg: WsMessage | string): void;
21
+ addOpenListener(listener: () => void): string;
22
+ removeOpenListener(id: string): void;
23
+ private startPing;
24
+ private stopPing;
25
+ private scheduleReconnect;
26
+ }
27
+
28
+ export { BaseExchangeWs };
@@ -0,0 +1,93 @@
1
+ class BaseExchangeWs {
2
+ ws = null;
3
+ reconnectAttempts = 0;
4
+ reconnectTimer = null;
5
+ pingTimer = null;
6
+ manualClose = false;
7
+ messageListeners = /* @__PURE__ */ new Map();
8
+ openListeners = /* @__PURE__ */ new Map();
9
+ connect() {
10
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
11
+ return;
12
+ }
13
+ this.manualClose = false;
14
+ this.ws = new WebSocket(this.getWsUrl());
15
+ this.ws.onopen = () => {
16
+ this.reconnectAttempts = 0;
17
+ this.startPing();
18
+ for (const listener of this.openListeners.values()) {
19
+ listener();
20
+ }
21
+ };
22
+ this.ws.onmessage = (event) => {
23
+ for (const listener of this.messageListeners.values()) {
24
+ listener(event.data);
25
+ }
26
+ };
27
+ this.ws.onerror = () => {
28
+ };
29
+ this.ws.onclose = () => {
30
+ this.stopPing();
31
+ if (!this.manualClose) {
32
+ this.scheduleReconnect();
33
+ }
34
+ };
35
+ }
36
+ disconnect() {
37
+ this.manualClose = true;
38
+ this.stopPing();
39
+ if (this.reconnectTimer) {
40
+ clearTimeout(this.reconnectTimer);
41
+ this.reconnectTimer = null;
42
+ }
43
+ if (this.ws) {
44
+ this.ws.close();
45
+ this.ws = null;
46
+ }
47
+ }
48
+ get connected() {
49
+ return this.ws?.readyState === WebSocket.OPEN;
50
+ }
51
+ addMessageListener(listener) {
52
+ const id = Math.random().toString(36).slice(2);
53
+ this.messageListeners.set(id, listener);
54
+ return id;
55
+ }
56
+ removeMessageListener(id) {
57
+ this.messageListeners.delete(id);
58
+ }
59
+ send(msg) {
60
+ if (this.ws?.readyState === WebSocket.OPEN) {
61
+ this.ws.send(typeof msg === "string" ? msg : JSON.stringify(msg));
62
+ }
63
+ }
64
+ addOpenListener(listener) {
65
+ const id = Math.random().toString(36).slice(2);
66
+ this.openListeners.set(id, listener);
67
+ return id;
68
+ }
69
+ removeOpenListener(id) {
70
+ this.openListeners.delete(id);
71
+ }
72
+ startPing() {
73
+ this.stopPing();
74
+ const intervalMs = this.getPingIntervalMs();
75
+ if (intervalMs <= 0) return;
76
+ const pingMsg = this.buildPingMessage();
77
+ if (!pingMsg) return;
78
+ this.pingTimer = setInterval(() => this.send(pingMsg), intervalMs);
79
+ }
80
+ stopPing() {
81
+ if (this.pingTimer) {
82
+ clearInterval(this.pingTimer);
83
+ this.pingTimer = null;
84
+ }
85
+ }
86
+ scheduleReconnect() {
87
+ this.reconnectAttempts += 1;
88
+ const delay = Math.min(1e3 * 2 ** (this.reconnectAttempts - 1), 3e4);
89
+ this.reconnectTimer = setTimeout(() => this.connect(), delay);
90
+ }
91
+ }
92
+
93
+ export { BaseExchangeWs };
@@ -0,0 +1,21 @@
1
+ import { CandleInterval, WsMessage, CandleData } from '../types.js';
2
+ import { BaseCandleWs } from './base-candle.js';
3
+ import { BaseExchangeWs } from './base-exchange.js';
4
+ import '@pear-protocol/types';
5
+
6
+ declare class BinanceExchangeWs extends BaseExchangeWs {
7
+ protected getWsUrl(): string;
8
+ protected getPingIntervalMs(): number;
9
+ protected buildPingMessage(): null;
10
+ }
11
+ declare class BinanceCandleWs extends BaseCandleWs {
12
+ private idCounter;
13
+ protected buildSubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
14
+ protected buildUnsubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
15
+ protected parseMessage(data: string): {
16
+ symbol: string;
17
+ candle: CandleData;
18
+ } | null;
19
+ }
20
+
21
+ export { BinanceCandleWs, BinanceExchangeWs };
@@ -0,0 +1,55 @@
1
+ import { BaseCandleWs } from './base-candle';
2
+ import { BaseExchangeWs } from './base-exchange';
3
+
4
+ const toStreamName = (symbol, interval) => `${symbol.toLowerCase()}@kline_${interval}`;
5
+ class BinanceExchangeWs extends BaseExchangeWs {
6
+ getWsUrl() {
7
+ return "wss://fstream.binance.com/ws";
8
+ }
9
+ getPingIntervalMs() {
10
+ return 0;
11
+ }
12
+ buildPingMessage() {
13
+ return null;
14
+ }
15
+ }
16
+ class BinanceCandleWs extends BaseCandleWs {
17
+ idCounter = 1;
18
+ buildSubscribeMessage(symbol, interval) {
19
+ return {
20
+ method: "SUBSCRIBE",
21
+ params: [toStreamName(symbol, interval)],
22
+ id: this.idCounter++
23
+ };
24
+ }
25
+ buildUnsubscribeMessage(symbol, interval) {
26
+ return {
27
+ method: "UNSUBSCRIBE",
28
+ params: [toStreamName(symbol, interval)],
29
+ id: this.idCounter++
30
+ };
31
+ }
32
+ parseMessage(data) {
33
+ let msg;
34
+ try {
35
+ msg = JSON.parse(data);
36
+ } catch {
37
+ return null;
38
+ }
39
+ if (msg.e !== "kline" || !msg.k) return null;
40
+ const k = msg.k;
41
+ return {
42
+ symbol: k.s,
43
+ candle: {
44
+ t: k.t,
45
+ T: k.T,
46
+ o: Number(k.o),
47
+ h: Number(k.h),
48
+ l: Number(k.l),
49
+ c: Number(k.c)
50
+ }
51
+ };
52
+ }
53
+ }
54
+
55
+ export { BinanceCandleWs, BinanceExchangeWs };
@@ -0,0 +1,20 @@
1
+ import { CandleInterval, WsMessage, CandleData } from '../types.js';
2
+ import { BaseCandleWs } from './base-candle.js';
3
+ import { BaseExchangeWs } from './base-exchange.js';
4
+ import '@pear-protocol/types';
5
+
6
+ declare class BybitExchangeWs extends BaseExchangeWs {
7
+ protected getWsUrl(): string;
8
+ protected getPingIntervalMs(): number;
9
+ protected buildPingMessage(): WsMessage;
10
+ }
11
+ declare class BybitCandleWs extends BaseCandleWs {
12
+ protected buildSubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
13
+ protected buildUnsubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
14
+ protected parseMessage(data: string): {
15
+ symbol: string;
16
+ candle: CandleData;
17
+ } | null;
18
+ }
19
+
20
+ export { BybitCandleWs, BybitExchangeWs };
@@ -0,0 +1,75 @@
1
+ import { BaseCandleWs } from './base-candle';
2
+ import { BaseExchangeWs } from './base-exchange';
3
+
4
+ const intervalMap = {
5
+ "1m": "1",
6
+ "3m": "3",
7
+ "5m": "5",
8
+ "15m": "15",
9
+ "30m": "30",
10
+ "1h": "60",
11
+ "2h": "120",
12
+ "4h": "240",
13
+ "8h": "480",
14
+ "12h": "720",
15
+ "1d": "D",
16
+ "3d": "D",
17
+ "1w": "W",
18
+ "1M": "M"
19
+ };
20
+ const toTopic = (symbol, interval) => `kline.${intervalMap[interval]}.${symbol}`;
21
+ const fromTopic = (topic) => {
22
+ const parts = topic.split(".");
23
+ return parts[2] ?? "";
24
+ };
25
+ class BybitExchangeWs extends BaseExchangeWs {
26
+ getWsUrl() {
27
+ return "wss://stream.bybit.com/v5/public/linear";
28
+ }
29
+ getPingIntervalMs() {
30
+ return 2e4;
31
+ }
32
+ buildPingMessage() {
33
+ return { op: "ping" };
34
+ }
35
+ }
36
+ class BybitCandleWs extends BaseCandleWs {
37
+ buildSubscribeMessage(symbol, interval) {
38
+ return {
39
+ op: "subscribe",
40
+ args: [toTopic(symbol, interval)]
41
+ };
42
+ }
43
+ buildUnsubscribeMessage(symbol, interval) {
44
+ return {
45
+ op: "unsubscribe",
46
+ args: [toTopic(symbol, interval)]
47
+ };
48
+ }
49
+ parseMessage(data) {
50
+ let msg;
51
+ try {
52
+ msg = JSON.parse(data);
53
+ } catch {
54
+ return null;
55
+ }
56
+ const topic = msg.topic;
57
+ const msgData = msg.data;
58
+ if (!topic || !msgData || !Array.isArray(msgData) || msgData.length === 0) return null;
59
+ if (!topic.startsWith("kline.")) return null;
60
+ const d = msgData[0];
61
+ return {
62
+ symbol: fromTopic(topic),
63
+ candle: {
64
+ t: d.start,
65
+ T: d.end,
66
+ o: Number(d.open),
67
+ h: Number(d.high),
68
+ l: Number(d.low),
69
+ c: Number(d.close)
70
+ }
71
+ };
72
+ }
73
+ }
74
+
75
+ export { BybitCandleWs, BybitExchangeWs };
@@ -0,0 +1,33 @@
1
+ import { CandleInterval, WsMessage, CandleData } from '../types.js';
2
+ import { BaseCandleWs } from './base-candle.js';
3
+ import { BaseExchangeWs } from './base-exchange.js';
4
+ import '@pear-protocol/types';
5
+
6
+ interface HyperliquidCandleSubscription {
7
+ type: 'candle';
8
+ coin: string;
9
+ interval: CandleInterval;
10
+ }
11
+ interface HyperliquidSubscribeMessage extends WsMessage {
12
+ method: 'subscribe';
13
+ subscription: HyperliquidCandleSubscription;
14
+ }
15
+ interface HyperliquidUnsubscribeMessage extends WsMessage {
16
+ method: 'unsubscribe';
17
+ subscription: HyperliquidCandleSubscription;
18
+ }
19
+ declare class HyperliquidExchangeWs extends BaseExchangeWs {
20
+ protected getWsUrl(): string;
21
+ protected getPingIntervalMs(): number;
22
+ protected buildPingMessage(): WsMessage;
23
+ }
24
+ declare class HyperliquidCandleWs extends BaseCandleWs {
25
+ protected buildSubscribeMessage(symbol: string, interval: CandleInterval): HyperliquidSubscribeMessage;
26
+ protected buildUnsubscribeMessage(symbol: string, interval: CandleInterval): HyperliquidUnsubscribeMessage;
27
+ protected parseMessage(data: string): {
28
+ symbol: string;
29
+ candle: CandleData;
30
+ } | null;
31
+ }
32
+
33
+ export { HyperliquidCandleWs, HyperliquidExchangeWs };
@@ -0,0 +1,52 @@
1
+ import { BaseCandleWs } from './base-candle';
2
+ import { BaseExchangeWs } from './base-exchange';
3
+
4
+ class HyperliquidExchangeWs extends BaseExchangeWs {
5
+ getWsUrl() {
6
+ return "wss://api.hyperliquid.xyz/ws";
7
+ }
8
+ getPingIntervalMs() {
9
+ return 3e4;
10
+ }
11
+ buildPingMessage() {
12
+ return { method: "ping" };
13
+ }
14
+ }
15
+ class HyperliquidCandleWs extends BaseCandleWs {
16
+ buildSubscribeMessage(symbol, interval) {
17
+ return {
18
+ method: "subscribe",
19
+ subscription: { type: "candle", coin: symbol, interval }
20
+ };
21
+ }
22
+ buildUnsubscribeMessage(symbol, interval) {
23
+ return {
24
+ method: "unsubscribe",
25
+ subscription: { type: "candle", coin: symbol, interval }
26
+ };
27
+ }
28
+ parseMessage(data) {
29
+ let msg;
30
+ try {
31
+ msg = JSON.parse(data);
32
+ } catch {
33
+ return null;
34
+ }
35
+ if (msg.channel !== "candle" || !msg.data) return null;
36
+ const d = msg.data;
37
+ return {
38
+ symbol: d.s,
39
+ candle: {
40
+ s: d.s,
41
+ t: d.t,
42
+ T: d.T,
43
+ o: Number(d.o),
44
+ h: Number(d.h),
45
+ l: Number(d.l),
46
+ c: Number(d.c)
47
+ }
48
+ };
49
+ }
50
+ }
51
+
52
+ export { HyperliquidCandleWs, HyperliquidExchangeWs };
@@ -0,0 +1,12 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { BaseCandleWs, CandleHandler } from './base-candle.js';
3
+ import { BaseExchangeWs } from './base-exchange.js';
4
+ import '../types.js';
5
+
6
+ interface CandleWsBundle {
7
+ exchangeWs: BaseExchangeWs;
8
+ candleWs: BaseCandleWs;
9
+ }
10
+ declare function createCandleWs(connector: Connector, onCandle: CandleHandler): CandleWsBundle;
11
+
12
+ export { BaseCandleWs, BaseExchangeWs, CandleHandler, type CandleWsBundle, createCandleWs };
@@ -0,0 +1,42 @@
1
+ export { BaseCandleWs } from './base-candle';
2
+ export { BaseExchangeWs } from './base-exchange';
3
+ import { BinanceExchangeWs, BinanceCandleWs } from './binance';
4
+ import { BybitExchangeWs, BybitCandleWs } from './bybit';
5
+ import { HyperliquidExchangeWs, HyperliquidCandleWs } from './hyperliquid';
6
+ import { OkxExchangeWs, OkxCandleWs } from './okx';
7
+
8
+ function createExchangeWs(connector) {
9
+ switch (connector) {
10
+ case "hyperliquid":
11
+ return new HyperliquidExchangeWs();
12
+ case "binanceusdm":
13
+ return new BinanceExchangeWs();
14
+ case "bybit":
15
+ return new BybitExchangeWs();
16
+ case "okx":
17
+ return new OkxExchangeWs();
18
+ default:
19
+ throw new Error(`Unsupported exchange: ${connector}`);
20
+ }
21
+ }
22
+ function createCandleWsForExchange(connector, exchangeWs, onCandle) {
23
+ switch (connector) {
24
+ case "hyperliquid":
25
+ return new HyperliquidCandleWs(exchangeWs, onCandle);
26
+ case "binanceusdm":
27
+ return new BinanceCandleWs(exchangeWs, onCandle);
28
+ case "bybit":
29
+ return new BybitCandleWs(exchangeWs, onCandle);
30
+ case "okx":
31
+ return new OkxCandleWs(exchangeWs, onCandle);
32
+ default:
33
+ throw new Error(`Unsupported exchange: ${connector}`);
34
+ }
35
+ }
36
+ function createCandleWs(connector, onCandle) {
37
+ const exchangeWs = createExchangeWs(connector);
38
+ const candleWs = createCandleWsForExchange(connector, exchangeWs, onCandle);
39
+ return { exchangeWs, candleWs };
40
+ }
41
+
42
+ export { createCandleWs };
@@ -0,0 +1,20 @@
1
+ import { CandleInterval, WsMessage, CandleData } from '../types.js';
2
+ import { BaseCandleWs } from './base-candle.js';
3
+ import { BaseExchangeWs } from './base-exchange.js';
4
+ import '@pear-protocol/types';
5
+
6
+ declare class OkxExchangeWs extends BaseExchangeWs {
7
+ protected getWsUrl(): string;
8
+ protected getPingIntervalMs(): number;
9
+ protected buildPingMessage(): string;
10
+ }
11
+ declare class OkxCandleWs extends BaseCandleWs {
12
+ protected buildSubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
13
+ protected buildUnsubscribeMessage(symbol: string, interval: CandleInterval): WsMessage;
14
+ protected parseMessage(data: string): {
15
+ symbol: string;
16
+ candle: CandleData;
17
+ } | null;
18
+ }
19
+
20
+ export { OkxCandleWs, OkxExchangeWs };
package/dist/ws/okx.js ADDED
@@ -0,0 +1,72 @@
1
+ import { BaseCandleWs } from './base-candle';
2
+ import { BaseExchangeWs } from './base-exchange';
3
+
4
+ const intervalMap = {
5
+ "1m": "1m",
6
+ "3m": "3m",
7
+ "5m": "5m",
8
+ "15m": "15m",
9
+ "30m": "30m",
10
+ "1h": "1H",
11
+ "2h": "2H",
12
+ "4h": "4H",
13
+ "8h": "8H",
14
+ "12h": "12H",
15
+ "1d": "1D",
16
+ "3d": "3D",
17
+ "1w": "1W",
18
+ "1M": "1M"
19
+ };
20
+ const toChannel = (interval) => `candle${intervalMap[interval]}`;
21
+ class OkxExchangeWs extends BaseExchangeWs {
22
+ getWsUrl() {
23
+ return "wss://ws.okx.com:8443/ws/v5/public";
24
+ }
25
+ getPingIntervalMs() {
26
+ return 25e3;
27
+ }
28
+ buildPingMessage() {
29
+ return "ping";
30
+ }
31
+ }
32
+ class OkxCandleWs extends BaseCandleWs {
33
+ buildSubscribeMessage(symbol, interval) {
34
+ return {
35
+ op: "subscribe",
36
+ args: [{ channel: toChannel(interval), instId: symbol }]
37
+ };
38
+ }
39
+ buildUnsubscribeMessage(symbol, interval) {
40
+ return {
41
+ op: "unsubscribe",
42
+ args: [{ channel: toChannel(interval), instId: symbol }]
43
+ };
44
+ }
45
+ parseMessage(data) {
46
+ if (data === "pong") return null;
47
+ let msg;
48
+ try {
49
+ msg = JSON.parse(data);
50
+ } catch {
51
+ return null;
52
+ }
53
+ const arg = msg.arg;
54
+ const candleData = msg.data;
55
+ if (!arg || !candleData || !Array.isArray(candleData) || candleData.length === 0) return null;
56
+ if (!arg.channel.startsWith("candle")) return null;
57
+ const d = candleData[0];
58
+ return {
59
+ symbol: arg.instId,
60
+ candle: {
61
+ t: Number(d[0]),
62
+ T: Number(d[0]),
63
+ o: Number(d[1]),
64
+ h: Number(d[2]),
65
+ l: Number(d[3]),
66
+ c: Number(d[4])
67
+ }
68
+ };
69
+ }
70
+ }
71
+
72
+ export { OkxCandleWs, OkxExchangeWs };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@pear-protocol/chart-sdk",
3
+ "version": "0.0.1",
4
+ "description": "Pear Protocol Chart SDK",
5
+ "private": false,
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "clean": "rm -rf dist .turbo",
25
+ "typecheck": "tsc --noEmit"
26
+ },
27
+ "dependencies": {
28
+ "@pear-protocol/types": "0.0.3"
29
+ },
30
+ "devDependencies": {
31
+ "@backend/typescript-config": "workspace:*",
32
+ "tsup": "8.5.1",
33
+ "typescript": "5.9.3"
34
+ }
35
+ }