@pear-protocol/hyperliquid-sdk 0.1.1-9.1-pnl → 0.1.2-pnl

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.
@@ -0,0 +1,7 @@
1
+ import type { ApiResponse, TradeHistoryDataDto } from '../types';
2
+ export interface GetTradeHistoryParams {
3
+ startDate?: string;
4
+ endDate?: string;
5
+ limit?: number;
6
+ }
7
+ export declare function getTradeHistory(baseUrl: string, params?: GetTradeHistoryParams): Promise<ApiResponse<TradeHistoryDataDto[]>>;
@@ -1,11 +1,28 @@
1
+ import type { TokenMetadata } from '../types';
1
2
  export type PnlCalendarTimeframe = '2W' | '3W' | '2M' | '3M';
3
+ export interface PnlCalendarOptions {
4
+ timeframe?: PnlCalendarTimeframe;
5
+ startDate?: Date | string;
6
+ endDate?: Date | string;
7
+ }
8
+ export interface PnlCalendarAsset {
9
+ coin: string;
10
+ metadata?: TokenMetadata | null;
11
+ }
12
+ export interface PnlCalendarTrade {
13
+ tradeHistoryId: string;
14
+ realizedPnl: number;
15
+ result: 'profit' | 'loss' | 'breakeven';
16
+ closedLongAssets: PnlCalendarAsset[];
17
+ closedShortAssets: PnlCalendarAsset[];
18
+ }
2
19
  export interface PnlCalendarDay {
3
20
  date: string;
4
21
  totalPnl: number;
5
22
  volume: number;
6
23
  positionsClosed: number;
7
24
  result: 'profit' | 'loss' | 'breakeven';
8
- tokens: string[];
25
+ trades: PnlCalendarTrade[];
9
26
  }
10
27
  export interface PeriodSummary {
11
28
  pnl: number;
@@ -34,5 +51,7 @@ export interface UsePnlCalendarResult {
34
51
  months: PnlCalendarMonth[];
35
52
  overall: PeriodSummary;
36
53
  isLoading: boolean;
54
+ error: string | null;
55
+ refetch: () => void;
37
56
  }
38
- export declare function usePnlCalendar(timeframe?: PnlCalendarTimeframe): UsePnlCalendarResult;
57
+ export declare function usePnlCalendar(options?: PnlCalendarTimeframe | PnlCalendarOptions): UsePnlCalendarResult;
package/dist/index.d.ts CHANGED
@@ -1499,13 +1499,29 @@ interface UseHyperliquidUserFillsState {
1499
1499
  declare function useHyperliquidUserFills(options: UseHyperliquidUserFillsOptions): UseHyperliquidUserFillsState;
1500
1500
 
1501
1501
  type PnlCalendarTimeframe = '2W' | '3W' | '2M' | '3M';
1502
+ interface PnlCalendarOptions {
1503
+ timeframe?: PnlCalendarTimeframe;
1504
+ startDate?: Date | string;
1505
+ endDate?: Date | string;
1506
+ }
1507
+ interface PnlCalendarAsset {
1508
+ coin: string;
1509
+ metadata?: TokenMetadata | null;
1510
+ }
1511
+ interface PnlCalendarTrade {
1512
+ tradeHistoryId: string;
1513
+ realizedPnl: number;
1514
+ result: 'profit' | 'loss' | 'breakeven';
1515
+ closedLongAssets: PnlCalendarAsset[];
1516
+ closedShortAssets: PnlCalendarAsset[];
1517
+ }
1502
1518
  interface PnlCalendarDay {
1503
1519
  date: string;
1504
1520
  totalPnl: number;
1505
1521
  volume: number;
1506
1522
  positionsClosed: number;
1507
1523
  result: 'profit' | 'loss' | 'breakeven';
1508
- tokens: string[];
1524
+ trades: PnlCalendarTrade[];
1509
1525
  }
1510
1526
  interface PeriodSummary {
1511
1527
  pnl: number;
@@ -1534,8 +1550,10 @@ interface UsePnlCalendarResult {
1534
1550
  months: PnlCalendarMonth[];
1535
1551
  overall: PeriodSummary;
1536
1552
  isLoading: boolean;
1553
+ error: string | null;
1554
+ refetch: () => void;
1537
1555
  }
1538
- declare function usePnlCalendar(timeframe?: PnlCalendarTimeframe): UsePnlCalendarResult;
1556
+ declare function usePnlCalendar(options?: PnlCalendarTimeframe | PnlCalendarOptions): UsePnlCalendarResult;
1539
1557
 
1540
1558
  /**
1541
1559
  * Mark notifications as read up to a given timestamp (ms)
@@ -1812,4 +1830,4 @@ interface MarketDataState {
1812
1830
  declare const useMarketData: zustand.UseBoundStore<zustand.StoreApi<MarketDataState>>;
1813
1831
 
1814
1832
  export { ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, ReadyState, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getCompleteTimestamps, getKalshiMarkets, getOrderDirection, getOrderLadderConfig, getOrderLeverage, getOrderReduceOnly, getOrderTpSlTriggerType, getOrderTrailingInfo, getOrderTriggerType, getOrderTriggerValue, getOrderTwapDuration, getOrderUsdValue, getPortfolio, isBtcDomOrder, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useAgentWallet, useAllUserBalances, useAuth, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidUserFills, useMarket, useMarketData, useMarketDataHook, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePnlCalendar, usePortfolio, usePosition, useSpotOrder, useTokenSelectionMetadata, useTradeHistories, useTwap, useUserSelection, useWatchlist, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
1815
- export type { AccountSummaryResponseDto, ActiveAssetData, ActiveAssetGroupItem, ActiveAssetsAllResponse, ActiveAssetsResponse, AddressState, AdjustAdvanceAssetInput, AdjustAdvanceItemInput, AdjustAdvanceResponseDto, AdjustExecutionType, AdjustOrderRequestInput, AdjustOrderResponseDto, AdjustPositionRequestInput, AdjustPositionResponseDto, AgentWalletDto, AgentWalletState, AgentWalletStatus, AllDexsAssetCtxsData, AllDexsClearinghouseStateData, AllPerpMetasItem, AllPerpMetasResponse, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetMarketData, AssetPosition, AuthenticateRequest, AuthenticateResponse, AvailableToTrades, BalanceSummaryDto, BaseTriggerOrderNotificationParams, BtcDomTriggerParams, CancelOrderResponseDto, CancelTwapResponseDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ChunkFillDto, ClearinghouseState, CloseAllPositionsResponseDto, CloseAllPositionsResultDto, CloseExecutionType, ClosePositionRequestInput, ClosePositionResponseDto, CollateralToken, CreateAgentWalletResponseDto, CreatePositionRequestInput, CreatePositionResponseDto, CrossAssetPriceTriggerParams, CrossMarginSummaryDto, CumFundingDto, EIP712AuthDetails, ExecutionType, ExternalFillDto, ExternalLiquidationDto, ExtraAgent, GetAgentWalletResponseDto, GetEIP712MessageResponse, GetKalshiMarketsParams, HLChannel, HLChannelDataMap, HLWebSocketResponse, HistoricalRange, KalshiMarket, KalshiMarketsResponse, KalshiMveLeg, KalshiPriceRange, LadderConfigInput, LadderOrderParameters, LeverageInfo, LogoutRequest, LogoutResponse, MarginRequiredPerCollateral, MarginRequiredResult, MarginSummaryDto, MarginTableDef, MarginTablesEntry, MarginTier, MarketOrderParameters, NotificationCategory, NotificationDto, OpenLimitOrderDto, OpenPositionDto, OrderAssetDto, OrderDirection, OrderParameters, OrderStatus, OrderType, PairAssetDto, PairAssetInput, PerformanceOverlay, PeriodSummary, PerpDex, PerpDexsResponse, PerpMetaAsset, PlatformAccountSummaryResponseDto, PnlCalendarDay, PnlCalendarMonth, PnlCalendarTimeframe, PnlCalendarWeek, PortfolioBucketDto, PortfolioInterval, PortfolioIntervalsDto, PortfolioOverallDto, PortfolioResponseDto, PositionAdjustmentType, PositionAssetDetailDto, PredictionMarketOutcomeTriggerParams, PriceRatioTriggerParams, PriceTriggerParams, PrivyAuthDetails, RawAssetDto, RawPositionDto, RealtimeBar, RealtimeBarsCallback, RebalanceAssetPlan, RebalancePlan, RefreshTokenRequest, RefreshTokenResponse, SpotBalance, SpotBalances, SpotOrderFilledStatus, SpotOrderHyperliquidData, SpotOrderHyperliquidResult, SpotOrderRequestInput, SpotOrderResponseDto, SpotState, SyncFillsRequestDto, SyncFillsResponseDto, ToggleWatchlistResponseDto, TokenConflict, TokenEntry, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TokenSelectorConfig, TpSlOrderParameters, TpSlThreshold, TpSlThresholdInput, TpSlThresholdType, TpSlTriggerType, TradeHistoryAssetDataDto, TradeHistoryDataDto, TriggerOrderNotificationAsset, TriggerOrderNotificationParams, TriggerOrderNotificationType, TriggerOrderParameters, TriggerType, TwapChunkStatus, TwapChunkStatusDto, TwapMonitoringDto, TwapOrderOverallStatus, TwapOrderParameters, TwapSliceFillResponseItem, UniverseAsset, UpdateLeverageRequestInput, UpdateLeverageResponseDto, UpdateRiskParametersRequestInput, UpdateRiskParametersResponseDto, UseAuthOptions, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseHyperliquidUserFillsOptions, UseHyperliquidUserFillsState, UseNotificationsResult, UsePerformanceOverlaysReturn, UsePnlCalendarResult, UsePortfolioResult, UseSpotOrderResult, UseTokenSelectionMetadataReturn, UserAbstraction, UserProfile, UserSelectionState, WatchlistAssetDto, WatchlistItemDto, WebData3AssetCtx, WebData3PerpDexState, WebData3Response, WebData3UserState, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WsAllMidsData };
1833
+ export type { AccountSummaryResponseDto, ActiveAssetData, ActiveAssetGroupItem, ActiveAssetsAllResponse, ActiveAssetsResponse, AddressState, AdjustAdvanceAssetInput, AdjustAdvanceItemInput, AdjustAdvanceResponseDto, AdjustExecutionType, AdjustOrderRequestInput, AdjustOrderResponseDto, AdjustPositionRequestInput, AdjustPositionResponseDto, AgentWalletDto, AgentWalletState, AgentWalletStatus, AllDexsAssetCtxsData, AllDexsClearinghouseStateData, AllPerpMetasItem, AllPerpMetasResponse, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetMarketData, AssetPosition, AuthenticateRequest, AuthenticateResponse, AvailableToTrades, BalanceSummaryDto, BaseTriggerOrderNotificationParams, BtcDomTriggerParams, CancelOrderResponseDto, CancelTwapResponseDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ChunkFillDto, ClearinghouseState, CloseAllPositionsResponseDto, CloseAllPositionsResultDto, CloseExecutionType, ClosePositionRequestInput, ClosePositionResponseDto, CollateralToken, CreateAgentWalletResponseDto, CreatePositionRequestInput, CreatePositionResponseDto, CrossAssetPriceTriggerParams, CrossMarginSummaryDto, CumFundingDto, EIP712AuthDetails, ExecutionType, ExternalFillDto, ExternalLiquidationDto, ExtraAgent, GetAgentWalletResponseDto, GetEIP712MessageResponse, GetKalshiMarketsParams, HLChannel, HLChannelDataMap, HLWebSocketResponse, HistoricalRange, KalshiMarket, KalshiMarketsResponse, KalshiMveLeg, KalshiPriceRange, LadderConfigInput, LadderOrderParameters, LeverageInfo, LogoutRequest, LogoutResponse, MarginRequiredPerCollateral, MarginRequiredResult, MarginSummaryDto, MarginTableDef, MarginTablesEntry, MarginTier, MarketOrderParameters, NotificationCategory, NotificationDto, OpenLimitOrderDto, OpenPositionDto, OrderAssetDto, OrderDirection, OrderParameters, OrderStatus, OrderType, PairAssetDto, PairAssetInput, PerformanceOverlay, PeriodSummary, PerpDex, PerpDexsResponse, PerpMetaAsset, PlatformAccountSummaryResponseDto, PnlCalendarAsset, PnlCalendarDay, PnlCalendarMonth, PnlCalendarOptions, PnlCalendarTimeframe, PnlCalendarTrade, PnlCalendarWeek, PortfolioBucketDto, PortfolioInterval, PortfolioIntervalsDto, PortfolioOverallDto, PortfolioResponseDto, PositionAdjustmentType, PositionAssetDetailDto, PredictionMarketOutcomeTriggerParams, PriceRatioTriggerParams, PriceTriggerParams, PrivyAuthDetails, RawAssetDto, RawPositionDto, RealtimeBar, RealtimeBarsCallback, RebalanceAssetPlan, RebalancePlan, RefreshTokenRequest, RefreshTokenResponse, SpotBalance, SpotBalances, SpotOrderFilledStatus, SpotOrderHyperliquidData, SpotOrderHyperliquidResult, SpotOrderRequestInput, SpotOrderResponseDto, SpotState, SyncFillsRequestDto, SyncFillsResponseDto, ToggleWatchlistResponseDto, TokenConflict, TokenEntry, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TokenSelectorConfig, TpSlOrderParameters, TpSlThreshold, TpSlThresholdInput, TpSlThresholdType, TpSlTriggerType, TradeHistoryAssetDataDto, TradeHistoryDataDto, TriggerOrderNotificationAsset, TriggerOrderNotificationParams, TriggerOrderNotificationType, TriggerOrderParameters, TriggerType, TwapChunkStatus, TwapChunkStatusDto, TwapMonitoringDto, TwapOrderOverallStatus, TwapOrderParameters, TwapSliceFillResponseItem, UniverseAsset, UpdateLeverageRequestInput, UpdateLeverageResponseDto, UpdateRiskParametersRequestInput, UpdateRiskParametersResponseDto, UseAuthOptions, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseHyperliquidUserFillsOptions, UseHyperliquidUserFillsState, UseNotificationsResult, UsePerformanceOverlaysReturn, UsePnlCalendarResult, UsePortfolioResult, UseSpotOrderResult, UseTokenSelectionMetadataReturn, UserAbstraction, UserProfile, UserSelectionState, WatchlistAssetDto, WatchlistItemDto, WebData3AssetCtx, WebData3PerpDexState, WebData3Response, WebData3UserState, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WsAllMidsData };
package/dist/index.js CHANGED
@@ -8422,6 +8422,20 @@ function useHyperliquidUserFills(options) {
8422
8422
  };
8423
8423
  }
8424
8424
 
8425
+ async function getTradeHistory(baseUrl, params) {
8426
+ const url = joinUrl(baseUrl, '/trade-history');
8427
+ try {
8428
+ const resp = await apiClient.get(url, {
8429
+ params,
8430
+ timeout: 60000,
8431
+ });
8432
+ return { data: resp.data, status: resp.status, headers: resp.headers };
8433
+ }
8434
+ catch (error) {
8435
+ throw toApiError(error);
8436
+ }
8437
+ }
8438
+
8425
8439
  // ─── helpers ────────────────────────────────────────────────────
8426
8440
  const EMPTY_SUMMARY = {
8427
8441
  pnl: 0,
@@ -8465,6 +8479,18 @@ const getMonday = (date) => {
8465
8479
  d.setHours(0, 0, 0, 0);
8466
8480
  return d;
8467
8481
  };
8482
+ const toLocalMidnight = (input) => {
8483
+ const d = typeof input === 'string' ? new Date(input + 'T00:00:00') : new Date(input);
8484
+ d.setHours(0, 0, 0, 0);
8485
+ return d;
8486
+ };
8487
+ const diffDays = (start, end) => {
8488
+ return Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
8489
+ };
8490
+ const toISODateString = (input) => {
8491
+ const d = typeof input === 'string' ? new Date(input + 'T00:00:00') : new Date(input);
8492
+ return d.toISOString();
8493
+ };
8468
8494
  const round2 = (n) => Math.round(n * 100) / 100;
8469
8495
  const buildSummary = (days) => {
8470
8496
  let pnl = 0;
@@ -8499,28 +8525,195 @@ const buildSummary = (days) => {
8499
8525
  totalLoss: round2(totalLoss),
8500
8526
  };
8501
8527
  };
8502
- const extractTokens = (trade) => {
8503
- const tokens = new Set();
8504
- for (const asset of trade.closedLongAssets) {
8505
- if (asset.coin)
8506
- tokens.add(asset.coin);
8528
+ const buildCalendarData = (tradeHistories, timeframe, rangeStart, rangeEnd, totalDays, useCustomDates) => {
8529
+ const startKey = toDateKey(rangeStart);
8530
+ const endKey = toDateKey(rangeEnd);
8531
+ // Build day buckets for the full range
8532
+ const buckets = new Map();
8533
+ for (let i = 0; i < totalDays; i++) {
8534
+ const d = new Date(rangeStart);
8535
+ d.setDate(rangeStart.getDate() + i);
8536
+ buckets.set(toDateKey(d), {
8537
+ pnl: 0,
8538
+ volume: 0,
8539
+ positionsClosed: 0,
8540
+ trades: [],
8541
+ });
8542
+ }
8543
+ // Populate buckets from trade histories
8544
+ for (const trade of tradeHistories) {
8545
+ if (!trade.createdAt)
8546
+ continue;
8547
+ const date = new Date(trade.createdAt);
8548
+ if (isNaN(date.getTime()))
8549
+ continue;
8550
+ const dateKey = toDateKey(date);
8551
+ if (dateKey < startKey || dateKey > endKey)
8552
+ continue;
8553
+ const bucket = buckets.get(dateKey);
8554
+ if (!bucket)
8555
+ continue;
8556
+ const pnl = trade.realizedPnl;
8557
+ bucket.pnl += isFinite(pnl) ? pnl : 0;
8558
+ const vol = trade.totalValue;
8559
+ bucket.volume += isFinite(vol) ? vol : 0;
8560
+ bucket.positionsClosed += 1;
8561
+ const tradePnl = trade.realizedPnl;
8562
+ bucket.trades.push({
8563
+ tradeHistoryId: trade.tradeHistoryId,
8564
+ realizedPnl: tradePnl,
8565
+ result: tradePnl > 0 ? 'profit' : tradePnl < 0 ? 'loss' : 'breakeven',
8566
+ closedLongAssets: trade.closedLongAssets.map((a) => ({ coin: a.coin, metadata: a.metadata })),
8567
+ closedShortAssets: trade.closedShortAssets.map((a) => ({ coin: a.coin, metadata: a.metadata })),
8568
+ });
8507
8569
  }
8508
- for (const asset of trade.closedShortAssets) {
8509
- if (asset.coin)
8510
- tokens.add(asset.coin);
8570
+ // Build day objects
8571
+ const allDays = [];
8572
+ const sortedKeys = Array.from(buckets.keys()).sort();
8573
+ for (const key of sortedKeys) {
8574
+ const bucket = buckets.get(key);
8575
+ const roundedPnl = round2(bucket.pnl);
8576
+ const result = roundedPnl > 0 ? 'profit' : roundedPnl < 0 ? 'loss' : 'breakeven';
8577
+ allDays.push({
8578
+ date: key,
8579
+ totalPnl: roundedPnl,
8580
+ volume: round2(bucket.volume),
8581
+ positionsClosed: bucket.positionsClosed,
8582
+ result,
8583
+ trades: bucket.trades,
8584
+ });
8585
+ }
8586
+ // Group into periods
8587
+ let weeks = [];
8588
+ let months = [];
8589
+ const useWeekGrouping = useCustomDates
8590
+ ? totalDays <= 28
8591
+ : isWeekTimeframe(timeframe);
8592
+ if (useWeekGrouping) {
8593
+ const weekMap = new Map();
8594
+ for (const day of allDays) {
8595
+ const date = new Date(day.date + 'T00:00:00');
8596
+ const monday = getMonday(date);
8597
+ const mondayKey = toDateKey(monday);
8598
+ if (!weekMap.has(mondayKey)) {
8599
+ weekMap.set(mondayKey, []);
8600
+ }
8601
+ weekMap.get(mondayKey).push(day);
8602
+ }
8603
+ const sortedWeekKeys = Array.from(weekMap.keys()).sort();
8604
+ weeks = sortedWeekKeys.map((mondayKey) => {
8605
+ const days = weekMap.get(mondayKey);
8606
+ const monday = new Date(mondayKey + 'T00:00:00');
8607
+ const sunday = new Date(monday);
8608
+ sunday.setDate(monday.getDate() + 6);
8609
+ return {
8610
+ weekStart: mondayKey,
8611
+ weekEnd: toDateKey(sunday),
8612
+ days,
8613
+ summary: buildSummary(days),
8614
+ };
8615
+ });
8616
+ }
8617
+ else {
8618
+ const monthMap = new Map();
8619
+ for (const day of allDays) {
8620
+ const date = new Date(day.date + 'T00:00:00');
8621
+ const mk = toMonthKey(date);
8622
+ if (!monthMap.has(mk)) {
8623
+ monthMap.set(mk, { days: [], label: formatMonthLabel(date) });
8624
+ }
8625
+ monthMap.get(mk).days.push(day);
8626
+ }
8627
+ const sortedMonthKeys = Array.from(monthMap.keys()).sort();
8628
+ months = sortedMonthKeys.map((mk) => {
8629
+ const { days, label } = monthMap.get(mk);
8630
+ return {
8631
+ month: mk,
8632
+ label,
8633
+ days,
8634
+ summary: buildSummary(days),
8635
+ };
8636
+ });
8511
8637
  }
8512
- return Array.from(tokens);
8638
+ return {
8639
+ timeframe,
8640
+ weeks,
8641
+ months,
8642
+ overall: buildSummary(allDays),
8643
+ isLoading: false,
8644
+ };
8513
8645
  };
8514
8646
  // ─── hook ───────────────────────────────────────────────────────
8515
- function usePnlCalendar(timeframe = '2W') {
8647
+ function usePnlCalendar(options) {
8648
+ var _a;
8649
+ const opts = typeof options === 'string'
8650
+ ? { timeframe: options }
8651
+ : options !== null && options !== void 0 ? options : {};
8652
+ const timeframe = (_a = opts.timeframe) !== null && _a !== void 0 ? _a : '2W';
8653
+ const customStart = opts.startDate;
8654
+ const customEnd = opts.endDate;
8516
8655
  const context = useContext(PearHyperliquidContext);
8517
8656
  if (!context) {
8518
8657
  throw new Error('usePnlCalendar must be used within a PearHyperliquidProvider');
8519
8658
  }
8520
- const tradeHistories = useUserData((state) => state.tradeHistories);
8521
- const isLoading = useMemo(() => {
8522
- return tradeHistories === null && context.isConnected;
8523
- }, [tradeHistories, context.isConnected]);
8659
+ const { apiBaseUrl } = context;
8660
+ const isAuthenticated = useUserData((state) => state.isAuthenticated);
8661
+ const [trades, setTrades] = useState(null);
8662
+ const [isLoading, setIsLoading] = useState(false);
8663
+ const [error, setError] = useState(null);
8664
+ const mountedRef = useRef(true);
8665
+ useEffect(() => {
8666
+ mountedRef.current = true;
8667
+ return () => { mountedRef.current = false; };
8668
+ }, []);
8669
+ // Compute the date range
8670
+ const useCustomDates = !!(customStart && customEnd);
8671
+ let rangeStart;
8672
+ let rangeEnd;
8673
+ let totalDays;
8674
+ if (useCustomDates) {
8675
+ rangeStart = toLocalMidnight(customStart);
8676
+ rangeEnd = toLocalMidnight(customEnd);
8677
+ totalDays = diffDays(rangeStart, rangeEnd);
8678
+ }
8679
+ else {
8680
+ totalDays = getTimeframeDays(timeframe);
8681
+ rangeEnd = new Date();
8682
+ rangeEnd.setHours(0, 0, 0, 0);
8683
+ rangeStart = new Date(rangeEnd);
8684
+ rangeStart.setDate(rangeEnd.getDate() - totalDays + 1);
8685
+ }
8686
+ const startIso = toISODateString(rangeStart);
8687
+ const endIso = toISODateString(rangeEnd);
8688
+ const fetchData = useCallback(async () => {
8689
+ if (!isAuthenticated)
8690
+ return;
8691
+ setIsLoading(true);
8692
+ setError(null);
8693
+ try {
8694
+ const response = await getTradeHistory(apiBaseUrl, {
8695
+ startDate: startIso,
8696
+ endDate: endIso,
8697
+ limit: totalDays * 50,
8698
+ });
8699
+ if (!mountedRef.current)
8700
+ return;
8701
+ setTrades(response.data);
8702
+ }
8703
+ catch (err) {
8704
+ if (!mountedRef.current)
8705
+ return;
8706
+ setError(err instanceof Error ? err.message : 'Failed to fetch trade history');
8707
+ setTrades(null);
8708
+ }
8709
+ finally {
8710
+ if (mountedRef.current)
8711
+ setIsLoading(false);
8712
+ }
8713
+ }, [apiBaseUrl, isAuthenticated, startIso, endIso, totalDays]);
8714
+ useEffect(() => {
8715
+ fetchData();
8716
+ }, [fetchData]);
8524
8717
  const result = useMemo(() => {
8525
8718
  const empty = {
8526
8719
  timeframe,
@@ -8529,122 +8722,13 @@ function usePnlCalendar(timeframe = '2W') {
8529
8722
  overall: EMPTY_SUMMARY,
8530
8723
  isLoading: true,
8531
8724
  };
8532
- if (!tradeHistories)
8725
+ if (!trades)
8533
8726
  return empty;
8534
- const totalDays = getTimeframeDays(timeframe);
8535
- const now = new Date();
8536
- const startDate = new Date(now);
8537
- startDate.setDate(now.getDate() - totalDays + 1);
8538
- startDate.setHours(0, 0, 0, 0);
8539
- const startKey = toDateKey(startDate);
8540
- // Build day buckets for the full range
8541
- const buckets = new Map();
8542
- for (let i = 0; i < totalDays; i++) {
8543
- const d = new Date(startDate);
8544
- d.setDate(startDate.getDate() + i);
8545
- buckets.set(toDateKey(d), {
8546
- pnl: 0,
8547
- volume: 0,
8548
- positionsClosed: 0,
8549
- tokens: new Set(),
8550
- });
8551
- }
8552
- // Populate buckets from trade histories
8553
- for (const trade of tradeHistories) {
8554
- if (!trade.createdAt)
8555
- continue;
8556
- const date = new Date(trade.createdAt);
8557
- if (isNaN(date.getTime()))
8558
- continue;
8559
- const dateKey = toDateKey(date);
8560
- if (dateKey < startKey)
8561
- continue;
8562
- const bucket = buckets.get(dateKey);
8563
- if (!bucket)
8564
- continue;
8565
- const pnl = trade.realizedPnl;
8566
- bucket.pnl += isFinite(pnl) ? pnl : 0;
8567
- const vol = trade.totalValue;
8568
- bucket.volume += isFinite(vol) ? vol : 0;
8569
- bucket.positionsClosed += 1;
8570
- for (const token of extractTokens(trade)) {
8571
- bucket.tokens.add(token);
8572
- }
8573
- }
8574
- // Build day objects
8575
- const allDays = [];
8576
- const sortedKeys = Array.from(buckets.keys()).sort();
8577
- for (const key of sortedKeys) {
8578
- const bucket = buckets.get(key);
8579
- const roundedPnl = round2(bucket.pnl);
8580
- const result = roundedPnl > 0 ? 'profit' : roundedPnl < 0 ? 'loss' : 'breakeven';
8581
- allDays.push({
8582
- date: key,
8583
- totalPnl: roundedPnl,
8584
- volume: round2(bucket.volume),
8585
- positionsClosed: bucket.positionsClosed,
8586
- result,
8587
- tokens: Array.from(bucket.tokens),
8588
- });
8589
- }
8590
- // Group into periods
8591
- let weeks = [];
8592
- let months = [];
8593
- if (isWeekTimeframe(timeframe)) {
8594
- const weekMap = new Map();
8595
- for (const day of allDays) {
8596
- const date = new Date(day.date + 'T00:00:00');
8597
- const monday = getMonday(date);
8598
- const mondayKey = toDateKey(monday);
8599
- if (!weekMap.has(mondayKey)) {
8600
- weekMap.set(mondayKey, []);
8601
- }
8602
- weekMap.get(mondayKey).push(day);
8603
- }
8604
- const sortedWeekKeys = Array.from(weekMap.keys()).sort();
8605
- weeks = sortedWeekKeys.map((mondayKey) => {
8606
- const days = weekMap.get(mondayKey);
8607
- const monday = new Date(mondayKey + 'T00:00:00');
8608
- const sunday = new Date(monday);
8609
- sunday.setDate(monday.getDate() + 6);
8610
- return {
8611
- weekStart: mondayKey,
8612
- weekEnd: toDateKey(sunday),
8613
- days,
8614
- summary: buildSummary(days),
8615
- };
8616
- });
8617
- }
8618
- else {
8619
- const monthMap = new Map();
8620
- for (const day of allDays) {
8621
- const date = new Date(day.date + 'T00:00:00');
8622
- const mk = toMonthKey(date);
8623
- if (!monthMap.has(mk)) {
8624
- monthMap.set(mk, { days: [], label: formatMonthLabel(date) });
8625
- }
8626
- monthMap.get(mk).days.push(day);
8627
- }
8628
- const sortedMonthKeys = Array.from(monthMap.keys()).sort();
8629
- months = sortedMonthKeys.map((mk) => {
8630
- const { days, label } = monthMap.get(mk);
8631
- return {
8632
- month: mk,
8633
- label,
8634
- days,
8635
- summary: buildSummary(days),
8636
- };
8637
- });
8638
- }
8639
- return {
8640
- timeframe,
8641
- weeks,
8642
- months,
8643
- overall: buildSummary(allDays),
8644
- isLoading: false,
8645
- };
8646
- }, [tradeHistories, timeframe]);
8647
- return { ...result, isLoading };
8727
+ if (totalDays <= 0)
8728
+ return empty;
8729
+ return buildCalendarData(trades, timeframe, rangeStart, rangeEnd, totalDays, useCustomDates);
8730
+ }, [trades, timeframe, startIso, endIso]);
8731
+ return { ...result, isLoading, error, refetch: fetchData };
8648
8732
  }
8649
8733
 
8650
8734
  const PearHyperliquidContext = createContext(undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/hyperliquid-sdk",
3
- "version": "0.1.19.1-pnl",
3
+ "version": "0.1.2-pnl",
4
4
  "description": "React SDK for Pear Protocol Hyperliquid API integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",