@pear-protocol/hyperliquid-sdk 0.0.20 → 0.0.24

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/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,
@@ -7047,290 +7176,73 @@ class AccountSummaryCalculator {
7047
7176
  }
7048
7177
  }
7049
7178
 
7050
- var PositionSide;
7051
- (function (PositionSide) {
7052
- PositionSide["LONG"] = "LONG";
7053
- PositionSide["SHORT"] = "SHORT";
7054
- })(PositionSide || (PositionSide = {}));
7055
- class PositionProcessor {
7056
- constructor(webData2, allMids) {
7057
- this.webData2 = webData2;
7058
- this.allMids = allMids;
7059
- }
7060
- execute(rawPositions) {
7061
- if (!rawPositions || rawPositions.length === 0) {
7062
- return [];
7063
- }
7064
- const userHyperliquidPositions = this.getUserPositions();
7065
- const platformTotalsByAsset = this.calculatePlatformTotalsByAsset(rawPositions);
7066
- const hlPositionsMap = new Map();
7067
- (userHyperliquidPositions || []).forEach(assetPos => {
7068
- var _a;
7069
- if ((_a = assetPos.position) === null || _a === void 0 ? void 0 : _a.coin) {
7070
- hlPositionsMap.set(assetPos.position.coin, assetPos);
7071
- }
7072
- });
7073
- const openPositionDtos = [];
7074
- for (const position of rawPositions) {
7075
- const syncedPositionDto = this.syncPositionWithAggregateData(position, hlPositionsMap, platformTotalsByAsset);
7076
- openPositionDtos.push(syncedPositionDto);
7077
- }
7078
- return openPositionDtos;
7079
- }
7080
- getUserPositions() {
7081
- var _a, _b;
7082
- return ((_b = (_a = this.webData2) === null || _a === void 0 ? void 0 : _a.clearinghouseState) === null || _b === void 0 ? void 0 : _b.assetPositions) || [];
7083
- }
7084
- getMarketPrice(coin) {
7085
- var _a;
7086
- if (!((_a = this.allMids) === null || _a === void 0 ? void 0 : _a.mids))
7087
- return 0;
7088
- const exactPrice = this.allMids.mids[coin];
7089
- if (exactPrice) {
7090
- return Number(exactPrice);
7091
- }
7092
- const baseCurrency = this.extractBaseCurrency(coin);
7093
- const basePrice = this.allMids.mids[baseCurrency];
7094
- if (basePrice) {
7095
- return Number(basePrice);
7096
- }
7097
- return 0;
7098
- }
7099
- calculatePlatformTotalsByAsset(positions) {
7100
- const totalsMap = new Map();
7101
- for (const position of positions) {
7102
- for (const asset of position.longAssets || []) {
7103
- const baseCurrency = this.extractBaseCurrency(asset.coin);
7104
- if (!totalsMap.has(baseCurrency)) {
7105
- totalsMap.set(baseCurrency, {
7106
- totalSize: 0,
7107
- positions: []
7108
- });
7109
- }
7110
- const totals = totalsMap.get(baseCurrency);
7111
- const assetSize = Number(asset.size || 0);
7112
- totals.totalSize += assetSize;
7113
- totals.positions.push({
7114
- positionId: position.positionId,
7115
- asset: asset,
7116
- size: assetSize
7117
- });
7118
- }
7119
- for (const asset of position.shortAssets || []) {
7120
- const baseCurrency = this.extractBaseCurrency(asset.coin);
7121
- if (!totalsMap.has(baseCurrency)) {
7122
- totalsMap.set(baseCurrency, {
7123
- totalSize: 0,
7124
- positions: []
7125
- });
7126
- }
7127
- const totals = totalsMap.get(baseCurrency);
7128
- const assetSize = Number(asset.size || 0);
7129
- totals.totalSize += assetSize;
7130
- totals.positions.push({
7131
- positionId: position.positionId,
7132
- asset: asset,
7133
- size: assetSize
7134
- });
7135
- }
7136
- }
7137
- return totalsMap;
7138
- }
7139
- extractBaseCurrency(assetName) {
7140
- return assetName.split('/')[0] || assetName;
7141
- }
7142
- syncPositionWithAggregateData(position, hlPositionsMap, platformTotalsByAsset) {
7143
- const syncResults = [];
7144
- let hasExternalModification = false;
7145
- let allAssetsClosed = true;
7146
- let longAssetStatuses = { total: 0, closed: 0 };
7147
- let shortAssetStatuses = { total: 0, closed: 0 };
7148
- // Process long assets
7149
- for (const asset of position.longAssets || []) {
7150
- const baseCurrency = this.extractBaseCurrency(asset.coin);
7151
- const hlPosition = hlPositionsMap.get(baseCurrency);
7152
- const platformTotals = platformTotalsByAsset.get(baseCurrency);
7153
- const syncResult = this.syncAssetWithAggregateData({ ...asset, side: PositionSide.LONG }, hlPosition, (platformTotals === null || platformTotals === void 0 ? void 0 : platformTotals.totalSize) || 0);
7154
- syncResults.push(syncResult);
7155
- longAssetStatuses.total++;
7156
- if (syncResult.actualSize === 0) {
7157
- longAssetStatuses.closed++;
7158
- }
7159
- if (syncResult.isExternallyModified) {
7160
- hasExternalModification = true;
7161
- }
7162
- if (syncResult.actualSize !== 0) {
7163
- allAssetsClosed = false;
7164
- }
7165
- }
7166
- // Process short assets
7167
- for (const asset of position.shortAssets || []) {
7168
- const baseCurrency = this.extractBaseCurrency(asset.coin);
7169
- const hlPosition = hlPositionsMap.get(baseCurrency);
7170
- const platformTotals = platformTotalsByAsset.get(baseCurrency);
7171
- const syncResult = this.syncAssetWithAggregateData({ ...asset, side: PositionSide.SHORT }, hlPosition, (platformTotals === null || platformTotals === void 0 ? void 0 : platformTotals.totalSize) || 0);
7172
- syncResults.push(syncResult);
7173
- shortAssetStatuses.total++;
7174
- if (syncResult.actualSize === 0) {
7175
- shortAssetStatuses.closed++;
7176
- }
7177
- if (syncResult.isExternallyModified) {
7178
- hasExternalModification = true;
7179
- }
7180
- if (syncResult.actualSize !== 0) {
7181
- allAssetsClosed = false;
7182
- }
7183
- }
7184
- const syncStatus = this.determineSyncStatus(hasExternalModification, allAssetsClosed, longAssetStatuses, shortAssetStatuses);
7185
- return this.mapPositionToDtoWithSyncData(position, syncResults, syncStatus);
7186
- }
7187
- determineSyncStatus(hasExternalModification, allAssetsClosed, longAssetStatuses, shortAssetStatuses) {
7188
- if (allAssetsClosed) {
7189
- return 'EXTERNALLY_CLOSED';
7190
- }
7191
- const allLongsClosed = longAssetStatuses.total > 0 &&
7192
- longAssetStatuses.closed === longAssetStatuses.total;
7193
- const allShortsClosed = shortAssetStatuses.total > 0 &&
7194
- shortAssetStatuses.closed === shortAssetStatuses.total;
7195
- if ((allLongsClosed && !allShortsClosed) || (!allLongsClosed && allShortsClosed)) {
7196
- return 'PAIR_BROKEN';
7197
- }
7198
- if (hasExternalModification) {
7199
- return 'EXTERNALLY_MODIFIED';
7200
- }
7201
- return 'SYNCED';
7202
- }
7203
- syncAssetWithAggregateData(asset, hlPosition, platformTotal) {
7204
- const platformSize = Number(asset.size || 0);
7205
- if (!hlPosition || !hlPosition.position || !hlPosition.position.szi) {
7206
- return {
7207
- asset,
7208
- actualSize: 0,
7209
- isExternallyModified: true,
7210
- cumFunding: { allTime: 0, sinceChange: 0, sinceOpen: 0 },
7211
- unrealizedPnl: 0,
7212
- liquidationPrice: 0
7213
- };
7179
+ const calculatePositionAsset = (asset, currentPrice, totalInitialPositionSize, leverage, cumFunding, isLong = true) => {
7180
+ const initialPositionValue = asset.entryPrice * asset.size;
7181
+ const positionValue = currentPrice * asset.size;
7182
+ const marginUsed = positionValue / leverage;
7183
+ const unrealizedPnl = isLong
7184
+ ? positionValue - initialPositionValue
7185
+ : initialPositionValue - positionValue;
7186
+ return {
7187
+ coin: asset.coin,
7188
+ entryPrice: asset.entryPrice,
7189
+ actualSize: asset.size,
7190
+ marginUsed: marginUsed,
7191
+ positionValue: positionValue,
7192
+ unrealizedPnl: unrealizedPnl,
7193
+ initialWeight: initialPositionValue / totalInitialPositionSize,
7194
+ cumFunding: {
7195
+ allTime: parseFloat((cumFunding === null || cumFunding === void 0 ? void 0 : cumFunding.allTime) || "0"),
7196
+ sinceChange: parseFloat((cumFunding === null || cumFunding === void 0 ? void 0 : cumFunding.sinceChange) || "0"),
7197
+ sinceOpen: parseFloat((cumFunding === null || cumFunding === void 0 ? void 0 : cumFunding.sinceOpen) || "0"),
7214
7198
  }
7215
- const hlTotalSize = Math.abs(Number(hlPosition.position.szi || 0));
7216
- const totalDifference = Math.abs(hlTotalSize - platformTotal);
7217
- const tolerance = platformTotal * 0.001;
7218
- const isExternallyModified = totalDifference > tolerance;
7219
- const proportion = platformTotal > 0 ? platformSize / platformTotal : 0;
7220
- const actualSize = hlTotalSize * proportion;
7221
- // Get cumFunding from hlPosition.position.cumFunding
7222
- const rawCumFunding = hlPosition.position.cumFunding;
7223
- const cumFunding = {
7224
- allTime: Number((rawCumFunding === null || rawCumFunding === void 0 ? void 0 : rawCumFunding.allTime) || 0),
7225
- sinceChange: Number((rawCumFunding === null || rawCumFunding === void 0 ? void 0 : rawCumFunding.sinceChange) || 0) * proportion,
7226
- sinceOpen: Number((rawCumFunding === null || rawCumFunding === void 0 ? void 0 : rawCumFunding.sinceOpen) || 0) * proportion
7227
- };
7228
- const unrealizedPnl = Number(hlPosition.position.unrealizedPnl || 0) * proportion;
7229
- const liquidationPrice = Number(hlPosition.position.liquidationPx || 0);
7230
- return {
7231
- asset,
7232
- actualSize,
7233
- isExternallyModified,
7234
- cumFunding,
7235
- unrealizedPnl,
7236
- liquidationPrice
7237
- };
7238
- }
7239
- mapPositionToDtoWithSyncData(position, syncResults, syncStatus) {
7240
- var _a, _b;
7241
- const longAssets = ((_a = position.longAssets) === null || _a === void 0 ? void 0 : _a.filter(asset => asset)) || [];
7242
- const shortAssets = ((_b = position.shortAssets) === null || _b === void 0 ? void 0 : _b.filter(asset => asset)) || [];
7243
- const syncResultsMap = new Map();
7244
- syncResults.forEach(result => {
7245
- syncResultsMap.set(`${result.asset.coin}-${result.asset.side}`, result);
7246
- });
7247
- const currentTotalPositionValue = this.calculateCurrentTotalPositionValue(syncResults);
7248
- const entryTotalPositionValue = this.calculateEntryTotalPositionValue(syncResults);
7249
- const totalMarginUsed = entryTotalPositionValue / position.leverage;
7250
- return {
7251
- syncStatus: syncStatus,
7199
+ };
7200
+ };
7201
+ const buildPositionValue = (rawPositions, webData2, allMids) => {
7202
+ return rawPositions.map((position) => {
7203
+ let mappedPosition = {
7252
7204
  positionId: position.positionId,
7253
7205
  address: position.address,
7254
7206
  leverage: position.leverage,
7255
- stopLoss: position.stopLoss,
7256
- takeProfit: position.takeProfit,
7257
- entryRatio: this.calculateEntryRatio(syncResults),
7258
- markRatio: this.calculateMarkRatio(syncResults),
7259
- netFunding: this.calculateNetFundingFromSyncResults(syncResults),
7260
- positionValue: currentTotalPositionValue,
7261
- marginUsed: totalMarginUsed,
7262
- unrealizedPnl: this.calculateTotalUnrealizedPnlFromSyncResults(syncResults),
7263
- lastSyncAt: new Date().toISOString(),
7264
- longAssets: longAssets.map(asset => this.mapAssetToDetailDto(asset, syncResultsMap.get(`${asset.coin}-LONG`))),
7265
- shortAssets: shortAssets.map(asset => this.mapAssetToDetailDto(asset, syncResultsMap.get(`${asset.coin}-SHORT`))),
7266
- createdAt: position.createdAt,
7267
- updatedAt: position.updatedAt
7207
+ entryRatio: 1,
7208
+ marginUsed: 0,
7209
+ markRatio: 1,
7210
+ unrealizedPnl: 0,
7211
+ netFunding: 0,
7212
+ positionValue: 0,
7268
7213
  };
7269
- }
7270
- mapAssetToDetailDto(asset, syncData) {
7271
- const currentPrice = this.getMarketPrice(asset.coin);
7272
- const actualSize = (syncData === null || syncData === void 0 ? void 0 : syncData.actualSize) || Number(asset.size || 0);
7273
- const positionValue = actualSize * currentPrice;
7274
- const entryValue = Number(asset.size || 0) * Number(asset.entryPrice || 0);
7275
- return {
7276
- coin: asset.coin,
7277
- entryPrice: Number(asset.entryPrice || 0),
7278
- platformSize: Number(asset.size || 0),
7279
- actualSize: actualSize,
7280
- isExternallyModified: (syncData === null || syncData === void 0 ? void 0 : syncData.isExternallyModified) || false,
7281
- cumFunding: (syncData === null || syncData === void 0 ? void 0 : syncData.cumFunding) || {
7282
- allTime: 0,
7283
- sinceChange: 0,
7284
- sinceOpen: 0
7285
- },
7286
- marginUsed: entryValue,
7287
- positionValue: positionValue,
7288
- unrealizedPnl: (syncData === null || syncData === void 0 ? void 0 : syncData.unrealizedPnl) || 0,
7289
- liquidationPrice: (syncData === null || syncData === void 0 ? void 0 : syncData.liquidationPrice) || 0
7290
- };
7291
- }
7292
- calculateEntryRatio(syncResults) {
7293
- var _a, _b;
7294
- const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
7295
- const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
7296
- if (longResults.length === 0 || shortResults.length === 0)
7297
- return 0;
7298
- const longEntryPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.entryPrice) ? Number(longResults[0].asset.entryPrice) : 0;
7299
- const shortEntryPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.entryPrice) ? Number(shortResults[0].asset.entryPrice) : 0;
7300
- return shortEntryPrice > 0 ? longEntryPrice / shortEntryPrice : 0;
7301
- }
7302
- calculateMarkRatio(syncResults) {
7303
- var _a, _b;
7304
- const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
7305
- const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
7306
- if (longResults.length === 0 || shortResults.length === 0)
7307
- return 0;
7308
- const longMarkPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.coin) ? this.getMarketPrice(longResults[0].asset.coin) : 0;
7309
- const shortMarkPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.coin) ? this.getMarketPrice(shortResults[0].asset.coin) : 0;
7310
- return shortMarkPrice > 0 ? longMarkPrice / shortMarkPrice : 0;
7311
- }
7312
- calculateNetFundingFromSyncResults(syncResults) {
7313
- const netFunding = syncResults.reduce((sum, result) => {
7314
- const funding = result.cumFunding.sinceOpen;
7315
- return sum + funding;
7316
- }, 0);
7317
- return netFunding;
7318
- }
7319
- calculateTotalUnrealizedPnlFromSyncResults(syncResults) {
7320
- return syncResults.reduce((sum, result) => sum + result.unrealizedPnl, 0);
7321
- }
7322
- calculateCurrentTotalPositionValue(syncResults) {
7323
- return syncResults.reduce((sum, result) => {
7324
- const currentPrice = this.getMarketPrice(result.asset.coin);
7325
- return sum + (result.actualSize * currentPrice);
7326
- }, 0);
7327
- }
7328
- calculateEntryTotalPositionValue(syncResults) {
7329
- return syncResults.reduce((sum, result) => {
7330
- return sum + (Number(result.asset.size || 0) * Number(result.asset.entryPrice || 0));
7331
- }, 0);
7332
- }
7333
- }
7214
+ const totalInitialPositionSize = position.longAssets.reduce((acc, asset) => acc + (asset.entryPrice * asset.size), 0) +
7215
+ position.shortAssets.reduce((acc, asset) => acc + (asset.entryPrice * asset.size), 0);
7216
+ mappedPosition.longAssets = position.longAssets.map(longAsset => {
7217
+ var _a;
7218
+ const currentPrice = parseFloat(allMids.mids[longAsset.coin]);
7219
+ const cumFunding = (_a = webData2.clearinghouseState.assetPositions.find(ap => ap.position.coin === longAsset.coin)) === null || _a === void 0 ? void 0 : _a.position.cumFunding;
7220
+ const mappedPositionAssets = calculatePositionAsset(longAsset, currentPrice, totalInitialPositionSize, mappedPosition.leverage, cumFunding, true);
7221
+ mappedPosition.unrealizedPnl += mappedPositionAssets.unrealizedPnl;
7222
+ mappedPosition.positionValue += mappedPositionAssets.positionValue;
7223
+ mappedPosition.marginUsed += mappedPositionAssets.marginUsed;
7224
+ mappedPosition.netFunding += mappedPositionAssets.cumFunding.sinceOpen;
7225
+ // Calculate weighted entry and mark ratios for long positions
7226
+ mappedPosition.entryRatio *= Math.pow(longAsset.entryPrice, mappedPositionAssets.initialWeight);
7227
+ mappedPosition.markRatio *= Math.pow(currentPrice, mappedPositionAssets.initialWeight);
7228
+ return mappedPositionAssets;
7229
+ });
7230
+ mappedPosition.shortAssets = position.shortAssets.map(shortAsset => {
7231
+ var _a;
7232
+ const currentPrice = parseFloat(allMids.mids[shortAsset.coin]);
7233
+ const cumFunding = (_a = webData2.clearinghouseState.assetPositions.find(ap => ap.position.coin === shortAsset.coin)) === null || _a === void 0 ? void 0 : _a.position.cumFunding;
7234
+ const mappedPositionAssets = calculatePositionAsset(shortAsset, currentPrice, totalInitialPositionSize, mappedPosition.leverage, cumFunding, false);
7235
+ mappedPosition.netFunding += mappedPositionAssets.cumFunding.sinceOpen;
7236
+ mappedPosition.unrealizedPnl += mappedPositionAssets.unrealizedPnl;
7237
+ mappedPosition.positionValue += mappedPositionAssets.positionValue;
7238
+ mappedPosition.marginUsed += mappedPositionAssets.marginUsed;
7239
+ mappedPosition.entryRatio *= Math.pow(shortAsset.entryPrice, -mappedPositionAssets.initialWeight);
7240
+ mappedPosition.markRatio *= Math.pow(currentPrice, -mappedPositionAssets.initialWeight);
7241
+ return mappedPositionAssets;
7242
+ });
7243
+ return mappedPosition;
7244
+ });
7245
+ };
7334
7246
 
7335
7247
  const useTradeHistories = () => {
7336
7248
  const context = useContext(PearHyperliquidContext);
@@ -7360,9 +7272,8 @@ const useOpenPositions = () => {
7360
7272
  if (!userOpenPositions || !webData2 || !allMids) {
7361
7273
  return { data: null, isLoading };
7362
7274
  }
7363
- const processor = new PositionProcessor(webData2, allMids);
7364
- const processed = processor.execute(userOpenPositions);
7365
- return { data: processed, isLoading };
7275
+ const positions = buildPositionValue(userOpenPositions, webData2, allMids);
7276
+ return { data: positions, isLoading };
7366
7277
  };
7367
7278
  /**
7368
7279
  * Hook to access open orders with loading state
@@ -7400,138 +7311,6 @@ const useAccountSummary = () => {
7400
7311
  return { data: calculated, isLoading };
7401
7312
  };
7402
7313
 
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
7314
  // Re-expose as a React hook without Zustand's selector signature
7536
7315
  const useUserSelection = () => {
7537
7316
  return useUserSelection$1();
@@ -7842,6 +7621,25 @@ const useTokenSelectionMetadata = () => {
7842
7621
  const createKey = (symbol, interval) => {
7843
7622
  return `${symbol}-${interval}`;
7844
7623
  };
7624
+ const getIntervalSeconds = (interval) => {
7625
+ switch (interval) {
7626
+ case "1m": return 60;
7627
+ case "3m": return 3 * 60;
7628
+ case "5m": return 5 * 60;
7629
+ case "15m": return 15 * 60;
7630
+ case "30m": return 30 * 60;
7631
+ case "1h": return 60 * 60;
7632
+ case "2h": return 2 * 60 * 60;
7633
+ case "4h": return 4 * 60 * 60;
7634
+ case "8h": return 8 * 60 * 60;
7635
+ case "12h": return 12 * 60 * 60;
7636
+ case "1d": return 24 * 60 * 60;
7637
+ case "3d": return 3 * 24 * 60 * 60;
7638
+ case "1w": return 7 * 24 * 60 * 60;
7639
+ case "1M": return 30 * 24 * 60 * 60; // Approximate month
7640
+ default: return 60;
7641
+ }
7642
+ };
7845
7643
  const useHistoricalPriceDataStore = create((set, get) => ({
7846
7644
  historicalPriceData: {},
7847
7645
  loadingTokens: new Set(),
@@ -7891,8 +7689,11 @@ const useHistoricalPriceDataStore = create((set, get) => ({
7891
7689
  const tokenData = historicalPriceData[key];
7892
7690
  if (!tokenData || tokenData.oldestTime === null || tokenData.latestTime === null)
7893
7691
  return false;
7894
- // Check if our cached data covers the requested time range
7895
- return tokenData.oldestTime <= startTime && tokenData.latestTime >= endTime;
7692
+ const intervalMilisecond = getIntervalSeconds(interval) * 1000;
7693
+ const hasStartCoverage = tokenData.oldestTime <= startTime;
7694
+ const hasEndCoverage = tokenData.latestTime >= endTime ||
7695
+ (tokenData.latestTime + intervalMilisecond >= endTime);
7696
+ return hasStartCoverage && hasEndCoverage;
7896
7697
  },
7897
7698
  getHistoricalPriceData: (symbol, interval, startTime, endTime) => {
7898
7699
  const { historicalPriceData } = get();
@@ -7900,8 +7701,7 @@ const useHistoricalPriceDataStore = create((set, get) => ({
7900
7701
  const tokenData = historicalPriceData[key];
7901
7702
  if (!tokenData)
7902
7703
  return [];
7903
- // Filter candles within the requested time range
7904
- return tokenData.candles.filter(candle => candle.t >= startTime && candle.T <= endTime);
7704
+ return tokenData.candles.filter(candle => candle.t >= startTime && candle.t < endTime);
7905
7705
  },
7906
7706
  setTokenLoading: (symbol, loading) => {
7907
7707
  set(state => {
@@ -7948,8 +7748,6 @@ const useHistoricalPriceData = () => {
7948
7748
  const longTokens = useUserSelection$1((state) => state.longTokens);
7949
7749
  const shortTokens = useUserSelection$1((state) => state.shortTokens);
7950
7750
  const candleInterval = useUserSelection$1((state) => state.candleInterval);
7951
- // Get real-time candle data from WebSocket
7952
- const candleData = useHyperliquidData((state) => state.candleData);
7953
7751
  // Historical price data store
7954
7752
  const { addHistoricalPriceData, hasHistoricalPriceData: storeHasData, getHistoricalPriceData: storeGetData, setTokenLoading, isTokenLoading, removeTokenPriceData, clearData, } = useHistoricalPriceDataStore();
7955
7753
  // Track previous tokens and interval to detect changes
@@ -7980,32 +7778,20 @@ const useHistoricalPriceData = () => {
7980
7778
  prevTokensRef.current = currentTokens;
7981
7779
  prevIntervalRef.current = candleInterval;
7982
7780
  }, [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) => {
7781
+ const hasHistoricalPriceData = useCallback((startTime, endTime, interval) => {
7996
7782
  const allTokens = getAllTokens();
7997
7783
  if (allTokens.length === 0)
7998
7784
  return false;
7999
- return allTokens.every(token => storeHasData(token.symbol, candleInterval, startTime, endTime));
8000
- }, [getAllTokens, candleInterval, storeHasData]);
8001
- const getHistoricalPriceData = useCallback((startTime, endTime) => {
7785
+ return allTokens.every(token => storeHasData(token.symbol, interval, startTime, endTime));
7786
+ }, [getAllTokens, storeHasData]);
7787
+ const getHistoricalPriceData = useCallback((startTime, endTime, interval) => {
8002
7788
  const allTokens = getAllTokens();
8003
7789
  const result = {};
8004
7790
  allTokens.forEach(token => {
8005
- result[token.symbol] = storeGetData(token.symbol, candleInterval, startTime, endTime);
7791
+ result[token.symbol] = storeGetData(token.symbol, interval, startTime, endTime);
8006
7792
  });
8007
7793
  return result;
8008
- }, [getAllTokens, candleInterval, storeGetData]);
7794
+ }, [getAllTokens, storeGetData]);
8009
7795
  const isLoading = useCallback((symbol) => {
8010
7796
  if (symbol) {
8011
7797
  return isTokenLoading(symbol);
@@ -8013,16 +7799,19 @@ const useHistoricalPriceData = () => {
8013
7799
  const allTokens = getAllTokens();
8014
7800
  return allTokens.some(token => isTokenLoading(token.symbol));
8015
7801
  }, [getAllTokens, isTokenLoading]);
8016
- const fetchHistoricalPriceData = useCallback(async (startTime, endTime, callback) => {
7802
+ const getAllHistoricalPriceData = useCallback(async () => {
7803
+ return useHistoricalPriceDataStore.getState().historicalPriceData;
7804
+ }, []);
7805
+ const fetchHistoricalPriceData = useCallback(async (startTime, endTime, interval, callback) => {
8017
7806
  const allTokens = getAllTokens();
8018
7807
  if (allTokens.length === 0) {
8019
7808
  const emptyResult = {};
8020
7809
  callback === null || callback === void 0 ? void 0 : callback(emptyResult);
8021
7810
  return emptyResult;
8022
7811
  }
8023
- const tokensToFetch = allTokens.filter(token => !storeHasData(token.symbol, candleInterval, startTime, endTime));
7812
+ const tokensToFetch = allTokens.filter(token => !storeHasData(token.symbol, interval, startTime, endTime));
8024
7813
  if (tokensToFetch.length === 0) {
8025
- const cachedData = getHistoricalPriceData(startTime, endTime);
7814
+ const cachedData = getHistoricalPriceData(startTime, endTime, interval);
8026
7815
  callback === null || callback === void 0 ? void 0 : callback(cachedData);
8027
7816
  return cachedData;
8028
7817
  }
@@ -8032,8 +7821,8 @@ const useHistoricalPriceData = () => {
8032
7821
  try {
8033
7822
  const fetchPromises = tokensToFetch.map(async (token) => {
8034
7823
  try {
8035
- const response = await client.fetchHistoricalCandles(token.symbol, startTime, endTime, candleInterval);
8036
- addHistoricalPriceData(token.symbol, candleInterval, response.data, { start: startTime, end: endTime });
7824
+ const response = await client.fetchHistoricalCandles(token.symbol, startTime, endTime, interval);
7825
+ addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
8037
7826
  return { symbol: token.symbol, candles: response.data, success: true };
8038
7827
  }
8039
7828
  catch (error) {
@@ -8042,7 +7831,7 @@ const useHistoricalPriceData = () => {
8042
7831
  }
8043
7832
  });
8044
7833
  await Promise.all(fetchPromises);
8045
- const allData = getHistoricalPriceData(startTime, endTime);
7834
+ const allData = getHistoricalPriceData(startTime, endTime, interval);
8046
7835
  callback === null || callback === void 0 ? void 0 : callback(allData);
8047
7836
  return allData;
8048
7837
  }
@@ -8064,13 +7853,23 @@ const useHistoricalPriceData = () => {
8064
7853
  client,
8065
7854
  addHistoricalPriceData,
8066
7855
  ]);
7856
+ const fetchFirstCandle = useCallback(async (symbol, interval) => {
7857
+ const key = `${symbol}-${interval}`;
7858
+ const storeData = useHistoricalPriceDataStore.getState().historicalPriceData[key];
7859
+ if (storeData && storeData.candles.length > 0) {
7860
+ return storeData.candles[0];
7861
+ }
7862
+ return null;
7863
+ }, [client, addHistoricalPriceData]);
8067
7864
  const clearCache = useCallback(() => {
8068
7865
  clearData();
8069
7866
  }, [clearData]);
8070
7867
  return {
8071
7868
  fetchHistoricalPriceData,
8072
7869
  hasHistoricalPriceData,
7870
+ getAllHistoricalPriceData,
8073
7871
  getHistoricalPriceData,
7872
+ fetchFirstCandle,
8074
7873
  isLoading,
8075
7874
  clearCache,
8076
7875
  };
@@ -8104,7 +7903,7 @@ const calculateWeightedRatio = (longTokens, shortTokens, candleLookups, timestam
8104
7903
  const lookup = candleLookups[token.symbol];
8105
7904
  const candle = lookup === null || lookup === void 0 ? void 0 : lookup.get(timestamp);
8106
7905
  if (candle) {
8107
- const price = parseFloat(candle[priceType]);
7906
+ const price = candle[priceType];
8108
7907
  if (price > 0) {
8109
7908
  const weightFactor = token.weight / 100;
8110
7909
  longProduct *= Math.pow(price, weightFactor);
@@ -8119,7 +7918,7 @@ const calculateWeightedRatio = (longTokens, shortTokens, candleLookups, timestam
8119
7918
  const lookup = candleLookups[token.symbol];
8120
7919
  const candle = lookup === null || lookup === void 0 ? void 0 : lookup.get(timestamp);
8121
7920
  if (candle) {
8122
- const price = parseFloat(candle[priceType]);
7921
+ const price = candle[priceType];
8123
7922
  if (price > 0) {
8124
7923
  const weightFactor = token.weight / 100;
8125
7924
  shortProduct *= Math.pow(price, -weightFactor);
@@ -8181,7 +7980,7 @@ const getCompleteTimestamps = (candleLookups, requiredSymbols) => {
8181
7980
  */
8182
7981
  const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8183
7982
  var _a, _b;
8184
- if (longTokens.length === 0 || shortTokens.length === 0) {
7983
+ if (longTokens.length === 0 && shortTokens.length === 0) {
8185
7984
  return [];
8186
7985
  }
8187
7986
  // Create efficient lookup maps once
@@ -8193,8 +7992,6 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8193
7992
  // Compute all OHLC products in a single pass per side
8194
7993
  let longOpen = 1, longHigh = 1, longLow = 1, longClose = 1;
8195
7994
  let shortOpen = 1, shortHigh = 1, shortLow = 1, shortClose = 1;
8196
- let totalVolume = 0;
8197
- let totalTrades = 0;
8198
7995
  let candleDuration = 0;
8199
7996
  let missing = false;
8200
7997
  // Accumulate volume/trades and compute long side contributions
@@ -8205,10 +8002,10 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8205
8002
  break;
8206
8003
  }
8207
8004
  const weightFactor = token.weight / 100;
8208
- const o = parseFloat(candle.o);
8209
- const h = parseFloat(candle.h);
8210
- const l = parseFloat(candle.l);
8211
- const c = parseFloat(candle.c);
8005
+ const o = candle.o;
8006
+ const h = candle.h;
8007
+ const l = candle.l;
8008
+ const c = candle.c;
8212
8009
  if (!(o > 0 && h > 0 && l > 0 && c > 0)) {
8213
8010
  missing = true;
8214
8011
  break;
@@ -8217,8 +8014,6 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8217
8014
  longHigh *= Math.pow(h, weightFactor);
8218
8015
  longLow *= Math.pow(l, weightFactor);
8219
8016
  longClose *= Math.pow(c, weightFactor);
8220
- totalVolume += parseFloat(candle.v);
8221
- totalTrades += candle.n;
8222
8017
  if (candleDuration === 0)
8223
8018
  candleDuration = candle.T - candle.t;
8224
8019
  }
@@ -8232,10 +8027,10 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8232
8027
  break;
8233
8028
  }
8234
8029
  const weightFactor = -(token.weight / 100);
8235
- const o = parseFloat(candle.o);
8236
- const h = parseFloat(candle.h);
8237
- const l = parseFloat(candle.l);
8238
- const c = parseFloat(candle.c);
8030
+ const o = candle.o;
8031
+ const h = candle.h;
8032
+ const l = candle.l;
8033
+ const c = candle.c;
8239
8034
  if (!(o > 0 && h > 0 && l > 0 && c > 0)) {
8240
8035
  missing = true;
8241
8036
  break;
@@ -8244,8 +8039,6 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8244
8039
  shortHigh *= Math.pow(h, weightFactor);
8245
8040
  shortLow *= Math.pow(l, weightFactor);
8246
8041
  shortClose *= Math.pow(c, weightFactor);
8247
- totalVolume += parseFloat(candle.v);
8248
- totalTrades += candle.n;
8249
8042
  if (candleDuration === 0)
8250
8043
  candleDuration = candle.T - candle.t;
8251
8044
  }
@@ -8258,8 +8051,6 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8258
8051
  h: longHigh * shortHigh,
8259
8052
  l: longLow * shortLow,
8260
8053
  c: longClose * shortClose,
8261
- v: totalVolume,
8262
- n: totalTrades,
8263
8054
  });
8264
8055
  }
8265
8056
  return basketCandles;
@@ -8274,17 +8065,258 @@ const computeBasketCandles = (longTokens, shortTokens, tokenCandles) => {
8274
8065
  const useBasketCandles = () => {
8275
8066
  const longTokens = useUserSelection$1((state) => state.longTokens);
8276
8067
  const shortTokens = useUserSelection$1((state) => state.shortTokens);
8277
- const { fetchHistoricalPriceData, isLoading: tokenLoading } = useHistoricalPriceData();
8278
- const fetchBasketCandles = useCallback(async (startTime, endTime) => {
8279
- const tokenCandles = await fetchHistoricalPriceData(startTime, endTime);
8068
+ const candleData = useHyperliquidData((s) => s.candleData);
8069
+ const { fetchHistoricalPriceData, isLoading: tokenLoading, getAllHistoricalPriceData } = useHistoricalPriceData();
8070
+ const fetchBasketCandles = useCallback(async (startTime, endTime, interval) => {
8071
+ const tokenCandles = await fetchHistoricalPriceData(startTime, endTime, interval);
8280
8072
  const basket = computeBasketCandles(longTokens, shortTokens, tokenCandles);
8281
8073
  return basket;
8282
8074
  }, [fetchHistoricalPriceData, longTokens, shortTokens]);
8075
+ const fetchPerformanceCandles = useCallback(async (startTime, endTime, interval, symbol) => {
8076
+ const assetSymbol = symbol.split(" ")[1];
8077
+ const tokenCandles = await fetchHistoricalPriceData(startTime, endTime, interval);
8078
+ const assetCandles = tokenCandles[assetSymbol];
8079
+ if (!assetCandles)
8080
+ return [];
8081
+ return assetCandles;
8082
+ }, [fetchHistoricalPriceData, longTokens, shortTokens]);
8083
+ const fetchOverallPerformanceCandles = useCallback(async (startTime, endTime, interval) => {
8084
+ await fetchHistoricalPriceData(startTime, endTime, interval);
8085
+ const allCandles = await getAllHistoricalPriceData();
8086
+ const allTokens = [...longTokens, ...shortTokens];
8087
+ if (allTokens.length === 0)
8088
+ return [];
8089
+ const symbolsData = {};
8090
+ const baselinePrices = {};
8091
+ for (const token of allTokens) {
8092
+ const candles = allCandles[`${token.symbol}-${interval}`].candles;
8093
+ if (!candles || candles.length === 0)
8094
+ continue;
8095
+ symbolsData[token.symbol] = candles;
8096
+ const firstCandle = symbolsData[token.symbol][0];
8097
+ baselinePrices[token.symbol] = firstCandle.o;
8098
+ }
8099
+ if (Object.keys(symbolsData).length === 0)
8100
+ return [];
8101
+ const allTimestamps = new Set();
8102
+ const symbolCandlesByTimestamp = {};
8103
+ Object.entries(symbolsData).forEach(([symbol, candles]) => {
8104
+ const candleMap = new Map();
8105
+ candles.forEach(candle => {
8106
+ allTimestamps.add(candle.t);
8107
+ candleMap.set(candle.t, candle);
8108
+ });
8109
+ symbolCandlesByTimestamp[symbol] = candleMap;
8110
+ });
8111
+ const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
8112
+ const performanceCandles = [];
8113
+ const initialPortfolioValue = 1000;
8114
+ const lastKnownPrices = {};
8115
+ for (const timestamp of sortedTimestamps) {
8116
+ let portfolioValue = 0;
8117
+ let hasAllData = true;
8118
+ for (const token of allTokens) {
8119
+ const candleMap = symbolCandlesByTimestamp[token.symbol];
8120
+ if (!candleMap) {
8121
+ hasAllData = false;
8122
+ break;
8123
+ }
8124
+ let candle = candleMap.get(timestamp);
8125
+ if (!candle) {
8126
+ candle = lastKnownPrices[token.symbol];
8127
+ if (!candle) {
8128
+ hasAllData = false;
8129
+ break;
8130
+ }
8131
+ }
8132
+ else {
8133
+ lastKnownPrices[token.symbol] = candle;
8134
+ }
8135
+ const currentPrice = candle.c;
8136
+ const baselinePrice = baselinePrices[token.symbol];
8137
+ const allocation = (token.weight / 100) * initialPortfolioValue;
8138
+ const priceRatio = currentPrice / baselinePrice;
8139
+ if (longTokens.includes(token)) {
8140
+ portfolioValue += allocation * priceRatio;
8141
+ }
8142
+ else {
8143
+ portfolioValue += allocation / priceRatio;
8144
+ }
8145
+ }
8146
+ if (!hasAllData)
8147
+ continue;
8148
+ const performanceCandle = {
8149
+ t: timestamp,
8150
+ T: timestamp,
8151
+ o: portfolioValue,
8152
+ h: portfolioValue,
8153
+ l: portfolioValue,
8154
+ c: portfolioValue,
8155
+ };
8156
+ performanceCandles.push(performanceCandle);
8157
+ }
8158
+ return performanceCandles;
8159
+ }, [fetchHistoricalPriceData, getAllHistoricalPriceData, longTokens, shortTokens]);
8283
8160
  // Aggregate loading across selected tokens via underlying hook
8284
8161
  const isLoading = tokenLoading();
8162
+ // Realtime listeners management
8163
+ const listenersRef = useRef(new Map());
8164
+ const lastEmittedRef = useRef(null);
8165
+ const addRealtimeListener = useCallback((cb) => {
8166
+ const id = Math.random().toString(36).slice(2);
8167
+ listenersRef.current.set(id, cb);
8168
+ return id;
8169
+ }, []);
8170
+ const removeRealtimeListener = useCallback((id) => {
8171
+ listenersRef.current.delete(id);
8172
+ }, []);
8173
+ // Helper: compute weighted bar from latest snapshot if all tokens aligned
8174
+ const computeRealtimeBar = useCallback(() => {
8175
+ if (!candleData)
8176
+ return null;
8177
+ const allTokens = [...longTokens, ...shortTokens];
8178
+ if (allTokens.length === 0)
8179
+ return null;
8180
+ // Collect candles, ensure presence and alignment
8181
+ const symbolSet = new Set(allTokens.map((t) => t.symbol));
8182
+ const snapshot = {};
8183
+ for (const symbol of symbolSet) {
8184
+ const c = candleData.get(symbol);
8185
+ if (!c)
8186
+ return null; // missing latest candle for symbol
8187
+ snapshot[symbol] = c;
8188
+ }
8189
+ // Verify same interval window (t and T match across symbols)
8190
+ let t = null;
8191
+ let T = null;
8192
+ for (const symbol of symbolSet) {
8193
+ const c = snapshot[symbol];
8194
+ if (t === null) {
8195
+ t = c.t;
8196
+ T = c.T;
8197
+ }
8198
+ else if (c.t !== t || c.T !== T) {
8199
+ return null; // not aligned yet
8200
+ }
8201
+ }
8202
+ // Compute weighted OHLC similar to computeBasketCandles
8203
+ let longOpen = 1, longHigh = 1, longLow = 1, longClose = 1;
8204
+ let shortOpen = 1, shortHigh = 1, shortLow = 1, shortClose = 1;
8205
+ for (const token of longTokens) {
8206
+ const c = snapshot[token.symbol];
8207
+ if (!c)
8208
+ return null;
8209
+ const w = token.weight / 100;
8210
+ const o = c.o, h = c.h, l = c.l, cl = c.c;
8211
+ if (!(o > 0 && h > 0 && l > 0 && cl > 0))
8212
+ return null;
8213
+ longOpen *= Math.pow(o, w);
8214
+ longHigh *= Math.pow(h, w);
8215
+ longLow *= Math.pow(l, w);
8216
+ longClose *= Math.pow(cl, w);
8217
+ }
8218
+ for (const token of shortTokens) {
8219
+ const c = snapshot[token.symbol];
8220
+ if (!c)
8221
+ return null;
8222
+ const w = -(token.weight / 100);
8223
+ const o = c.o, h = c.h, l = c.l, cl = c.c;
8224
+ if (!(o > 0 && h > 0 && l > 0 && cl > 0))
8225
+ return null;
8226
+ shortOpen *= Math.pow(o, w);
8227
+ shortHigh *= Math.pow(h, w);
8228
+ shortLow *= Math.pow(l, w);
8229
+ shortClose *= Math.pow(cl, w);
8230
+ }
8231
+ if (t === null || T === null)
8232
+ return null;
8233
+ const weighted = {
8234
+ t,
8235
+ T,
8236
+ o: longOpen * shortOpen,
8237
+ h: longHigh * shortHigh,
8238
+ l: longLow * shortLow,
8239
+ c: longClose * shortClose,
8240
+ };
8241
+ return weighted;
8242
+ }, [candleData, longTokens, shortTokens]);
8243
+ // Emit realtime bars when aligned snapshot updates
8244
+ useEffect(() => {
8245
+ if (listenersRef.current.size === 0)
8246
+ return;
8247
+ const bar = computeRealtimeBar();
8248
+ if (!bar)
8249
+ return;
8250
+ lastEmittedRef.current = bar.t;
8251
+ const tvBar = {
8252
+ time: bar.t,
8253
+ open: bar.o,
8254
+ high: bar.h,
8255
+ low: bar.l,
8256
+ close: bar.c,
8257
+ };
8258
+ listenersRef.current.forEach((cb) => {
8259
+ try {
8260
+ cb(tvBar);
8261
+ }
8262
+ catch (e) { /* noop */ }
8263
+ });
8264
+ }, [computeRealtimeBar]);
8285
8265
  return {
8286
8266
  fetchBasketCandles,
8267
+ fetchPerformanceCandles,
8268
+ fetchOverallPerformanceCandles,
8287
8269
  isLoading,
8270
+ addRealtimeListener,
8271
+ removeRealtimeListener,
8272
+ };
8273
+ };
8274
+
8275
+ // Color palette for overlays
8276
+ const LONG_COLORS = [
8277
+ '#22C55E', '#16A34A', '#15803D', '#166534', '#14532D', // Green shades
8278
+ '#84CC16', '#65A30D', '#4D7C0F', '#365314', '#1A2E05' // Lime shades
8279
+ ];
8280
+ const SHORT_COLORS = [
8281
+ '#EF4444', '#DC2626', '#B91C1C', '#991B1B', '#7F1D1D', // Red shades
8282
+ '#F97316', '#EA580C', '#C2410C', '#9A3412', '#7C2D12' // Orange/red shades
8283
+ ];
8284
+ const usePerformanceOverlays = () => {
8285
+ const longTokens = useUserSelection$1((state) => state.longTokens);
8286
+ const shortTokens = useUserSelection$1((state) => state.shortTokens);
8287
+ // Generate performance overlays from current portfolio - all enabled by default
8288
+ const overlays = [
8289
+ ...longTokens.map((token, index) => ({
8290
+ id: `perf_long_${token.symbol}`,
8291
+ symbol: `LONG ${token.symbol}`,
8292
+ label: `${token.symbol} (${token.weight}%)`,
8293
+ color: LONG_COLORS[index % LONG_COLORS.length], // Green shades for long positions
8294
+ enabled: true, // Always enabled
8295
+ type: 'asset',
8296
+ weight: token.weight,
8297
+ })),
8298
+ ...shortTokens.map((token, index) => ({
8299
+ id: `perf_short_${token.symbol}`,
8300
+ symbol: `SHORT ${token.symbol}`,
8301
+ label: `${token.symbol} (-${token.weight}%)`,
8302
+ color: SHORT_COLORS[index % SHORT_COLORS.length], // Red shades for short positions
8303
+ enabled: true, // Always enabled
8304
+ type: 'asset',
8305
+ weight: -token.weight,
8306
+ }))
8307
+ ];
8308
+ const generateOverlaySymbols = useCallback(() => {
8309
+ const symbols = [];
8310
+ // Always include all asset overlays
8311
+ overlays.forEach(overlay => {
8312
+ symbols.push(overlay.symbol);
8313
+ });
8314
+ console.log("final symbols", symbols);
8315
+ return symbols;
8316
+ }, [overlays]);
8317
+ return {
8318
+ overlays,
8319
+ generateOverlaySymbols,
8288
8320
  };
8289
8321
  };
8290
8322
 
@@ -8330,4 +8362,49 @@ class ConflictDetector {
8330
8362
  }
8331
8363
  }
8332
8364
 
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 };
8365
+ /**
8366
+ * Maps TradingView ResolutionString to CandleInterval
8367
+ */
8368
+ function mapTradingViewIntervalToCandleInterval(interval) {
8369
+ switch (interval) {
8370
+ case '1': return '1m';
8371
+ case '3': return '3m';
8372
+ case '5': return '5m';
8373
+ case '15': return '15m';
8374
+ case '30': return '30m';
8375
+ case '60': return '1h';
8376
+ case '120': return '2h';
8377
+ case '240': return '4h';
8378
+ case '480': return '8h';
8379
+ case '720': return '12h';
8380
+ case '1D': return '1d';
8381
+ case '3D': return '3d';
8382
+ case '1W': return '1w';
8383
+ case '1M': return '1M';
8384
+ default: return '1h'; // fallback to 1 hour
8385
+ }
8386
+ }
8387
+ /**
8388
+ * Maps CandleInterval to TradingView ResolutionString
8389
+ */
8390
+ function mapCandleIntervalToTradingViewInterval(interval) {
8391
+ switch (interval) {
8392
+ case '1m': return '1';
8393
+ case '3m': return '3';
8394
+ case '5m': return '5';
8395
+ case '15m': return '15';
8396
+ case '30m': return '30';
8397
+ case '1h': return '60';
8398
+ case '2h': return '120';
8399
+ case '4h': return '240';
8400
+ case '8h': return '480';
8401
+ case '12h': return '720';
8402
+ case '1d': return '1D';
8403
+ case '3d': return '3D';
8404
+ case '1w': return '1W';
8405
+ case '1M': return '1M';
8406
+ default: return '60'; // fallback to 1 hour
8407
+ }
8408
+ }
8409
+
8410
+ 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, usePerformanceOverlays, useTokenSelectionMetadata, useTradeHistories, useUserSelection, useWebData };