@pear-protocol/hyperliquid-sdk 0.1.4 → 0.1.6

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.
@@ -3,6 +3,7 @@ export interface UseBasketCandlesReturn {
3
3
  fetchBasketCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<CandleData[]>;
4
4
  fetchPerformanceCandles: (startTime: number, endTime: number, interval: CandleInterval, symbol: string) => Promise<CandleData[]>;
5
5
  fetchOverallPerformanceCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<CandleData[]>;
6
+ getEffectiveDataBoundary: (interval: CandleInterval) => number | null;
6
7
  isLoading: boolean;
7
8
  addRealtimeListener: (cb: RealtimeBarsCallback) => string;
8
9
  removeRealtimeListener: (id: string) => void;
@@ -4,6 +4,7 @@ export interface UseHistoricalPriceDataReturn {
4
4
  fetchHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
5
5
  hasHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => boolean;
6
6
  getHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => Record<string, CandleData[]>;
7
+ getEffectiveDataBoundary: (interval: CandleInterval) => number | null;
7
8
  getAllHistoricalPriceData(): Promise<Record<string, TokenHistoricalPriceData>>;
8
9
  isLoading: (symbol?: string) => boolean;
9
10
  clearCache: () => void;
package/dist/index.d.ts CHANGED
@@ -1068,6 +1068,8 @@ interface TokenHistoricalPriceData {
1068
1068
  candles: CandleData[];
1069
1069
  oldestTime: number | null;
1070
1070
  latestTime: number | null;
1071
+ requestedRanges: HistoricalRange[];
1072
+ noDataBefore: number | null;
1071
1073
  }
1072
1074
  interface HistoricalPriceDataState {
1073
1075
  historicalPriceData: Record<string, TokenHistoricalPriceData>;
@@ -1075,6 +1077,7 @@ interface HistoricalPriceDataState {
1075
1077
  addHistoricalPriceData: (symbol: string, interval: CandleInterval, candles: CandleData[], range: HistoricalRange) => void;
1076
1078
  hasHistoricalPriceData: (symbol: string, interval: CandleInterval, startTime: number, endTime: number) => boolean;
1077
1079
  getHistoricalPriceData: (symbol: string, interval: CandleInterval, startTime: number, endTime: number) => CandleData[];
1080
+ getEffectiveDataBoundary: (symbols: string[], interval: CandleInterval) => number | null;
1078
1081
  setTokenLoading: (symbol: string, loading: boolean) => void;
1079
1082
  isTokenLoading: (symbol: string) => boolean;
1080
1083
  removeTokenPriceData: (symbol: string, interval: CandleInterval) => void;
@@ -1086,6 +1089,7 @@ interface UseHistoricalPriceDataReturn {
1086
1089
  fetchHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval, callback?: (data: Record<string, CandleData[]>) => void) => Promise<Record<string, CandleData[]>>;
1087
1090
  hasHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => boolean;
1088
1091
  getHistoricalPriceData: (startTime: number, endTime: number, interval: CandleInterval) => Record<string, CandleData[]>;
1092
+ getEffectiveDataBoundary: (interval: CandleInterval) => number | null;
1089
1093
  getAllHistoricalPriceData(): Promise<Record<string, TokenHistoricalPriceData>>;
1090
1094
  isLoading: (symbol?: string) => boolean;
1091
1095
  clearCache: () => void;
@@ -1096,6 +1100,7 @@ interface UseBasketCandlesReturn {
1096
1100
  fetchBasketCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<CandleData[]>;
1097
1101
  fetchPerformanceCandles: (startTime: number, endTime: number, interval: CandleInterval, symbol: string) => Promise<CandleData[]>;
1098
1102
  fetchOverallPerformanceCandles: (startTime: number, endTime: number, interval: CandleInterval) => Promise<CandleData[]>;
1103
+ getEffectiveDataBoundary: (interval: CandleInterval) => number | null;
1099
1104
  isLoading: boolean;
1100
1105
  addRealtimeListener: (cb: RealtimeBarsCallback) => string;
1101
1106
  removeRealtimeListener: (id: string) => void;
package/dist/index.js CHANGED
@@ -1819,6 +1819,22 @@ const getIntervalSeconds = (interval) => {
1819
1819
  default: return 60;
1820
1820
  }
1821
1821
  };
1822
+ /**
1823
+ * Merges overlapping or adjacent ranges to prevent unbounded growth
1824
+ */
1825
+ const mergeRanges = (ranges, newRange) => {
1826
+ const all = [...ranges, newRange].sort((a, b) => a.start - b.start);
1827
+ const merged = [];
1828
+ for (const r of all) {
1829
+ if (merged.length === 0 || r.start > merged[merged.length - 1].end) {
1830
+ merged.push({ start: r.start, end: r.end });
1831
+ }
1832
+ else {
1833
+ merged[merged.length - 1].end = Math.max(merged[merged.length - 1].end, r.end);
1834
+ }
1835
+ }
1836
+ return merged;
1837
+ };
1822
1838
  const useHistoricalPriceDataStore = create((set, get) => ({
1823
1839
  historicalPriceData: {},
1824
1840
  loadingTokens: new Set(),
@@ -1829,6 +1845,8 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1829
1845
  if (!existing) {
1830
1846
  // Create new entry
1831
1847
  const sortedCandles = [...candles].sort((a, b) => a.t - b.t);
1848
+ // If first fetch returns empty, mark that there's no data before the requested end time
1849
+ const noDataBefore = sortedCandles.length === 0 ? range.end : null;
1832
1850
  return {
1833
1851
  historicalPriceData: {
1834
1852
  ...state.historicalPriceData,
@@ -1837,7 +1855,9 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1837
1855
  interval,
1838
1856
  candles: sortedCandles,
1839
1857
  oldestTime: sortedCandles.length > 0 ? sortedCandles[0].t : null,
1840
- latestTime: sortedCandles.length > 0 ? sortedCandles[sortedCandles.length - 1].t : null
1858
+ latestTime: sortedCandles.length > 0 ? sortedCandles[sortedCandles.length - 1].t : null,
1859
+ requestedRanges: [range],
1860
+ noDataBefore
1841
1861
  }
1842
1862
  }
1843
1863
  };
@@ -1849,6 +1869,16 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1849
1869
  // Update time pointers
1850
1870
  const oldestTime = mergedCandles.length > 0 ? mergedCandles[0].t : null;
1851
1871
  const latestTime = mergedCandles.length > 0 ? mergedCandles[mergedCandles.length - 1].t : null;
1872
+ // Merge requested ranges
1873
+ const mergedRanges = mergeRanges(existing.requestedRanges || [], range);
1874
+ // Update noDataBefore boundary:
1875
+ // If we fetched a range older than our current oldest data and got no new candles,
1876
+ // it means there's no data before our current oldest time
1877
+ let noDataBefore = existing.noDataBefore;
1878
+ if (candles.length === 0 && existing.oldestTime !== null && range.end <= existing.oldestTime) {
1879
+ // We tried to fetch older data and got nothing - set boundary to our oldest known data
1880
+ noDataBefore = existing.oldestTime;
1881
+ }
1852
1882
  return {
1853
1883
  historicalPriceData: {
1854
1884
  ...state.historicalPriceData,
@@ -1856,7 +1886,9 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1856
1886
  ...existing,
1857
1887
  candles: mergedCandles,
1858
1888
  oldestTime,
1859
- latestTime
1889
+ latestTime,
1890
+ requestedRanges: mergedRanges,
1891
+ noDataBefore
1860
1892
  }
1861
1893
  }
1862
1894
  };
@@ -1866,8 +1898,24 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1866
1898
  const { historicalPriceData } = get();
1867
1899
  const key = createKey(symbol, interval);
1868
1900
  const tokenData = historicalPriceData[key];
1869
- if (!tokenData || tokenData.oldestTime === null || tokenData.latestTime === null)
1901
+ if (!tokenData)
1902
+ return false;
1903
+ // Check if we've hit the "no data before" boundary
1904
+ // If the requested range ends before or at the boundary, we know there's no data
1905
+ if (tokenData.noDataBefore !== null && endTime <= tokenData.noDataBefore) {
1906
+ return true; // No point fetching - we know there's no data before this point
1907
+ }
1908
+ // Check if we've already attempted to fetch this range
1909
+ const requestedRanges = tokenData.requestedRanges || [];
1910
+ for (const range of requestedRanges) {
1911
+ if (range.start <= startTime && range.end >= endTime) {
1912
+ return true; // Already attempted this fetch
1913
+ }
1914
+ }
1915
+ // Check actual data coverage
1916
+ if (tokenData.oldestTime === null || tokenData.latestTime === null) {
1870
1917
  return false;
1918
+ }
1871
1919
  const intervalMilisecond = getIntervalSeconds(interval) * 1000;
1872
1920
  const hasStartCoverage = tokenData.oldestTime <= startTime;
1873
1921
  const hasEndCoverage = tokenData.latestTime >= endTime ||
@@ -1882,6 +1930,27 @@ const useHistoricalPriceDataStore = create((set, get) => ({
1882
1930
  return [];
1883
1931
  return tokenData.candles.filter(candle => candle.t >= startTime && candle.t < endTime);
1884
1932
  },
1933
+ getEffectiveDataBoundary: (symbols, interval) => {
1934
+ var _a;
1935
+ const { historicalPriceData } = get();
1936
+ if (symbols.length === 0)
1937
+ return null;
1938
+ let maxBoundary = null;
1939
+ for (const symbol of symbols) {
1940
+ const key = createKey(symbol, interval);
1941
+ const tokenData = historicalPriceData[key];
1942
+ if (!tokenData)
1943
+ continue;
1944
+ // Use noDataBefore if set, otherwise use oldestTime
1945
+ const boundary = (_a = tokenData.noDataBefore) !== null && _a !== void 0 ? _a : tokenData.oldestTime;
1946
+ if (boundary !== null) {
1947
+ if (maxBoundary === null || boundary > maxBoundary) {
1948
+ maxBoundary = boundary;
1949
+ }
1950
+ }
1951
+ }
1952
+ return maxBoundary;
1953
+ },
1885
1954
  setTokenLoading: (symbol, loading) => {
1886
1955
  set(state => {
1887
1956
  const newLoadingTokens = new Set(state.loadingTokens);
@@ -6012,7 +6081,7 @@ const useHistoricalPriceData = () => {
6012
6081
  const shortTokens = useUserSelection$1((state) => state.shortTokens);
6013
6082
  const candleInterval = useUserSelection$1((state) => state.candleInterval);
6014
6083
  // Historical price data store
6015
- const { addHistoricalPriceData, hasHistoricalPriceData: storeHasData, getHistoricalPriceData: storeGetData, setTokenLoading, isTokenLoading, removeTokenPriceData, clearData, } = useHistoricalPriceDataStore();
6084
+ const { addHistoricalPriceData, hasHistoricalPriceData: storeHasData, getHistoricalPriceData: storeGetData, getEffectiveDataBoundary: storeGetBoundary, setTokenLoading, isTokenLoading, removeTokenPriceData, clearData, } = useHistoricalPriceDataStore();
6016
6085
  // Track previous tokens and interval to detect changes
6017
6086
  const prevTokensRef = useRef(new Set());
6018
6087
  const prevIntervalRef = useRef(null);
@@ -6065,6 +6134,11 @@ const useHistoricalPriceData = () => {
6065
6134
  const getAllHistoricalPriceData = useCallback(async () => {
6066
6135
  return useHistoricalPriceDataStore.getState().historicalPriceData;
6067
6136
  }, []);
6137
+ const getEffectiveDataBoundary = useCallback((interval) => {
6138
+ const allTokens = getAllTokens();
6139
+ const symbols = allTokens.map(t => t.symbol);
6140
+ return storeGetBoundary(symbols, interval);
6141
+ }, [getAllTokens, storeGetBoundary]);
6068
6142
  const fetchHistoricalPriceData = useCallback(async (startTime, endTime, interval, callback) => {
6069
6143
  const allTokens = getAllTokens();
6070
6144
  if (allTokens.length === 0) {
@@ -6122,6 +6196,7 @@ const useHistoricalPriceData = () => {
6122
6196
  hasHistoricalPriceData,
6123
6197
  getAllHistoricalPriceData,
6124
6198
  getHistoricalPriceData,
6199
+ getEffectiveDataBoundary,
6125
6200
  isLoading,
6126
6201
  clearCache,
6127
6202
  };
@@ -6318,7 +6393,7 @@ const useBasketCandles = () => {
6318
6393
  const longTokens = useUserSelection$1((state) => state.longTokens);
6319
6394
  const shortTokens = useUserSelection$1((state) => state.shortTokens);
6320
6395
  const candleData = useHyperliquidData((s) => s.candleData);
6321
- const { fetchHistoricalPriceData, isLoading: tokenLoading, getAllHistoricalPriceData } = useHistoricalPriceData();
6396
+ const { fetchHistoricalPriceData, isLoading: tokenLoading, getAllHistoricalPriceData, getEffectiveDataBoundary } = useHistoricalPriceData();
6322
6397
  const fetchBasketCandles = useCallback(async (startTime, endTime, interval) => {
6323
6398
  const tokenCandles = await fetchHistoricalPriceData(startTime, endTime, interval);
6324
6399
  const basket = computeBasketCandles(longTokens, shortTokens, tokenCandles);
@@ -6518,6 +6593,7 @@ const useBasketCandles = () => {
6518
6593
  fetchBasketCandles,
6519
6594
  fetchPerformanceCandles,
6520
6595
  fetchOverallPerformanceCandles,
6596
+ getEffectiveDataBoundary,
6521
6597
  isLoading,
6522
6598
  addRealtimeListener,
6523
6599
  removeRealtimeListener,
@@ -9,6 +9,8 @@ interface TokenHistoricalPriceData {
9
9
  candles: CandleData[];
10
10
  oldestTime: number | null;
11
11
  latestTime: number | null;
12
+ requestedRanges: HistoricalRange[];
13
+ noDataBefore: number | null;
12
14
  }
13
15
  interface HistoricalPriceDataState {
14
16
  historicalPriceData: Record<string, TokenHistoricalPriceData>;
@@ -16,6 +18,7 @@ interface HistoricalPriceDataState {
16
18
  addHistoricalPriceData: (symbol: string, interval: CandleInterval, candles: CandleData[], range: HistoricalRange) => void;
17
19
  hasHistoricalPriceData: (symbol: string, interval: CandleInterval, startTime: number, endTime: number) => boolean;
18
20
  getHistoricalPriceData: (symbol: string, interval: CandleInterval, startTime: number, endTime: number) => CandleData[];
21
+ getEffectiveDataBoundary: (symbols: string[], interval: CandleInterval) => number | null;
19
22
  setTokenLoading: (symbol: string, loading: boolean) => void;
20
23
  isTokenLoading: (symbol: string) => boolean;
21
24
  removeTokenPriceData: (symbol: string, interval: CandleInterval) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/hyperliquid-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "React SDK for Pear Protocol Hyperliquid API integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",