@pear-protocol/hyperliquid-sdk 0.0.20 → 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.
@@ -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, tokens, candleInterval }: UseHyperliquidNativeWebSocketProps) => UseHyperliquidNativeWebSocketReturn;
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 = Record<string, CandleData>;
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, tokens, candleInterval }: UseHyperliquidNativeWebSocketProps) => UseHyperliquidNativeWebSocketReturn;
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, 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, 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, useMemo, useContext, useCallback } from 'react';
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
- return {
6657
- candleData: {
6658
- ...state.candleData,
6659
- [symbol]: candle
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 || !(symbol in state.candleData)) {
6667
- return state; // No change if symbol doesn't exist
6668
- }
6669
- const updated = { ...state.candleData };
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 useHyperliquidNativeWebSocket = ({ address, tokens = [], candleInterval = "1h" }) => {
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 [subscribedCandleInterval, setSubscribedCandleInterval] = useState(null);
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.c, 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 tokensToSubscribe = tokens.filter(token => token && !subscribedTokens.includes(token));
6812
- const tokensToUnsubscribe = subscribedTokens.filter(token => !tokens.includes(token));
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(tokens.filter(token => token));
6839
- tokensToSubscribe.forEach(token => deleteActiveAssetData(token));
6965
+ setSubscribedTokens(effectiveTokens.filter((token) => token));
6966
+ tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
6840
6967
  }
6841
- }, [isConnected, address, tokens, subscribedTokens, sendJsonMessage, setActiveAssetData]);
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
- if (subscribedCandleInterval && subscribedCandleInterval !== candleInterval) {
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: subscribedCandleInterval,
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 = tokens.filter(token => token && !subscribedCandleTokens.includes(token));
6863
- const tokensToUnsubscribe = subscribedCandleTokens.filter(token => !tokens.includes(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 || subscribedCandleInterval !== candleInterval) {
6890
- setSubscribedCandleTokens(tokens.filter(token => token));
6891
- setSubscribedCandleInterval(candleInterval);
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, tokens, candleInterval, subscribedCandleTokens, subscribedCandleInterval, sendJsonMessage, setCandleData]);
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
- // Check if our cached data covers the requested time range
7895
- return tokenData.oldestTime <= startTime && tokenData.latestTime >= endTime;
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
- // Filter candles within the requested time range
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
- useEffect(() => {
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, candleInterval, startTime, endTime));
8000
- }, [getAllTokens, candleInterval, storeHasData]);
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, candleInterval, startTime, endTime);
8009
+ result[token.symbol] = storeGetData(token.symbol, interval, startTime, endTime);
8006
8010
  });
8007
8011
  return result;
8008
- }, [getAllTokens, candleInterval, storeGetData]);
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, candleInterval, startTime, endTime));
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, candleInterval);
8036
- addHistoricalPriceData(token.symbol, candleInterval, response.data, { start: startTime, end: endTime });
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 || shortTokens.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
- 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 };
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 = Record<string, CandleData>;
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.20",
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",