@pear-protocol/hyperliquid-sdk 0.0.19 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/useBasketCandles.d.ts +13 -2
- package/dist/hooks/useHistoricalPriceData.d.ts +4 -4
- package/dist/hyperliquid-websocket.d.ts +1 -3
- package/dist/index.d.ts +29 -9
- package/dist/index.js +362 -198
- package/dist/store/userSelection.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils/chart-interval-mappers.d.ts +9 -0
- package/package.json +5 -2
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import type { WeightedCandleData } from '../types';
|
|
1
|
+
import type { CandleInterval, WeightedCandleData } from '../types';
|
|
2
2
|
export interface UseBasketCandlesReturn {
|
|
3
|
-
fetchBasketCandles: (startTime: number, endTime: number) => Promise<WeightedCandleData[]>;
|
|
3
|
+
fetchBasketCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<WeightedCandleData[]>;
|
|
4
4
|
isLoading: boolean;
|
|
5
|
+
addRealtimeListener: (cb: RealtimeBarsCallback) => string;
|
|
6
|
+
removeRealtimeListener: (id: string) => void;
|
|
5
7
|
}
|
|
8
|
+
export type RealtimeBar = {
|
|
9
|
+
time: number;
|
|
10
|
+
open: number;
|
|
11
|
+
high: number;
|
|
12
|
+
low: number;
|
|
13
|
+
close: number;
|
|
14
|
+
volume?: number;
|
|
15
|
+
};
|
|
16
|
+
export type RealtimeBarsCallback = (bar: RealtimeBar) => void;
|
|
6
17
|
/**
|
|
7
18
|
* Composes historical price fetching with basket candle computation.
|
|
8
19
|
* - Listens to `longTokens` and `shortTokens` from user selection.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { CandleData } from '../types';
|
|
1
|
+
import type { CandleData, CandleInterval } from '../types';
|
|
2
2
|
export interface UseHistoricalPriceDataReturn {
|
|
3
|
-
fetchHistoricalPriceData: (startTime: number, endTime: number, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
|
|
4
|
-
hasHistoricalPriceData: (startTime: number, endTime: number) => boolean;
|
|
5
|
-
getHistoricalPriceData: (startTime: number, endTime: number) => Record<string, CandleData[]>;
|
|
3
|
+
fetchHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
|
|
4
|
+
hasHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => boolean;
|
|
5
|
+
getHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => Record<string, CandleData[]>;
|
|
6
6
|
isLoading: (symbol?: string) => boolean;
|
|
7
7
|
clearCache: () => void;
|
|
8
8
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { ReadyState } from 'react-use-websocket';
|
|
2
|
-
import type { CandleInterval } from './types';
|
|
3
2
|
export interface UseHyperliquidNativeWebSocketProps {
|
|
4
3
|
address: string | null;
|
|
5
4
|
tokens?: string[];
|
|
6
|
-
candleInterval?: CandleInterval;
|
|
7
5
|
}
|
|
8
6
|
export interface UseHyperliquidNativeWebSocketReturn {
|
|
9
7
|
connectionStatus: ReadyState;
|
|
10
8
|
isConnected: boolean;
|
|
11
9
|
lastError: string | null;
|
|
12
10
|
}
|
|
13
|
-
export declare const useHyperliquidNativeWebSocket: ({ address,
|
|
11
|
+
export declare const useHyperliquidNativeWebSocket: ({ address, }: UseHyperliquidNativeWebSocketProps) => UseHyperliquidNativeWebSocketReturn;
|
package/dist/index.d.ts
CHANGED
|
@@ -630,7 +630,7 @@ interface CandleData {
|
|
|
630
630
|
* Candle chart data organized by symbol only
|
|
631
631
|
* Since new candles always have latest timestamp, no need to store per interval
|
|
632
632
|
*/
|
|
633
|
-
type CandleChartData =
|
|
633
|
+
type CandleChartData = Map<string, CandleData>;
|
|
634
634
|
/**
|
|
635
635
|
* Historical candle data request
|
|
636
636
|
*/
|
|
@@ -840,6 +840,7 @@ interface UserSelectionState {
|
|
|
840
840
|
openConflictModal: boolean;
|
|
841
841
|
conflicts: TokenConflict[];
|
|
842
842
|
candleInterval: CandleInterval;
|
|
843
|
+
isWeightBalanced: boolean;
|
|
843
844
|
setLongTokens: (tokens: TokenSelection[]) => void;
|
|
844
845
|
setShortTokens: (tokens: TokenSelection[]) => void;
|
|
845
846
|
setOpenTokenSelector: (open: boolean) => void;
|
|
@@ -883,18 +884,29 @@ interface UseTokenSelectionMetadataReturn {
|
|
|
883
884
|
declare const useTokenSelectionMetadata: () => UseTokenSelectionMetadataReturn;
|
|
884
885
|
|
|
885
886
|
interface UseHistoricalPriceDataReturn {
|
|
886
|
-
fetchHistoricalPriceData: (startTime: number, endTime: number, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
|
|
887
|
-
hasHistoricalPriceData: (startTime: number, endTime: number) => boolean;
|
|
888
|
-
getHistoricalPriceData: (startTime: number, endTime: number) => Record<string, CandleData[]>;
|
|
887
|
+
fetchHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
|
|
888
|
+
hasHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => boolean;
|
|
889
|
+
getHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => Record<string, CandleData[]>;
|
|
889
890
|
isLoading: (symbol?: string) => boolean;
|
|
890
891
|
clearCache: () => void;
|
|
891
892
|
}
|
|
892
893
|
declare const useHistoricalPriceData: () => UseHistoricalPriceDataReturn;
|
|
893
894
|
|
|
894
895
|
interface UseBasketCandlesReturn {
|
|
895
|
-
fetchBasketCandles: (startTime: number, endTime: number) => Promise<WeightedCandleData[]>;
|
|
896
|
+
fetchBasketCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<WeightedCandleData[]>;
|
|
896
897
|
isLoading: boolean;
|
|
898
|
+
addRealtimeListener: (cb: RealtimeBarsCallback) => string;
|
|
899
|
+
removeRealtimeListener: (id: string) => void;
|
|
897
900
|
}
|
|
901
|
+
type RealtimeBar = {
|
|
902
|
+
time: number;
|
|
903
|
+
open: number;
|
|
904
|
+
high: number;
|
|
905
|
+
low: number;
|
|
906
|
+
close: number;
|
|
907
|
+
volume?: number;
|
|
908
|
+
};
|
|
909
|
+
type RealtimeBarsCallback = (bar: RealtimeBar) => void;
|
|
898
910
|
/**
|
|
899
911
|
* Composes historical price fetching with basket candle computation.
|
|
900
912
|
* - Listens to `longTokens` and `shortTokens` from user selection.
|
|
@@ -916,14 +928,13 @@ declare const useHyperliquidWebSocket: ({ wsUrl, address }: UseHyperliquidWebSoc
|
|
|
916
928
|
interface UseHyperliquidNativeWebSocketProps {
|
|
917
929
|
address: string | null;
|
|
918
930
|
tokens?: string[];
|
|
919
|
-
candleInterval?: CandleInterval;
|
|
920
931
|
}
|
|
921
932
|
interface UseHyperliquidNativeWebSocketReturn {
|
|
922
933
|
connectionStatus: ReadyState;
|
|
923
934
|
isConnected: boolean;
|
|
924
935
|
lastError: string | null;
|
|
925
936
|
}
|
|
926
|
-
declare const useHyperliquidNativeWebSocket: ({ address,
|
|
937
|
+
declare const useHyperliquidNativeWebSocket: ({ address, }: UseHyperliquidNativeWebSocketProps) => UseHyperliquidNativeWebSocketReturn;
|
|
927
938
|
|
|
928
939
|
/**
|
|
929
940
|
* Account summary calculation utility class
|
|
@@ -1018,6 +1029,15 @@ declare const getCompleteTimestamps: (candleLookups: Record<string, Map<number,
|
|
|
1018
1029
|
*/
|
|
1019
1030
|
declare const computeBasketCandles: (longTokens: TokenSelection[], shortTokens: TokenSelection[], tokenCandles: Record<string, CandleData[]>) => WeightedCandleData[];
|
|
1020
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Maps TradingView ResolutionString to CandleInterval
|
|
1034
|
+
*/
|
|
1035
|
+
declare function mapTradingViewIntervalToCandleInterval(interval: string): CandleInterval;
|
|
1036
|
+
/**
|
|
1037
|
+
* Maps CandleInterval to TradingView ResolutionString
|
|
1038
|
+
*/
|
|
1039
|
+
declare function mapCandleIntervalToTradingViewInterval(interval: CandleInterval): string;
|
|
1040
|
+
|
|
1021
1041
|
interface HistoricalRange {
|
|
1022
1042
|
start: number;
|
|
1023
1043
|
end: number;
|
|
@@ -1031,5 +1051,5 @@ interface TokenHistoricalPriceData {
|
|
|
1031
1051
|
}
|
|
1032
1052
|
declare const useHistoricalPriceDataStore: any;
|
|
1033
1053
|
|
|
1034
|
-
export { AccountSummaryCalculator, ConflictDetector, PearHyperliquidClient, PearHyperliquidProvider, PearMigrationSDK, TokenMetadataExtractor, calculateWeightedRatio, computeBasketCandles, createCandleLookups, PearHyperliquidClient as default, getCompleteTimestamps, useAccountSummary, useAddress, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMigrationSDK, useOpenOrders, useOpenPositions, usePearHyperliquidClient, useTokenSelectionMetadata, useTradeHistories, useUserSelection, useWebData };
|
|
1035
|
-
export type { AccountSummaryResponseDto, AgentWalletDto, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetPosition, BalanceSummaryDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ClearinghouseState, CrossMarginSummaryDto, CumFundingDto, HLWebSocketResponse, HistoricalRange, MarginSummaryDto, MigrationHookState, MigrationHooks, OpenLimitOrderDto, OpenOrderV1Dto, OpenPositionDto, OpenPositionV1Dto, OrderAssetDto, OrderStatus, PearHyperliquidConfig, PnlDto, PositionAssetDetailDto, PositionSideDto, PositionSyncStatus, RawValueDto, SyncOpenOrderDto, SyncOpenOrderResponseDto, SyncOpenPositionDto, SyncOpenPositionResponseDto, SyncTradeHistoryDto, SyncTradeHistoryResponseDto, TokenConflict, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TpSlDto, TradeHistoryAssetDataDto, TradeHistoryDataDto, TradeHistoryV1Dto, UniverseAsset, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseTokenSelectionMetadataReturn, UserSelectionState, WebData2Response, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WeightedCandleData, WsAllMidsData };
|
|
1054
|
+
export { AccountSummaryCalculator, ConflictDetector, PearHyperliquidClient, PearHyperliquidProvider, PearMigrationSDK, TokenMetadataExtractor, calculateWeightedRatio, computeBasketCandles, createCandleLookups, PearHyperliquidClient as default, getCompleteTimestamps, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, useAccountSummary, useAddress, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMigrationSDK, useOpenOrders, useOpenPositions, usePearHyperliquidClient, useTokenSelectionMetadata, useTradeHistories, useUserSelection, useWebData };
|
|
1055
|
+
export type { AccountSummaryResponseDto, AgentWalletDto, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetMarketData, AssetPosition, BalanceSummaryDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ClearinghouseState, CrossMarginSummaryDto, CumFundingDto, HLWebSocketResponse, HistoricalRange, MarginSummaryDto, MigrationHookState, MigrationHooks, OpenLimitOrderDto, OpenOrderV1Dto, OpenPositionDto, OpenPositionV1Dto, OrderAssetDto, OrderStatus, PearHyperliquidConfig, PnlDto, PositionAssetDetailDto, PositionSideDto, PositionSyncStatus, RawValueDto, RealtimeBar, RealtimeBarsCallback, SyncOpenOrderDto, SyncOpenOrderResponseDto, SyncOpenPositionDto, SyncOpenPositionResponseDto, SyncTradeHistoryDto, SyncTradeHistoryResponseDto, TokenConflict, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TpSlDto, TradeHistoryAssetDataDto, TradeHistoryDataDto, TradeHistoryV1Dto, UniverseAsset, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseTokenSelectionMetadataReturn, UserSelectionState, WebData2Response, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WeightedCandleData, WsAllMidsData };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import require$$0, { useState, useEffect, useRef, createContext,
|
|
1
|
+
import require$$0, { useState, useEffect, useMemo, useRef, createContext, useContext, useCallback } from 'react';
|
|
2
2
|
import require$$1 from 'react-dom';
|
|
3
3
|
import { create } from 'zustand';
|
|
4
4
|
|
|
@@ -6653,21 +6653,19 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
6653
6653
|
},
|
|
6654
6654
|
addCandleData(symbol, candle) {
|
|
6655
6655
|
set((state) => {
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
};
|
|
6656
|
+
var _a;
|
|
6657
|
+
const map = (_a = state.candleData) !== null && _a !== void 0 ? _a : new Map();
|
|
6658
|
+
const updatedMap = new Map(map);
|
|
6659
|
+
updatedMap.set(symbol, candle); // store latest only
|
|
6660
|
+
return { candleData: updatedMap };
|
|
6662
6661
|
});
|
|
6663
6662
|
},
|
|
6664
6663
|
deleteCandleSymbol(symbol) {
|
|
6665
6664
|
set((state) => {
|
|
6666
|
-
if (!state.candleData
|
|
6667
|
-
return state;
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
delete updated[symbol];
|
|
6665
|
+
if (!state.candleData)
|
|
6666
|
+
return state;
|
|
6667
|
+
const updated = new Map(state.candleData);
|
|
6668
|
+
updated.delete(symbol);
|
|
6671
6669
|
return { candleData: updated };
|
|
6672
6670
|
});
|
|
6673
6671
|
},
|
|
@@ -6680,13 +6678,141 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
6680
6678
|
}))
|
|
6681
6679
|
}));
|
|
6682
6680
|
|
|
6683
|
-
const
|
|
6681
|
+
const DEFAULT_STATE = {
|
|
6682
|
+
longTokens: [
|
|
6683
|
+
{ symbol: "HYPE", weight: 25 },
|
|
6684
|
+
{ symbol: "BTC", weight: 25 },
|
|
6685
|
+
],
|
|
6686
|
+
shortTokens: [
|
|
6687
|
+
{ symbol: "AVAX", weight: 10 },
|
|
6688
|
+
{ symbol: "SEI", weight: 10 },
|
|
6689
|
+
{ symbol: "ADA", weight: 10 },
|
|
6690
|
+
{ symbol: "TRUMP", weight: 10 },
|
|
6691
|
+
{ symbol: "SUI", weight: 10 },
|
|
6692
|
+
],
|
|
6693
|
+
openTokenSelector: false,
|
|
6694
|
+
selectorConfig: null,
|
|
6695
|
+
openConflictModal: false,
|
|
6696
|
+
conflicts: [],
|
|
6697
|
+
candleInterval: "1h",
|
|
6698
|
+
isWeightBalanced: true,
|
|
6699
|
+
};
|
|
6700
|
+
const useUserSelection$1 = create((set, get) => ({
|
|
6701
|
+
...DEFAULT_STATE,
|
|
6702
|
+
setLongTokens: (tokens) => set((state) => ({ ...state, longTokens: tokens })),
|
|
6703
|
+
setShortTokens: (tokens) => set((state) => ({ ...state, shortTokens: tokens })),
|
|
6704
|
+
setOpenTokenSelector: (open) => set((state) => ({ ...state, openTokenSelector: open })),
|
|
6705
|
+
setSelectorConfig: (config) => set((state) => ({ ...state, selectorConfig: config })),
|
|
6706
|
+
setOpenConflictModal: (open) => set((state) => ({ ...state, openConflictModal: open })),
|
|
6707
|
+
setConflicts: (conflicts) => set((state) => ({ ...state, conflicts })),
|
|
6708
|
+
setCandleInterval: (interval) => set((state) => ({ ...state, candleInterval: interval })),
|
|
6709
|
+
updateTokenWeight: (isLong, index, newWeight) => {
|
|
6710
|
+
set((prev) => {
|
|
6711
|
+
if (isLong) {
|
|
6712
|
+
const updated = [...prev.longTokens];
|
|
6713
|
+
updated[index] = { ...updated[index], weight: newWeight };
|
|
6714
|
+
const longTotal = updated.reduce((sum, t) => sum + t.weight, 0);
|
|
6715
|
+
const shortTotal = prev.shortTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
6716
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6717
|
+
return { ...prev, longTokens: updated, isWeightBalanced };
|
|
6718
|
+
}
|
|
6719
|
+
else {
|
|
6720
|
+
const updated = [...prev.shortTokens];
|
|
6721
|
+
updated[index] = { ...updated[index], weight: newWeight };
|
|
6722
|
+
const longTotal = prev.longTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
6723
|
+
const shortTotal = updated.reduce((sum, t) => sum + t.weight, 0);
|
|
6724
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6725
|
+
return { ...prev, shortTokens: updated, isWeightBalanced };
|
|
6726
|
+
}
|
|
6727
|
+
});
|
|
6728
|
+
},
|
|
6729
|
+
addToken: (isLong) => {
|
|
6730
|
+
const currentTokens = isLong ? get().longTokens : get().shortTokens;
|
|
6731
|
+
const newIndex = currentTokens.length;
|
|
6732
|
+
set((prev) => ({
|
|
6733
|
+
...prev,
|
|
6734
|
+
selectorConfig: { isLong, index: newIndex },
|
|
6735
|
+
openTokenSelector: true,
|
|
6736
|
+
}));
|
|
6737
|
+
},
|
|
6738
|
+
removeToken: (isLong, index) => {
|
|
6739
|
+
set((prev) => {
|
|
6740
|
+
if (isLong) {
|
|
6741
|
+
const updated = prev.longTokens.filter((_, i) => i !== index);
|
|
6742
|
+
const longTotal = updated.reduce((sum, t) => sum + t.weight, 0);
|
|
6743
|
+
const shortTotal = prev.shortTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
6744
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6745
|
+
return { ...prev, longTokens: updated, isWeightBalanced };
|
|
6746
|
+
}
|
|
6747
|
+
else {
|
|
6748
|
+
const updated = prev.shortTokens.filter((_, i) => i !== index);
|
|
6749
|
+
const longTotal = prev.longTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
6750
|
+
const shortTotal = updated.reduce((sum, t) => sum + t.weight, 0);
|
|
6751
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6752
|
+
return { ...prev, shortTokens: updated, isWeightBalanced };
|
|
6753
|
+
}
|
|
6754
|
+
});
|
|
6755
|
+
},
|
|
6756
|
+
handleTokenSelect: (selectedToken) => {
|
|
6757
|
+
const { selectorConfig, longTokens, shortTokens } = get();
|
|
6758
|
+
if (!selectorConfig)
|
|
6759
|
+
return;
|
|
6760
|
+
const { isLong, index } = selectorConfig;
|
|
6761
|
+
const existingTokens = isLong ? longTokens : shortTokens;
|
|
6762
|
+
if (existingTokens.some((t) => t.symbol === selectedToken))
|
|
6763
|
+
return;
|
|
6764
|
+
set((prev) => {
|
|
6765
|
+
if (index >= existingTokens.length) {
|
|
6766
|
+
const newToken = { symbol: selectedToken, weight: 1 };
|
|
6767
|
+
if (isLong) {
|
|
6768
|
+
const updatedLongTokens = [...prev.longTokens, newToken];
|
|
6769
|
+
const longTotal = updatedLongTokens.reduce((s, t) => s + t.weight, 0);
|
|
6770
|
+
const shortTotal = prev.shortTokens.reduce((s, t) => s + t.weight, 0);
|
|
6771
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6772
|
+
return {
|
|
6773
|
+
...prev,
|
|
6774
|
+
longTokens: updatedLongTokens,
|
|
6775
|
+
isWeightBalanced,
|
|
6776
|
+
};
|
|
6777
|
+
}
|
|
6778
|
+
const updatedShortTokens = [...prev.shortTokens, newToken];
|
|
6779
|
+
const longTotal = prev.longTokens.reduce((s, t) => s + t.weight, 0);
|
|
6780
|
+
const shortTotal = updatedShortTokens.reduce((s, t) => s + t.weight, 0);
|
|
6781
|
+
const isWeightBalanced = longTotal + shortTotal === 100;
|
|
6782
|
+
return {
|
|
6783
|
+
...prev,
|
|
6784
|
+
shortTokens: updatedShortTokens,
|
|
6785
|
+
isWeightBalanced,
|
|
6786
|
+
};
|
|
6787
|
+
}
|
|
6788
|
+
else {
|
|
6789
|
+
if (isLong) {
|
|
6790
|
+
const updated = [...prev.longTokens];
|
|
6791
|
+
updated[index] = { ...updated[index], symbol: selectedToken };
|
|
6792
|
+
return { ...prev, longTokens: updated };
|
|
6793
|
+
}
|
|
6794
|
+
else {
|
|
6795
|
+
const updated = [...prev.shortTokens];
|
|
6796
|
+
updated[index] = { ...updated[index], symbol: selectedToken };
|
|
6797
|
+
return { ...prev, shortTokens: updated };
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
});
|
|
6801
|
+
},
|
|
6802
|
+
resetToDefaults: () => set((prev) => ({ ...prev, ...DEFAULT_STATE })),
|
|
6803
|
+
}));
|
|
6804
|
+
|
|
6805
|
+
const useHyperliquidNativeWebSocket = ({ address, }) => {
|
|
6684
6806
|
const { setWebData2, setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData } = useHyperliquidData();
|
|
6807
|
+
const { candleInterval } = useUserSelection$1();
|
|
6808
|
+
const longTokens = useUserSelection$1((s) => s.longTokens);
|
|
6809
|
+
const shortTokens = useUserSelection$1((s) => s.shortTokens);
|
|
6810
|
+
const selectedTokenSymbols = useMemo(() => ([...longTokens, ...shortTokens].map((t) => t.symbol)), [longTokens, shortTokens]);
|
|
6685
6811
|
const [lastError, setLastError] = useState(null);
|
|
6686
6812
|
const [subscribedAddress, setSubscribedAddress] = useState(null);
|
|
6687
6813
|
const [subscribedTokens, setSubscribedTokens] = useState([]);
|
|
6688
6814
|
const [subscribedCandleTokens, setSubscribedCandleTokens] = useState([]);
|
|
6689
|
-
const
|
|
6815
|
+
const prevCandleIntervalRef = useRef(null);
|
|
6690
6816
|
const pingIntervalRef = useRef(null);
|
|
6691
6817
|
const { readyState, sendJsonMessage } = useWebSocket('wss://api.hyperliquid.xyz/ws', {
|
|
6692
6818
|
shouldReconnect: () => true,
|
|
@@ -6726,7 +6852,7 @@ const useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval =
|
|
|
6726
6852
|
break;
|
|
6727
6853
|
case 'candle':
|
|
6728
6854
|
const candleDataItem = response.data;
|
|
6729
|
-
addCandleData(candleDataItem.
|
|
6855
|
+
addCandleData(candleDataItem.s, candleDataItem);
|
|
6730
6856
|
break;
|
|
6731
6857
|
default:
|
|
6732
6858
|
console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
|
|
@@ -6808,8 +6934,9 @@ const useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval =
|
|
|
6808
6934
|
useEffect(() => {
|
|
6809
6935
|
if (!isConnected || !address)
|
|
6810
6936
|
return;
|
|
6811
|
-
const
|
|
6812
|
-
const
|
|
6937
|
+
const effectiveTokens = selectedTokenSymbols;
|
|
6938
|
+
const tokensToSubscribe = effectiveTokens.filter((token) => token && !subscribedTokens.includes(token));
|
|
6939
|
+
const tokensToUnsubscribe = subscribedTokens.filter((token) => !effectiveTokens.includes(token));
|
|
6813
6940
|
// Unsubscribe from tokens no longer in the list
|
|
6814
6941
|
tokensToUnsubscribe.forEach(token => {
|
|
6815
6942
|
const unsubscribeMessage = {
|
|
@@ -6835,34 +6962,36 @@ const useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval =
|
|
|
6835
6962
|
sendJsonMessage(subscribeMessage);
|
|
6836
6963
|
});
|
|
6837
6964
|
if (tokensToSubscribe.length > 0 || tokensToUnsubscribe.length > 0) {
|
|
6838
|
-
setSubscribedTokens(
|
|
6839
|
-
tokensToSubscribe.forEach(token => deleteActiveAssetData(token));
|
|
6965
|
+
setSubscribedTokens(effectiveTokens.filter((token) => token));
|
|
6966
|
+
tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
|
|
6840
6967
|
}
|
|
6841
|
-
}, [isConnected, address,
|
|
6968
|
+
}, [isConnected, address, selectedTokenSymbols, subscribedTokens, sendJsonMessage, setActiveAssetData]);
|
|
6842
6969
|
// Handle candle subscriptions for tokens and interval changes
|
|
6843
6970
|
useEffect(() => {
|
|
6844
6971
|
if (!isConnected)
|
|
6845
6972
|
return;
|
|
6973
|
+
const effectiveTokens = selectedTokenSymbols;
|
|
6846
6974
|
// Unsubscribe from previous candle subscriptions if interval changed
|
|
6847
|
-
|
|
6975
|
+
const prevInterval = prevCandleIntervalRef.current;
|
|
6976
|
+
if (prevInterval && prevInterval !== candleInterval) {
|
|
6848
6977
|
subscribedCandleTokens.forEach(token => {
|
|
6849
6978
|
const unsubscribeMessage = {
|
|
6850
6979
|
method: 'unsubscribe',
|
|
6851
6980
|
subscription: {
|
|
6852
6981
|
type: 'candle',
|
|
6853
6982
|
coin: token,
|
|
6854
|
-
interval:
|
|
6983
|
+
interval: prevInterval,
|
|
6855
6984
|
},
|
|
6856
6985
|
};
|
|
6857
6986
|
sendJsonMessage(unsubscribeMessage);
|
|
6858
6987
|
});
|
|
6859
|
-
setCandleData(
|
|
6988
|
+
setCandleData(new Map());
|
|
6860
6989
|
setSubscribedCandleTokens([]);
|
|
6861
6990
|
}
|
|
6862
|
-
const tokensToSubscribe =
|
|
6863
|
-
const tokensToUnsubscribe = subscribedCandleTokens.filter(token => !
|
|
6991
|
+
const tokensToSubscribe = effectiveTokens.filter((token) => token && !subscribedCandleTokens.includes(token));
|
|
6992
|
+
const tokensToUnsubscribe = subscribedCandleTokens.filter((token) => !effectiveTokens.includes(token));
|
|
6864
6993
|
// Unsubscribe from tokens no longer in the list
|
|
6865
|
-
tokensToUnsubscribe.forEach(token => {
|
|
6994
|
+
tokensToUnsubscribe.forEach((token) => {
|
|
6866
6995
|
const unsubscribeMessage = {
|
|
6867
6996
|
method: 'unsubscribe',
|
|
6868
6997
|
subscription: {
|
|
@@ -6874,7 +7003,7 @@ const useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval =
|
|
|
6874
7003
|
sendJsonMessage(unsubscribeMessage);
|
|
6875
7004
|
});
|
|
6876
7005
|
// Subscribe to new tokens
|
|
6877
|
-
tokensToSubscribe.forEach(token => {
|
|
7006
|
+
tokensToSubscribe.forEach((token) => {
|
|
6878
7007
|
const subscribeMessage = {
|
|
6879
7008
|
method: 'subscribe',
|
|
6880
7009
|
subscription: {
|
|
@@ -6886,12 +7015,12 @@ const useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval =
|
|
|
6886
7015
|
sendJsonMessage(subscribeMessage);
|
|
6887
7016
|
});
|
|
6888
7017
|
// Update subscribed state
|
|
6889
|
-
if (tokensToSubscribe.length > 0 || tokensToUnsubscribe.length > 0 ||
|
|
6890
|
-
setSubscribedCandleTokens(
|
|
6891
|
-
|
|
6892
|
-
tokensToUnsubscribe.forEach(token => deleteCandleSymbol(token));
|
|
7018
|
+
if (tokensToSubscribe.length > 0 || tokensToUnsubscribe.length > 0 || prevInterval !== candleInterval) {
|
|
7019
|
+
setSubscribedCandleTokens(effectiveTokens.filter((token) => token));
|
|
7020
|
+
prevCandleIntervalRef.current = candleInterval;
|
|
7021
|
+
tokensToUnsubscribe.forEach((token) => deleteCandleSymbol(token));
|
|
6893
7022
|
}
|
|
6894
|
-
}, [isConnected,
|
|
7023
|
+
}, [isConnected, selectedTokenSymbols, candleInterval, subscribedCandleTokens, sendJsonMessage, setCandleData]);
|
|
6895
7024
|
return {
|
|
6896
7025
|
connectionStatus: readyState,
|
|
6897
7026
|
isConnected,
|
|
@@ -7400,138 +7529,6 @@ const useAccountSummary = () => {
|
|
|
7400
7529
|
return { data: calculated, isLoading };
|
|
7401
7530
|
};
|
|
7402
7531
|
|
|
7403
|
-
const DEFAULT_STATE = {
|
|
7404
|
-
longTokens: [
|
|
7405
|
-
{ symbol: "HYPE", weight: 25 },
|
|
7406
|
-
{ symbol: "BTC", weight: 25 },
|
|
7407
|
-
],
|
|
7408
|
-
shortTokens: [
|
|
7409
|
-
{ symbol: "AVAX", weight: 10 },
|
|
7410
|
-
{ symbol: "SEI", weight: 10 },
|
|
7411
|
-
{ symbol: "ADA", weight: 10 },
|
|
7412
|
-
{ symbol: "TRUMP", weight: 10 },
|
|
7413
|
-
{ symbol: "SUI", weight: 10 },
|
|
7414
|
-
],
|
|
7415
|
-
openTokenSelector: false,
|
|
7416
|
-
selectorConfig: null,
|
|
7417
|
-
openConflictModal: false,
|
|
7418
|
-
conflicts: [],
|
|
7419
|
-
candleInterval: "1h",
|
|
7420
|
-
};
|
|
7421
|
-
const useUserSelection$1 = create((set, get) => ({
|
|
7422
|
-
...DEFAULT_STATE,
|
|
7423
|
-
setLongTokens: (tokens) => set((state) => ({ ...state, longTokens: tokens })),
|
|
7424
|
-
setShortTokens: (tokens) => set((state) => ({ ...state, shortTokens: tokens })),
|
|
7425
|
-
setOpenTokenSelector: (open) => set((state) => ({ ...state, openTokenSelector: open })),
|
|
7426
|
-
setSelectorConfig: (config) => set((state) => ({ ...state, selectorConfig: config })),
|
|
7427
|
-
setOpenConflictModal: (open) => set((state) => ({ ...state, openConflictModal: open })),
|
|
7428
|
-
setConflicts: (conflicts) => set((state) => ({ ...state, conflicts })),
|
|
7429
|
-
setCandleInterval: (interval) => set((state) => ({ ...state, candleInterval: interval })),
|
|
7430
|
-
updateTokenWeight: (isLong, index, newWeight) => {
|
|
7431
|
-
const clampedWeight = Math.max(1, Math.min(100, newWeight));
|
|
7432
|
-
set((prev) => {
|
|
7433
|
-
var _a, _b;
|
|
7434
|
-
const currentLongTotal = prev.longTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
7435
|
-
const currentShortTotal = prev.shortTokens.reduce((sum, t) => sum + t.weight, 0);
|
|
7436
|
-
if (isLong) {
|
|
7437
|
-
const oldWeight = ((_a = prev.longTokens[index]) === null || _a === void 0 ? void 0 : _a.weight) || 0;
|
|
7438
|
-
const weightDiff = clampedWeight - oldWeight;
|
|
7439
|
-
const newLongTotal = currentLongTotal + weightDiff;
|
|
7440
|
-
if (newLongTotal + currentShortTotal > 100) {
|
|
7441
|
-
const maxAllowedWeight = Math.max(1, 100 - currentShortTotal - (currentLongTotal - oldWeight));
|
|
7442
|
-
const updated = [...prev.longTokens];
|
|
7443
|
-
updated[index] = { ...updated[index], weight: maxAllowedWeight };
|
|
7444
|
-
return { ...prev, longTokens: updated };
|
|
7445
|
-
}
|
|
7446
|
-
else {
|
|
7447
|
-
const updated = [...prev.longTokens];
|
|
7448
|
-
updated[index] = { ...updated[index], weight: clampedWeight };
|
|
7449
|
-
return { ...prev, longTokens: updated };
|
|
7450
|
-
}
|
|
7451
|
-
}
|
|
7452
|
-
else {
|
|
7453
|
-
const oldWeight = ((_b = prev.shortTokens[index]) === null || _b === void 0 ? void 0 : _b.weight) || 0;
|
|
7454
|
-
const weightDiff = clampedWeight - oldWeight;
|
|
7455
|
-
const newShortTotal = currentShortTotal + weightDiff;
|
|
7456
|
-
if (currentLongTotal + newShortTotal > 100) {
|
|
7457
|
-
const maxAllowedWeight = Math.max(1, 100 - currentLongTotal - (currentShortTotal - oldWeight));
|
|
7458
|
-
const updated = [...prev.shortTokens];
|
|
7459
|
-
updated[index] = { ...updated[index], weight: maxAllowedWeight };
|
|
7460
|
-
return { ...prev, shortTokens: updated };
|
|
7461
|
-
}
|
|
7462
|
-
else {
|
|
7463
|
-
const updated = [...prev.shortTokens];
|
|
7464
|
-
updated[index] = { ...updated[index], weight: clampedWeight };
|
|
7465
|
-
return { ...prev, shortTokens: updated };
|
|
7466
|
-
}
|
|
7467
|
-
}
|
|
7468
|
-
});
|
|
7469
|
-
},
|
|
7470
|
-
addToken: (isLong) => {
|
|
7471
|
-
const currentTokens = isLong ? get().longTokens : get().shortTokens;
|
|
7472
|
-
const newIndex = currentTokens.length;
|
|
7473
|
-
set((prev) => ({
|
|
7474
|
-
...prev,
|
|
7475
|
-
selectorConfig: { isLong, index: newIndex },
|
|
7476
|
-
openTokenSelector: true,
|
|
7477
|
-
}));
|
|
7478
|
-
},
|
|
7479
|
-
removeToken: (isLong, index) => {
|
|
7480
|
-
set((prev) => {
|
|
7481
|
-
if (isLong) {
|
|
7482
|
-
const updated = prev.longTokens.filter((_, i) => i !== index);
|
|
7483
|
-
return { ...prev, longTokens: updated };
|
|
7484
|
-
}
|
|
7485
|
-
else {
|
|
7486
|
-
const updated = prev.shortTokens.filter((_, i) => i !== index);
|
|
7487
|
-
return { ...prev, shortTokens: updated };
|
|
7488
|
-
}
|
|
7489
|
-
});
|
|
7490
|
-
},
|
|
7491
|
-
handleTokenSelect: (selectedToken) => {
|
|
7492
|
-
const { selectorConfig, longTokens, shortTokens } = get();
|
|
7493
|
-
if (!selectorConfig)
|
|
7494
|
-
return;
|
|
7495
|
-
const { isLong, index } = selectorConfig;
|
|
7496
|
-
const existingTokens = isLong ? longTokens : shortTokens;
|
|
7497
|
-
if (existingTokens.some((t) => t.symbol === selectedToken))
|
|
7498
|
-
return;
|
|
7499
|
-
set((prev) => {
|
|
7500
|
-
const longTotal = prev.longTokens.reduce((s, t) => s + t.weight, 0);
|
|
7501
|
-
const shortTotal = prev.shortTokens.reduce((s, t) => s + t.weight, 0);
|
|
7502
|
-
const currentTotal = longTotal + shortTotal;
|
|
7503
|
-
if (index >= existingTokens.length) {
|
|
7504
|
-
const maxAvailableWeight = Math.max(1, 100 - currentTotal);
|
|
7505
|
-
const safeWeight = Math.min(20, maxAvailableWeight);
|
|
7506
|
-
const newToken = { symbol: selectedToken, weight: safeWeight };
|
|
7507
|
-
if (isLong) {
|
|
7508
|
-
return {
|
|
7509
|
-
...prev,
|
|
7510
|
-
longTokens: [...prev.longTokens, newToken],
|
|
7511
|
-
};
|
|
7512
|
-
}
|
|
7513
|
-
return {
|
|
7514
|
-
...prev,
|
|
7515
|
-
shortTokens: [...prev.shortTokens, newToken],
|
|
7516
|
-
};
|
|
7517
|
-
}
|
|
7518
|
-
else {
|
|
7519
|
-
if (isLong) {
|
|
7520
|
-
const updated = [...prev.longTokens];
|
|
7521
|
-
updated[index] = { ...updated[index], symbol: selectedToken };
|
|
7522
|
-
return { ...prev, longTokens: updated };
|
|
7523
|
-
}
|
|
7524
|
-
else {
|
|
7525
|
-
const updated = [...prev.shortTokens];
|
|
7526
|
-
updated[index] = { ...updated[index], symbol: selectedToken };
|
|
7527
|
-
return { ...prev, shortTokens: updated };
|
|
7528
|
-
}
|
|
7529
|
-
}
|
|
7530
|
-
});
|
|
7531
|
-
},
|
|
7532
|
-
resetToDefaults: () => set((prev) => ({ ...prev, ...DEFAULT_STATE })),
|
|
7533
|
-
}));
|
|
7534
|
-
|
|
7535
7532
|
// Re-expose as a React hook without Zustand's selector signature
|
|
7536
7533
|
const useUserSelection = () => {
|
|
7537
7534
|
return useUserSelection$1();
|
|
@@ -7842,6 +7839,25 @@ const useTokenSelectionMetadata = () => {
|
|
|
7842
7839
|
const createKey = (symbol, interval) => {
|
|
7843
7840
|
return `${symbol}-${interval}`;
|
|
7844
7841
|
};
|
|
7842
|
+
const getIntervalSeconds = (interval) => {
|
|
7843
|
+
switch (interval) {
|
|
7844
|
+
case "1m": return 60;
|
|
7845
|
+
case "3m": return 3 * 60;
|
|
7846
|
+
case "5m": return 5 * 60;
|
|
7847
|
+
case "15m": return 15 * 60;
|
|
7848
|
+
case "30m": return 30 * 60;
|
|
7849
|
+
case "1h": return 60 * 60;
|
|
7850
|
+
case "2h": return 2 * 60 * 60;
|
|
7851
|
+
case "4h": return 4 * 60 * 60;
|
|
7852
|
+
case "8h": return 8 * 60 * 60;
|
|
7853
|
+
case "12h": return 12 * 60 * 60;
|
|
7854
|
+
case "1d": return 24 * 60 * 60;
|
|
7855
|
+
case "3d": return 3 * 24 * 60 * 60;
|
|
7856
|
+
case "1w": return 7 * 24 * 60 * 60;
|
|
7857
|
+
case "1M": return 30 * 24 * 60 * 60; // Approximate month
|
|
7858
|
+
default: return 60;
|
|
7859
|
+
}
|
|
7860
|
+
};
|
|
7845
7861
|
const useHistoricalPriceDataStore = create((set, get) => ({
|
|
7846
7862
|
historicalPriceData: {},
|
|
7847
7863
|
loadingTokens: new Set(),
|
|
@@ -7891,8 +7907,11 @@ const useHistoricalPriceDataStore = create((set, get) => ({
|
|
|
7891
7907
|
const tokenData = historicalPriceData[key];
|
|
7892
7908
|
if (!tokenData || tokenData.oldestTime === null || tokenData.latestTime === null)
|
|
7893
7909
|
return false;
|
|
7894
|
-
|
|
7895
|
-
|
|
7910
|
+
const intervalMilisecond = getIntervalSeconds(interval) * 1000;
|
|
7911
|
+
const hasStartCoverage = tokenData.oldestTime <= startTime;
|
|
7912
|
+
const hasEndCoverage = tokenData.latestTime >= endTime ||
|
|
7913
|
+
(tokenData.latestTime + intervalMilisecond >= endTime);
|
|
7914
|
+
return hasStartCoverage && hasEndCoverage;
|
|
7896
7915
|
},
|
|
7897
7916
|
getHistoricalPriceData: (symbol, interval, startTime, endTime) => {
|
|
7898
7917
|
const { historicalPriceData } = get();
|
|
@@ -7900,8 +7919,7 @@ const useHistoricalPriceDataStore = create((set, get) => ({
|
|
|
7900
7919
|
const tokenData = historicalPriceData[key];
|
|
7901
7920
|
if (!tokenData)
|
|
7902
7921
|
return [];
|
|
7903
|
-
|
|
7904
|
-
return tokenData.candles.filter(candle => candle.t >= startTime && candle.T <= endTime);
|
|
7922
|
+
return tokenData.candles.filter(candle => candle.t >= startTime && candle.t < endTime);
|
|
7905
7923
|
},
|
|
7906
7924
|
setTokenLoading: (symbol, loading) => {
|
|
7907
7925
|
set(state => {
|
|
@@ -7948,8 +7966,6 @@ const useHistoricalPriceData = () => {
|
|
|
7948
7966
|
const longTokens = useUserSelection$1((state) => state.longTokens);
|
|
7949
7967
|
const shortTokens = useUserSelection$1((state) => state.shortTokens);
|
|
7950
7968
|
const candleInterval = useUserSelection$1((state) => state.candleInterval);
|
|
7951
|
-
// Get real-time candle data from WebSocket
|
|
7952
|
-
const candleData = useHyperliquidData((state) => state.candleData);
|
|
7953
7969
|
// Historical price data store
|
|
7954
7970
|
const { addHistoricalPriceData, hasHistoricalPriceData: storeHasData, getHistoricalPriceData: storeGetData, setTokenLoading, isTokenLoading, removeTokenPriceData, clearData, } = useHistoricalPriceDataStore();
|
|
7955
7971
|
// Track previous tokens and interval to detect changes
|
|
@@ -7980,32 +7996,20 @@ const useHistoricalPriceData = () => {
|
|
|
7980
7996
|
prevTokensRef.current = currentTokens;
|
|
7981
7997
|
prevIntervalRef.current = candleInterval;
|
|
7982
7998
|
}, [longTokens, shortTokens, candleInterval, removeTokenPriceData, clearData, getAllTokens]);
|
|
7983
|
-
|
|
7984
|
-
if (!candleData)
|
|
7985
|
-
return;
|
|
7986
|
-
const allTokenSymbols = new Set(getAllTokens().map(token => token.symbol));
|
|
7987
|
-
Object.entries(candleData).forEach(([symbol, candle]) => {
|
|
7988
|
-
if (allTokenSymbols.has(symbol)) {
|
|
7989
|
-
if (candle.i === candleInterval) {
|
|
7990
|
-
addHistoricalPriceData(symbol, candleInterval, [candle], { start: candle.t, end: candle.T });
|
|
7991
|
-
}
|
|
7992
|
-
}
|
|
7993
|
-
});
|
|
7994
|
-
}, [candleData, getAllTokens, candleInterval, addHistoricalPriceData]);
|
|
7995
|
-
const hasHistoricalPriceData = useCallback((startTime, endTime) => {
|
|
7999
|
+
const hasHistoricalPriceData = useCallback((startTime, endTime, interval) => {
|
|
7996
8000
|
const allTokens = getAllTokens();
|
|
7997
8001
|
if (allTokens.length === 0)
|
|
7998
8002
|
return false;
|
|
7999
|
-
return allTokens.every(token => storeHasData(token.symbol,
|
|
8000
|
-
}, [getAllTokens,
|
|
8001
|
-
const getHistoricalPriceData = useCallback((startTime, endTime) => {
|
|
8003
|
+
return allTokens.every(token => storeHasData(token.symbol, interval, startTime, endTime));
|
|
8004
|
+
}, [getAllTokens, storeHasData]);
|
|
8005
|
+
const getHistoricalPriceData = useCallback((startTime, endTime, interval) => {
|
|
8002
8006
|
const allTokens = getAllTokens();
|
|
8003
8007
|
const result = {};
|
|
8004
8008
|
allTokens.forEach(token => {
|
|
8005
|
-
result[token.symbol] = storeGetData(token.symbol,
|
|
8009
|
+
result[token.symbol] = storeGetData(token.symbol, interval, startTime, endTime);
|
|
8006
8010
|
});
|
|
8007
8011
|
return result;
|
|
8008
|
-
}, [getAllTokens,
|
|
8012
|
+
}, [getAllTokens, storeGetData]);
|
|
8009
8013
|
const isLoading = useCallback((symbol) => {
|
|
8010
8014
|
if (symbol) {
|
|
8011
8015
|
return isTokenLoading(symbol);
|
|
@@ -8013,16 +8017,16 @@ const useHistoricalPriceData = () => {
|
|
|
8013
8017
|
const allTokens = getAllTokens();
|
|
8014
8018
|
return allTokens.some(token => isTokenLoading(token.symbol));
|
|
8015
8019
|
}, [getAllTokens, isTokenLoading]);
|
|
8016
|
-
const fetchHistoricalPriceData = useCallback(async (startTime, endTime, callback) => {
|
|
8020
|
+
const fetchHistoricalPriceData = useCallback(async (startTime, endTime, interval, callback) => {
|
|
8017
8021
|
const allTokens = getAllTokens();
|
|
8018
8022
|
if (allTokens.length === 0) {
|
|
8019
8023
|
const emptyResult = {};
|
|
8020
8024
|
callback === null || callback === void 0 ? void 0 : callback(emptyResult);
|
|
8021
8025
|
return emptyResult;
|
|
8022
8026
|
}
|
|
8023
|
-
const tokensToFetch = allTokens.filter(token => !storeHasData(token.symbol,
|
|
8027
|
+
const tokensToFetch = allTokens.filter(token => !storeHasData(token.symbol, interval, startTime, endTime));
|
|
8024
8028
|
if (tokensToFetch.length === 0) {
|
|
8025
|
-
const cachedData = getHistoricalPriceData(startTime, endTime);
|
|
8029
|
+
const cachedData = getHistoricalPriceData(startTime, endTime, interval);
|
|
8026
8030
|
callback === null || callback === void 0 ? void 0 : callback(cachedData);
|
|
8027
8031
|
return cachedData;
|
|
8028
8032
|
}
|
|
@@ -8032,8 +8036,8 @@ const useHistoricalPriceData = () => {
|
|
|
8032
8036
|
try {
|
|
8033
8037
|
const fetchPromises = tokensToFetch.map(async (token) => {
|
|
8034
8038
|
try {
|
|
8035
|
-
const response = await client.fetchHistoricalCandles(token.symbol, startTime, endTime,
|
|
8036
|
-
addHistoricalPriceData(token.symbol,
|
|
8039
|
+
const response = await client.fetchHistoricalCandles(token.symbol, startTime, endTime, interval);
|
|
8040
|
+
addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
|
|
8037
8041
|
return { symbol: token.symbol, candles: response.data, success: true };
|
|
8038
8042
|
}
|
|
8039
8043
|
catch (error) {
|
|
@@ -8042,7 +8046,7 @@ const useHistoricalPriceData = () => {
|
|
|
8042
8046
|
}
|
|
8043
8047
|
});
|
|
8044
8048
|
await Promise.all(fetchPromises);
|
|
8045
|
-
const allData = getHistoricalPriceData(startTime, endTime);
|
|
8049
|
+
const allData = getHistoricalPriceData(startTime, endTime, interval);
|
|
8046
8050
|
callback === null || callback === void 0 ? void 0 : callback(allData);
|
|
8047
8051
|
return allData;
|
|
8048
8052
|
}
|
|
@@ -8181,7 +8185,7 @@ const getCompleteTimestamps = (candleLookups, requiredSymbols) => {
|
|
|
8181
8185
|
*/
|
|
8182
8186
|
const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
|
|
8183
8187
|
var _a, _b;
|
|
8184
|
-
if (longTokens.length === 0
|
|
8188
|
+
if (longTokens.length === 0 && shortTokens.length === 0) {
|
|
8185
8189
|
return [];
|
|
8186
8190
|
}
|
|
8187
8191
|
// Create efficient lookup maps once
|
|
@@ -8274,17 +8278,132 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
|
|
|
8274
8278
|
const useBasketCandles = () => {
|
|
8275
8279
|
const longTokens = useUserSelection$1((state) => state.longTokens);
|
|
8276
8280
|
const shortTokens = useUserSelection$1((state) => state.shortTokens);
|
|
8281
|
+
const candleData = useHyperliquidData((s) => s.candleData);
|
|
8277
8282
|
const { fetchHistoricalPriceData, isLoading: tokenLoading } = useHistoricalPriceData();
|
|
8278
|
-
const fetchBasketCandles = useCallback(async (startTime, endTime) => {
|
|
8279
|
-
const tokenCandles = await fetchHistoricalPriceData(startTime, endTime);
|
|
8283
|
+
const fetchBasketCandles = useCallback(async (startTime, endTime, interval) => {
|
|
8284
|
+
const tokenCandles = await fetchHistoricalPriceData(startTime, endTime, interval);
|
|
8280
8285
|
const basket = computeBasketCandles(longTokens, shortTokens, tokenCandles);
|
|
8281
8286
|
return basket;
|
|
8282
8287
|
}, [fetchHistoricalPriceData, longTokens, shortTokens]);
|
|
8283
8288
|
// Aggregate loading across selected tokens via underlying hook
|
|
8284
8289
|
const isLoading = tokenLoading();
|
|
8290
|
+
// Realtime listeners management
|
|
8291
|
+
const listenersRef = useRef(new Map());
|
|
8292
|
+
const lastEmittedRef = useRef(null);
|
|
8293
|
+
const addRealtimeListener = useCallback((cb) => {
|
|
8294
|
+
const id = Math.random().toString(36).slice(2);
|
|
8295
|
+
listenersRef.current.set(id, cb);
|
|
8296
|
+
return id;
|
|
8297
|
+
}, []);
|
|
8298
|
+
const removeRealtimeListener = useCallback((id) => {
|
|
8299
|
+
listenersRef.current.delete(id);
|
|
8300
|
+
}, []);
|
|
8301
|
+
// Helper: compute weighted bar from latest snapshot if all tokens aligned
|
|
8302
|
+
const computeRealtimeBar = useCallback(() => {
|
|
8303
|
+
if (!candleData)
|
|
8304
|
+
return null;
|
|
8305
|
+
const allTokens = [...longTokens, ...shortTokens];
|
|
8306
|
+
if (allTokens.length === 0)
|
|
8307
|
+
return null;
|
|
8308
|
+
// Collect candles, ensure presence and alignment
|
|
8309
|
+
const symbolSet = new Set(allTokens.map((t) => t.symbol));
|
|
8310
|
+
const snapshot = {};
|
|
8311
|
+
for (const symbol of symbolSet) {
|
|
8312
|
+
const c = candleData.get(symbol);
|
|
8313
|
+
if (!c)
|
|
8314
|
+
return null; // missing latest candle for symbol
|
|
8315
|
+
snapshot[symbol] = c;
|
|
8316
|
+
}
|
|
8317
|
+
// Verify same interval window (t and T match across symbols)
|
|
8318
|
+
let t = null;
|
|
8319
|
+
let T = null;
|
|
8320
|
+
for (const symbol of symbolSet) {
|
|
8321
|
+
const c = snapshot[symbol];
|
|
8322
|
+
if (t === null) {
|
|
8323
|
+
t = c.t;
|
|
8324
|
+
T = c.T;
|
|
8325
|
+
}
|
|
8326
|
+
else if (c.t !== t || c.T !== T) {
|
|
8327
|
+
return null; // not aligned yet
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8330
|
+
// Compute weighted OHLC similar to computeBasketCandles
|
|
8331
|
+
let longOpen = 1, longHigh = 1, longLow = 1, longClose = 1;
|
|
8332
|
+
let shortOpen = 1, shortHigh = 1, shortLow = 1, shortClose = 1;
|
|
8333
|
+
let totalVolume = 0;
|
|
8334
|
+
let totalTrades = 0;
|
|
8335
|
+
for (const token of longTokens) {
|
|
8336
|
+
const c = snapshot[token.symbol];
|
|
8337
|
+
if (!c)
|
|
8338
|
+
return null;
|
|
8339
|
+
const w = token.weight / 100;
|
|
8340
|
+
const o = parseFloat(c.o), h = parseFloat(c.h), l = parseFloat(c.l), cl = parseFloat(c.c);
|
|
8341
|
+
if (!(o > 0 && h > 0 && l > 0 && cl > 0))
|
|
8342
|
+
return null;
|
|
8343
|
+
longOpen *= Math.pow(o, w);
|
|
8344
|
+
longHigh *= Math.pow(h, w);
|
|
8345
|
+
longLow *= Math.pow(l, w);
|
|
8346
|
+
longClose *= Math.pow(cl, w);
|
|
8347
|
+
totalVolume += parseFloat(c.v);
|
|
8348
|
+
totalTrades += c.n;
|
|
8349
|
+
}
|
|
8350
|
+
for (const token of shortTokens) {
|
|
8351
|
+
const c = snapshot[token.symbol];
|
|
8352
|
+
if (!c)
|
|
8353
|
+
return null;
|
|
8354
|
+
const w = -(token.weight / 100);
|
|
8355
|
+
const o = parseFloat(c.o), h = parseFloat(c.h), l = parseFloat(c.l), cl = parseFloat(c.c);
|
|
8356
|
+
if (!(o > 0 && h > 0 && l > 0 && cl > 0))
|
|
8357
|
+
return null;
|
|
8358
|
+
shortOpen *= Math.pow(o, w);
|
|
8359
|
+
shortHigh *= Math.pow(h, w);
|
|
8360
|
+
shortLow *= Math.pow(l, w);
|
|
8361
|
+
shortClose *= Math.pow(cl, w);
|
|
8362
|
+
totalVolume += parseFloat(c.v);
|
|
8363
|
+
totalTrades += c.n;
|
|
8364
|
+
}
|
|
8365
|
+
if (t === null || T === null)
|
|
8366
|
+
return null;
|
|
8367
|
+
const weighted = {
|
|
8368
|
+
t,
|
|
8369
|
+
T,
|
|
8370
|
+
o: longOpen * shortOpen,
|
|
8371
|
+
h: longHigh * shortHigh,
|
|
8372
|
+
l: longLow * shortLow,
|
|
8373
|
+
c: longClose * shortClose,
|
|
8374
|
+
v: totalVolume,
|
|
8375
|
+
n: totalTrades,
|
|
8376
|
+
};
|
|
8377
|
+
return weighted;
|
|
8378
|
+
}, [candleData, longTokens, shortTokens]);
|
|
8379
|
+
// Emit realtime bars when aligned snapshot updates
|
|
8380
|
+
useEffect(() => {
|
|
8381
|
+
if (listenersRef.current.size === 0)
|
|
8382
|
+
return;
|
|
8383
|
+
const bar = computeRealtimeBar();
|
|
8384
|
+
if (!bar)
|
|
8385
|
+
return;
|
|
8386
|
+
lastEmittedRef.current = bar.t;
|
|
8387
|
+
const tvBar = {
|
|
8388
|
+
time: bar.t,
|
|
8389
|
+
open: bar.o,
|
|
8390
|
+
high: bar.h,
|
|
8391
|
+
low: bar.l,
|
|
8392
|
+
close: bar.c,
|
|
8393
|
+
volume: bar.v,
|
|
8394
|
+
};
|
|
8395
|
+
listenersRef.current.forEach((cb) => {
|
|
8396
|
+
try {
|
|
8397
|
+
cb(tvBar);
|
|
8398
|
+
}
|
|
8399
|
+
catch (e) { /* noop */ }
|
|
8400
|
+
});
|
|
8401
|
+
}, [computeRealtimeBar]);
|
|
8285
8402
|
return {
|
|
8286
8403
|
fetchBasketCandles,
|
|
8287
8404
|
isLoading,
|
|
8405
|
+
addRealtimeListener,
|
|
8406
|
+
removeRealtimeListener,
|
|
8288
8407
|
};
|
|
8289
8408
|
};
|
|
8290
8409
|
|
|
@@ -8330,4 +8449,49 @@ class ConflictDetector {
|
|
|
8330
8449
|
}
|
|
8331
8450
|
}
|
|
8332
8451
|
|
|
8333
|
-
|
|
8452
|
+
/**
|
|
8453
|
+
* Maps TradingView ResolutionString to CandleInterval
|
|
8454
|
+
*/
|
|
8455
|
+
function mapTradingViewIntervalToCandleInterval(interval) {
|
|
8456
|
+
switch (interval) {
|
|
8457
|
+
case '1': return '1m';
|
|
8458
|
+
case '3': return '3m';
|
|
8459
|
+
case '5': return '5m';
|
|
8460
|
+
case '15': return '15m';
|
|
8461
|
+
case '30': return '30m';
|
|
8462
|
+
case '60': return '1h';
|
|
8463
|
+
case '120': return '2h';
|
|
8464
|
+
case '240': return '4h';
|
|
8465
|
+
case '480': return '8h';
|
|
8466
|
+
case '720': return '12h';
|
|
8467
|
+
case '1D': return '1d';
|
|
8468
|
+
case '3D': return '3d';
|
|
8469
|
+
case '1W': return '1w';
|
|
8470
|
+
case '1M': return '1M';
|
|
8471
|
+
default: return '1h'; // fallback to 1 hour
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
/**
|
|
8475
|
+
* Maps CandleInterval to TradingView ResolutionString
|
|
8476
|
+
*/
|
|
8477
|
+
function mapCandleIntervalToTradingViewInterval(interval) {
|
|
8478
|
+
switch (interval) {
|
|
8479
|
+
case '1m': return '1';
|
|
8480
|
+
case '3m': return '3';
|
|
8481
|
+
case '5m': return '5';
|
|
8482
|
+
case '15m': return '15';
|
|
8483
|
+
case '30m': return '30';
|
|
8484
|
+
case '1h': return '60';
|
|
8485
|
+
case '2h': return '120';
|
|
8486
|
+
case '4h': return '240';
|
|
8487
|
+
case '8h': return '480';
|
|
8488
|
+
case '12h': return '720';
|
|
8489
|
+
case '1d': return '1D';
|
|
8490
|
+
case '3d': return '3D';
|
|
8491
|
+
case '1w': return '1W';
|
|
8492
|
+
case '1M': return '1M';
|
|
8493
|
+
default: return '60'; // fallback to 1 hour
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8497
|
+
export { AccountSummaryCalculator, ConflictDetector, PearHyperliquidClient, PearHyperliquidProvider, PearMigrationSDK, TokenMetadataExtractor, calculateWeightedRatio, computeBasketCandles, createCandleLookups, PearHyperliquidClient as default, getCompleteTimestamps, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, useAccountSummary, useAddress, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMigrationSDK, useOpenOrders, useOpenPositions, usePearHyperliquidClient, useTokenSelectionMetadata, useTradeHistories, useUserSelection, useWebData };
|
|
@@ -8,6 +8,7 @@ interface UserSelectionState {
|
|
|
8
8
|
openConflictModal: boolean;
|
|
9
9
|
conflicts: TokenConflict[];
|
|
10
10
|
candleInterval: CandleInterval;
|
|
11
|
+
isWeightBalanced: boolean;
|
|
11
12
|
setLongTokens: (tokens: TokenSelection[]) => void;
|
|
12
13
|
setShortTokens: (tokens: TokenSelection[]) => void;
|
|
13
14
|
setOpenTokenSelector: (open: boolean) => void;
|
package/dist/types.d.ts
CHANGED
|
@@ -635,7 +635,7 @@ export interface CandleData {
|
|
|
635
635
|
* Candle chart data organized by symbol only
|
|
636
636
|
* Since new candles always have latest timestamp, no need to store per interval
|
|
637
637
|
*/
|
|
638
|
-
export type CandleChartData =
|
|
638
|
+
export type CandleChartData = Map<string, CandleData>;
|
|
639
639
|
/**
|
|
640
640
|
* Historical candle data request
|
|
641
641
|
*/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CandleInterval } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Maps TradingView ResolutionString to CandleInterval
|
|
4
|
+
*/
|
|
5
|
+
export declare function mapTradingViewIntervalToCandleInterval(interval: string): CandleInterval;
|
|
6
|
+
/**
|
|
7
|
+
* Maps CandleInterval to TradingView ResolutionString
|
|
8
|
+
*/
|
|
9
|
+
export declare function mapCandleIntervalToTradingViewInterval(interval: CandleInterval): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/hyperliquid-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "React SDK for Pear Protocol Hyperliquid API integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "rollup -c",
|
|
20
20
|
"dev": "rollup -c -w",
|
|
21
|
+
"copy-watch": "nodemon --watch dist --exec \"npm run copy-to-node-modules\"",
|
|
22
|
+
"copy-to-node-modules": "cp -r dist/* ../node_modules/@pear-protocol/hyperliquid-sdk/dist/",
|
|
21
23
|
"type-check": "tsc --noEmit",
|
|
22
24
|
"clean": "rimraf dist"
|
|
23
25
|
},
|
|
@@ -35,9 +37,10 @@
|
|
|
35
37
|
"@rollup/plugin-terser": "^0.4.4",
|
|
36
38
|
"@rollup/plugin-typescript": "^11.0.0",
|
|
37
39
|
"@types/react": "^18.0.0",
|
|
40
|
+
"concurrently": "^9.2.1",
|
|
41
|
+
"esbuild": "^0.25.9",
|
|
38
42
|
"react": "^18.3.1",
|
|
39
43
|
"react-dom": "^18.3.1",
|
|
40
|
-
"esbuild": "^0.25.9",
|
|
41
44
|
"rimraf": "^5.0.0",
|
|
42
45
|
"rollup": "^3.0.0",
|
|
43
46
|
"rollup-plugin-dts": "^6.0.0",
|