@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/hooks/index.d.ts +1 -0
- package/dist/hooks/useBasketCandles.d.ts +15 -2
- package/dist/hooks/useHistoricalPriceData.d.ts +7 -4
- package/dist/hooks/usePerformanceOverlays.d.ts +14 -0
- package/dist/hyperliquid-websocket.d.ts +1 -3
- package/dist/index.d.ts +67 -43
- package/dist/index.js +576 -499
- package/dist/store/userSelection.d.ts +1 -0
- package/dist/types.d.ts +7 -22
- package/dist/utils/basket-calculator.d.ts +2 -2
- package/dist/utils/chart-interval-mappers.d.ts +9 -0
- package/dist/utils/position-processor.d.ts +2 -22
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import require$$0, { useState, useEffect, useRef, createContext,
|
|
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
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
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
|
|
6667
|
-
return state;
|
|
6668
|
-
|
|
6669
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
6812
|
-
const
|
|
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(
|
|
6839
|
-
tokensToSubscribe.forEach(token => deleteActiveAssetData(token));
|
|
6965
|
+
setSubscribedTokens(effectiveTokens.filter((token) => token));
|
|
6966
|
+
tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
|
|
6840
6967
|
}
|
|
6841
|
-
}, [isConnected, address,
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
6863
|
-
const tokensToUnsubscribe = subscribedCandleTokens.filter(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 ||
|
|
6890
|
-
setSubscribedCandleTokens(
|
|
6891
|
-
|
|
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,
|
|
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
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
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
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
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
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
netFunding:
|
|
7260
|
-
positionValue:
|
|
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
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
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
|
|
7364
|
-
|
|
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
|
-
|
|
7895
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
8000
|
-
}, [getAllTokens,
|
|
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,
|
|
7791
|
+
result[token.symbol] = storeGetData(token.symbol, interval, startTime, endTime);
|
|
8006
7792
|
});
|
|
8007
7793
|
return result;
|
|
8008
|
-
}, [getAllTokens,
|
|
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
|
|
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,
|
|
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,
|
|
8036
|
-
addHistoricalPriceData(token.symbol,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
8209
|
-
const h =
|
|
8210
|
-
const l =
|
|
8211
|
-
const 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 =
|
|
8236
|
-
const h =
|
|
8237
|
-
const l =
|
|
8238
|
-
const 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
|
|
8278
|
-
const
|
|
8279
|
-
|
|
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
|
-
|
|
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 };
|