@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.
- package/dist/clients/tradeHistory.d.ts +7 -0
- package/dist/hooks/usePnlCalendar.d.ts +21 -2
- package/dist/index.d.ts +21 -3
- package/dist/index.js +213 -129
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
8503
|
-
const
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
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
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
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
|
|
8638
|
+
return {
|
|
8639
|
+
timeframe,
|
|
8640
|
+
weeks,
|
|
8641
|
+
months,
|
|
8642
|
+
overall: buildSummary(allDays),
|
|
8643
|
+
isLoading: false,
|
|
8644
|
+
};
|
|
8513
8645
|
};
|
|
8514
8646
|
// ─── hook ───────────────────────────────────────────────────────
|
|
8515
|
-
function usePnlCalendar(
|
|
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
|
|
8521
|
-
const
|
|
8522
|
-
|
|
8523
|
-
|
|
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 (!
|
|
8725
|
+
if (!trades)
|
|
8533
8726
|
return empty;
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
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);
|