@pear-protocol/hyperliquid-sdk 0.0.7 → 0.0.8
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.d.ts +17 -12
- package/dist/index.esm.js +148 -131
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +148 -131
- package/dist/index.js.map +1 -1
- package/dist/utils/{position-calculator.d.ts → aggregate-position-calculator.d.ts} +16 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2962,9 +2962,9 @@ exports.PositionSide = void 0;
|
|
|
2962
2962
|
PositionSide["SHORT"] = "SHORT";
|
|
2963
2963
|
})(exports.PositionSide || (exports.PositionSide = {}));
|
|
2964
2964
|
/**
|
|
2965
|
-
*
|
|
2965
|
+
* Aggregate position calculation utility class that handles cross-position asset syncing
|
|
2966
2966
|
*/
|
|
2967
|
-
class
|
|
2967
|
+
class AggregatePositionCalculator {
|
|
2968
2968
|
constructor(webData2, allMids) {
|
|
2969
2969
|
this.webData2 = webData2;
|
|
2970
2970
|
this.allMids = allMids;
|
|
@@ -2987,7 +2987,7 @@ class PositionCalculator {
|
|
|
2987
2987
|
if (basePrice) {
|
|
2988
2988
|
return Number(basePrice);
|
|
2989
2989
|
}
|
|
2990
|
-
console.warn(`[
|
|
2990
|
+
console.warn(`[AggregatePositionCalculator] No market price found for coin: ${coin}`);
|
|
2991
2991
|
return 0;
|
|
2992
2992
|
}
|
|
2993
2993
|
/**
|
|
@@ -2999,19 +2999,16 @@ class PositionCalculator {
|
|
|
2999
2999
|
}
|
|
3000
3000
|
/**
|
|
3001
3001
|
* Calculate updated open positions by syncing platform positions with HyperLiquid data
|
|
3002
|
+
* Uses aggregate totals across all positions for accurate cross-position sync
|
|
3002
3003
|
*/
|
|
3003
3004
|
calculateOpenPositions(platformPositions) {
|
|
3004
3005
|
if (!(platformPositions === null || platformPositions === void 0 ? void 0 : platformPositions.length)) {
|
|
3005
3006
|
return [];
|
|
3006
3007
|
}
|
|
3007
3008
|
const hyperliquidPositions = this.getUserPositions();
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
* Sync a single position with HyperLiquid data
|
|
3012
|
-
*/
|
|
3013
|
-
syncPositionWithHyperliquid(position, hyperliquidPositions) {
|
|
3014
|
-
// Create a map of HyperLiquid positions by coin for quick lookup
|
|
3009
|
+
// Build a map of total platform sizes per asset across ALL positions
|
|
3010
|
+
const platformTotalsByAsset = this.calculatePlatformTotalsByAsset(platformPositions);
|
|
3011
|
+
// Create a map of HyperLiquid positions by coin
|
|
3015
3012
|
const hlPositionsMap = new Map();
|
|
3016
3013
|
hyperliquidPositions.forEach(assetPos => {
|
|
3017
3014
|
var _a;
|
|
@@ -3019,112 +3016,159 @@ class PositionCalculator {
|
|
|
3019
3016
|
hlPositionsMap.set(assetPos.position.coin, assetPos);
|
|
3020
3017
|
}
|
|
3021
3018
|
});
|
|
3022
|
-
//
|
|
3023
|
-
|
|
3024
|
-
...position.longAssets.map(asset => ({ ...asset, side: exports.PositionSide.LONG })),
|
|
3025
|
-
...position.shortAssets.map(asset => ({ ...asset, side: exports.PositionSide.SHORT }))
|
|
3026
|
-
];
|
|
3027
|
-
// Group assets by base currency for multi-pair position handling
|
|
3028
|
-
const assetsByBaseCurrency = this.groupAssetsByBaseCurrency(allAssets);
|
|
3029
|
-
// Sync each asset group with HyperLiquid data
|
|
3030
|
-
const syncResults = [];
|
|
3031
|
-
for (const [baseCurrency, assets] of assetsByBaseCurrency.entries()) {
|
|
3032
|
-
const hlPosition = hlPositionsMap.get(baseCurrency);
|
|
3033
|
-
const groupSyncResults = this.syncAssetGroupWithHyperliquid(assets, hlPosition);
|
|
3034
|
-
syncResults.push(...groupSyncResults);
|
|
3035
|
-
}
|
|
3036
|
-
// Update position sync status based on asset sync results
|
|
3037
|
-
const syncStatus = this.determineSyncStatus(syncResults);
|
|
3038
|
-
// Rebuild position with synced data
|
|
3039
|
-
return this.buildUpdatedPosition(position, syncResults, syncStatus);
|
|
3019
|
+
// Process each position with awareness of the aggregated totals
|
|
3020
|
+
return platformPositions.map(position => this.syncPositionWithAggregateData(position, hlPositionsMap, platformTotalsByAsset));
|
|
3040
3021
|
}
|
|
3041
3022
|
/**
|
|
3042
|
-
*
|
|
3023
|
+
* Calculate total platform sizes per asset across all positions
|
|
3043
3024
|
*/
|
|
3044
|
-
|
|
3045
|
-
const
|
|
3046
|
-
for (const
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3025
|
+
calculatePlatformTotalsByAsset(positions) {
|
|
3026
|
+
const totalsMap = new Map();
|
|
3027
|
+
for (const position of positions) {
|
|
3028
|
+
// Combine all assets from both sides
|
|
3029
|
+
const allAssets = [
|
|
3030
|
+
...position.longAssets.map(asset => ({ ...asset, side: exports.PositionSide.LONG })),
|
|
3031
|
+
...position.shortAssets.map(asset => ({ ...asset, side: exports.PositionSide.SHORT }))
|
|
3032
|
+
];
|
|
3033
|
+
for (const asset of allAssets) {
|
|
3034
|
+
const baseCurrency = this.extractBaseCurrency(asset.coin);
|
|
3035
|
+
if (!totalsMap.has(baseCurrency)) {
|
|
3036
|
+
totalsMap.set(baseCurrency, {
|
|
3037
|
+
totalSize: 0,
|
|
3038
|
+
positions: []
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
const totals = totalsMap.get(baseCurrency);
|
|
3042
|
+
const assetSize = asset.platformSize;
|
|
3043
|
+
totals.totalSize += assetSize;
|
|
3044
|
+
totals.positions.push({
|
|
3045
|
+
positionId: position.positionId,
|
|
3046
|
+
asset: asset,
|
|
3047
|
+
size: assetSize
|
|
3048
|
+
});
|
|
3050
3049
|
}
|
|
3051
|
-
grouped.get(baseCurrency).push(asset);
|
|
3052
3050
|
}
|
|
3053
|
-
return
|
|
3051
|
+
return totalsMap;
|
|
3054
3052
|
}
|
|
3055
3053
|
/**
|
|
3056
|
-
*
|
|
3054
|
+
* Extract base currency from asset name (handles "LINK/USD" -> "LINK")
|
|
3057
3055
|
*/
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
const
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
if (
|
|
3086
|
-
|
|
3087
|
-
allTime: Number(hlPosition.cumFunding.allTime || 0),
|
|
3088
|
-
sinceChange: Number(hlPosition.cumFunding.sinceChange || 0) * sizeRatio,
|
|
3089
|
-
sinceOpen: Number(hlPosition.cumFunding.sinceOpen || 0) * sizeRatio
|
|
3090
|
-
};
|
|
3056
|
+
extractBaseCurrency(assetName) {
|
|
3057
|
+
return assetName.split('/')[0] || assetName;
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Sync a single position with HyperLiquid data using aggregate totals
|
|
3061
|
+
*/
|
|
3062
|
+
syncPositionWithAggregateData(position, hlPositionsMap, platformTotalsByAsset) {
|
|
3063
|
+
const syncResults = [];
|
|
3064
|
+
let hasExternalModification = false;
|
|
3065
|
+
let allAssetsClosed = true;
|
|
3066
|
+
// Separate tracking for long and short sides
|
|
3067
|
+
let longAssetStatuses = { total: 0, closed: 0 };
|
|
3068
|
+
let shortAssetStatuses = { total: 0, closed: 0 };
|
|
3069
|
+
// Process all assets
|
|
3070
|
+
const allAssets = [
|
|
3071
|
+
...position.longAssets.map(asset => ({ ...asset, side: exports.PositionSide.LONG })),
|
|
3072
|
+
...position.shortAssets.map(asset => ({ ...asset, side: exports.PositionSide.SHORT }))
|
|
3073
|
+
];
|
|
3074
|
+
for (const asset of allAssets) {
|
|
3075
|
+
const baseCurrency = this.extractBaseCurrency(asset.coin);
|
|
3076
|
+
const hlPosition = hlPositionsMap.get(baseCurrency);
|
|
3077
|
+
const platformTotals = platformTotalsByAsset.get(baseCurrency);
|
|
3078
|
+
const syncResult = this.syncAssetWithAggregateData(asset, hlPosition, (platformTotals === null || platformTotals === void 0 ? void 0 : platformTotals.totalSize) || 0);
|
|
3079
|
+
syncResults.push(syncResult);
|
|
3080
|
+
// Track status by side
|
|
3081
|
+
if (asset.side === exports.PositionSide.LONG) {
|
|
3082
|
+
longAssetStatuses.total++;
|
|
3083
|
+
if (syncResult.actualSize === 0) {
|
|
3084
|
+
longAssetStatuses.closed++;
|
|
3091
3085
|
}
|
|
3092
|
-
unrealizedPnl = Number(hlPosition.position.unrealizedPnl || 0) * sizeRatio;
|
|
3093
|
-
liquidationPrice = Number(hlPosition.position.liquidationPx || 0);
|
|
3094
3086
|
}
|
|
3095
|
-
else {
|
|
3096
|
-
|
|
3097
|
-
actualSize
|
|
3098
|
-
|
|
3087
|
+
else if (asset.side === exports.PositionSide.SHORT) {
|
|
3088
|
+
shortAssetStatuses.total++;
|
|
3089
|
+
if (syncResult.actualSize === 0) {
|
|
3090
|
+
shortAssetStatuses.closed++;
|
|
3091
|
+
}
|
|
3099
3092
|
}
|
|
3100
|
-
|
|
3093
|
+
// Update flags
|
|
3094
|
+
if (syncResult.isExternallyModified) {
|
|
3095
|
+
hasExternalModification = true;
|
|
3096
|
+
}
|
|
3097
|
+
if (syncResult.actualSize !== 0) {
|
|
3098
|
+
allAssetsClosed = false;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
// Determine sync status
|
|
3102
|
+
const syncStatus = this.determineSyncStatus(hasExternalModification, allAssetsClosed, longAssetStatuses, shortAssetStatuses);
|
|
3103
|
+
return this.buildUpdatedPosition(position, syncResults, syncStatus);
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Sync individual asset with aggregate data awareness
|
|
3107
|
+
*/
|
|
3108
|
+
syncAssetWithAggregateData(asset, hlPosition, platformTotal) {
|
|
3109
|
+
var _a, _b, _c;
|
|
3110
|
+
const platformSize = asset.platformSize;
|
|
3111
|
+
// No position on HyperLiquid - asset was closed
|
|
3112
|
+
if (!hlPosition || !hlPosition.position || !hlPosition.position.szi) {
|
|
3113
|
+
return {
|
|
3101
3114
|
asset,
|
|
3102
|
-
actualSize,
|
|
3103
|
-
isExternallyModified,
|
|
3104
|
-
cumFunding,
|
|
3105
|
-
unrealizedPnl,
|
|
3106
|
-
liquidationPrice,
|
|
3115
|
+
actualSize: 0,
|
|
3116
|
+
isExternallyModified: true,
|
|
3117
|
+
cumFunding: { allTime: 0, sinceChange: 0, sinceOpen: 0 },
|
|
3118
|
+
unrealizedPnl: 0,
|
|
3119
|
+
liquidationPrice: 0,
|
|
3107
3120
|
side: asset.side
|
|
3108
|
-
}
|
|
3121
|
+
};
|
|
3109
3122
|
}
|
|
3110
|
-
|
|
3123
|
+
const hlTotalSize = Math.abs(Number(hlPosition.position.szi || 0));
|
|
3124
|
+
// Check if the TOTAL platform size matches HyperLiquid total
|
|
3125
|
+
const totalDifference = Math.abs(hlTotalSize - platformTotal);
|
|
3126
|
+
const tolerance = platformTotal * 0.001; // 0.1% tolerance
|
|
3127
|
+
const isExternallyModified = totalDifference > tolerance;
|
|
3128
|
+
// Calculate this position's proportional share of the HyperLiquid position
|
|
3129
|
+
const proportion = platformTotal > 0 ? platformSize / platformTotal : 0;
|
|
3130
|
+
const actualSize = hlTotalSize * proportion;
|
|
3131
|
+
// Distribute funding proportionally
|
|
3132
|
+
const cumFunding = {
|
|
3133
|
+
allTime: Number(((_a = hlPosition.cumFunding) === null || _a === void 0 ? void 0 : _a.allTime) || 0),
|
|
3134
|
+
sinceChange: Number(((_b = hlPosition.cumFunding) === null || _b === void 0 ? void 0 : _b.sinceChange) || 0) * proportion,
|
|
3135
|
+
sinceOpen: Number(((_c = hlPosition.cumFunding) === null || _c === void 0 ? void 0 : _c.sinceOpen) || 0) * proportion
|
|
3136
|
+
};
|
|
3137
|
+
// Distribute PnL proportionally
|
|
3138
|
+
const unrealizedPnl = Number(hlPosition.position.unrealizedPnl || 0) * proportion;
|
|
3139
|
+
// Liquidation price is the same for all positions of the same asset
|
|
3140
|
+
const liquidationPrice = Number(hlPosition.position.liquidationPx || 0);
|
|
3141
|
+
return {
|
|
3142
|
+
asset,
|
|
3143
|
+
actualSize,
|
|
3144
|
+
isExternallyModified,
|
|
3145
|
+
cumFunding,
|
|
3146
|
+
unrealizedPnl,
|
|
3147
|
+
liquidationPrice,
|
|
3148
|
+
side: asset.side
|
|
3149
|
+
};
|
|
3111
3150
|
}
|
|
3112
3151
|
/**
|
|
3113
|
-
* Determine sync status
|
|
3152
|
+
* Determine sync status with sophisticated side-aware logic
|
|
3114
3153
|
*/
|
|
3115
|
-
determineSyncStatus(
|
|
3116
|
-
|
|
3117
|
-
const allAssetsClosed = syncResults.every(result => result.actualSize === 0);
|
|
3118
|
-
const someAssetsClosed = syncResults.some(result => result.actualSize === 0) && !allAssetsClosed;
|
|
3154
|
+
determineSyncStatus(hasExternalModification, allAssetsClosed, longAssetStatuses, shortAssetStatuses) {
|
|
3155
|
+
// All assets closed externally
|
|
3119
3156
|
if (allAssetsClosed) {
|
|
3120
3157
|
return 'EXTERNALLY_CLOSED';
|
|
3121
3158
|
}
|
|
3122
|
-
|
|
3159
|
+
// Check if pair is broken (one entire side closed)
|
|
3160
|
+
const allLongsClosed = longAssetStatuses.total > 0 &&
|
|
3161
|
+
longAssetStatuses.closed === longAssetStatuses.total;
|
|
3162
|
+
const allShortsClosed = shortAssetStatuses.total > 0 &&
|
|
3163
|
+
shortAssetStatuses.closed === shortAssetStatuses.total;
|
|
3164
|
+
if ((allLongsClosed && !allShortsClosed) || (!allLongsClosed && allShortsClosed)) {
|
|
3123
3165
|
return 'PAIR_BROKEN';
|
|
3124
3166
|
}
|
|
3125
|
-
|
|
3167
|
+
// External modification detected
|
|
3168
|
+
if (hasExternalModification) {
|
|
3126
3169
|
return 'EXTERNALLY_MODIFIED';
|
|
3127
3170
|
}
|
|
3171
|
+
// Everything synced properly
|
|
3128
3172
|
return 'SYNCED';
|
|
3129
3173
|
}
|
|
3130
3174
|
/**
|
|
@@ -3171,65 +3215,38 @@ class PositionCalculator {
|
|
|
3171
3215
|
};
|
|
3172
3216
|
}
|
|
3173
3217
|
/**
|
|
3174
|
-
* Calculate entry ratio
|
|
3218
|
+
* Calculate entry ratio using actual sizes from sync results
|
|
3175
3219
|
*/
|
|
3176
3220
|
calculateEntryRatio(syncResults) {
|
|
3177
3221
|
const longResults = syncResults.filter(result => result.side === exports.PositionSide.LONG);
|
|
3178
3222
|
const shortResults = syncResults.filter(result => result.side === exports.PositionSide.SHORT);
|
|
3179
3223
|
if (longResults.length === 0 || shortResults.length === 0)
|
|
3180
3224
|
return 0;
|
|
3181
|
-
|
|
3182
|
-
const totalPositionValue = syncResults.reduce((sum, result) => {
|
|
3225
|
+
const longValue = longResults.reduce((sum, result) => {
|
|
3183
3226
|
return sum + (result.actualSize * result.asset.entryPrice);
|
|
3184
3227
|
}, 0);
|
|
3185
|
-
|
|
3186
|
-
return
|
|
3187
|
-
// Calculate weighted long portfolio entry value
|
|
3188
|
-
const weightedLongValue = longResults.reduce((sum, result) => {
|
|
3189
|
-
const entryPrice = result.asset.entryPrice;
|
|
3190
|
-
const assetValue = result.actualSize * entryPrice;
|
|
3191
|
-
const weight = assetValue / totalPositionValue;
|
|
3192
|
-
return sum + (entryPrice * weight);
|
|
3193
|
-
}, 0);
|
|
3194
|
-
// Calculate weighted short portfolio entry value
|
|
3195
|
-
const weightedShortValue = shortResults.reduce((sum, result) => {
|
|
3196
|
-
const entryPrice = result.asset.entryPrice;
|
|
3197
|
-
const assetValue = result.actualSize * entryPrice;
|
|
3198
|
-
const weight = assetValue / totalPositionValue;
|
|
3199
|
-
return sum + (entryPrice * weight);
|
|
3228
|
+
const shortValue = shortResults.reduce((sum, result) => {
|
|
3229
|
+
return sum + (result.actualSize * result.asset.entryPrice);
|
|
3200
3230
|
}, 0);
|
|
3201
|
-
return
|
|
3231
|
+
return shortValue > 0 ? longValue / shortValue : 0;
|
|
3202
3232
|
}
|
|
3203
3233
|
/**
|
|
3204
|
-
* Calculate mark ratio
|
|
3234
|
+
* Calculate mark ratio using actual sizes and current prices
|
|
3205
3235
|
*/
|
|
3206
3236
|
calculateMarkRatio(syncResults) {
|
|
3207
3237
|
const longResults = syncResults.filter(result => result.side === exports.PositionSide.LONG);
|
|
3208
3238
|
const shortResults = syncResults.filter(result => result.side === exports.PositionSide.SHORT);
|
|
3209
3239
|
if (longResults.length === 0 || shortResults.length === 0)
|
|
3210
3240
|
return 0;
|
|
3211
|
-
|
|
3212
|
-
const totalPositionValue = syncResults.reduce((sum, result) => {
|
|
3241
|
+
const longValue = longResults.reduce((sum, result) => {
|
|
3213
3242
|
const currentPrice = this.getMarketPrice(result.asset.coin);
|
|
3214
3243
|
return sum + (result.actualSize * currentPrice);
|
|
3215
3244
|
}, 0);
|
|
3216
|
-
|
|
3217
|
-
return 0;
|
|
3218
|
-
// Calculate weighted long portfolio value
|
|
3219
|
-
const weightedLongValue = longResults.reduce((sum, result) => {
|
|
3245
|
+
const shortValue = shortResults.reduce((sum, result) => {
|
|
3220
3246
|
const currentPrice = this.getMarketPrice(result.asset.coin);
|
|
3221
|
-
|
|
3222
|
-
const weight = assetValue / totalPositionValue;
|
|
3223
|
-
return sum + (currentPrice * weight);
|
|
3224
|
-
}, 0);
|
|
3225
|
-
// Calculate weighted short portfolio value
|
|
3226
|
-
const weightedShortValue = shortResults.reduce((sum, result) => {
|
|
3227
|
-
const currentPrice = this.getMarketPrice(result.asset.coin);
|
|
3228
|
-
const assetValue = result.actualSize * currentPrice;
|
|
3229
|
-
const weight = assetValue / totalPositionValue;
|
|
3230
|
-
return sum + (currentPrice * weight);
|
|
3247
|
+
return sum + (result.actualSize * currentPrice);
|
|
3231
3248
|
}, 0);
|
|
3232
|
-
return
|
|
3249
|
+
return shortValue > 0 ? longValue / shortValue : 0;
|
|
3233
3250
|
}
|
|
3234
3251
|
/**
|
|
3235
3252
|
* Calculate net funding from sync results
|
|
@@ -3286,7 +3303,7 @@ const useCalculatedOpenPositions = (platformPositions, webData2, allMids) => {
|
|
|
3286
3303
|
availableMids: Object.keys(allMids.mids || {}).length
|
|
3287
3304
|
});
|
|
3288
3305
|
// Create calculator and compute positions
|
|
3289
|
-
const calculator = new
|
|
3306
|
+
const calculator = new AggregatePositionCalculator(webData2, allMids);
|
|
3290
3307
|
const calculated = calculator.calculateOpenPositions(platformPositions);
|
|
3291
3308
|
console.log('[useCalculatedOpenPositions] Calculation completed', {
|
|
3292
3309
|
inputCount: platformPositions.length,
|
|
@@ -3477,10 +3494,10 @@ const useAccountSummary = () => {
|
|
|
3477
3494
|
};
|
|
3478
3495
|
|
|
3479
3496
|
exports.AccountSummaryCalculator = AccountSummaryCalculator;
|
|
3497
|
+
exports.AggregatePositionCalculator = AggregatePositionCalculator;
|
|
3480
3498
|
exports.PearHyperliquidClient = PearHyperliquidClient;
|
|
3481
3499
|
exports.PearHyperliquidProvider = PearHyperliquidProvider;
|
|
3482
3500
|
exports.PearMigrationSDK = PearMigrationSDK;
|
|
3483
|
-
exports.PositionCalculator = PositionCalculator;
|
|
3484
3501
|
exports.default = PearHyperliquidClient;
|
|
3485
3502
|
exports.useAccountSummary = useAccountSummary;
|
|
3486
3503
|
exports.useAddress = useAddress;
|