@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.js CHANGED
@@ -2962,9 +2962,9 @@ exports.PositionSide = void 0;
2962
2962
  PositionSide["SHORT"] = "SHORT";
2963
2963
  })(exports.PositionSide || (exports.PositionSide = {}));
2964
2964
  /**
2965
- * Position calculation utility class
2965
+ * Aggregate position calculation utility class that handles cross-position asset syncing
2966
2966
  */
2967
- class PositionCalculator {
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(`[PositionCalculator] No market price found for coin: ${coin}`);
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
- return platformPositions.map(position => this.syncPositionWithHyperliquid(position, hyperliquidPositions));
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
- // Combine all assets for processing
3023
- const allAssets = [
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
- * Group assets by their base currency
3023
+ * Calculate total platform sizes per asset across all positions
3043
3024
  */
3044
- groupAssetsByBaseCurrency(assets) {
3045
- const grouped = new Map();
3046
- for (const asset of assets) {
3047
- const baseCurrency = asset.coin.split('/')[0] || asset.coin;
3048
- if (!grouped.has(baseCurrency)) {
3049
- grouped.set(baseCurrency, []);
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 grouped;
3051
+ return totalsMap;
3054
3052
  }
3055
3053
  /**
3056
- * Sync a group of assets (same base currency) with HyperLiquid position data
3054
+ * Extract base currency from asset name (handles "LINK/USD" -> "LINK")
3057
3055
  */
3058
- syncAssetGroupWithHyperliquid(assets, hlPosition) {
3059
- const results = [];
3060
- // Calculate total platform size for this base currency
3061
- const totalPlatformSize = assets.reduce((sum, asset) => sum + asset.platformSize, 0);
3062
- const actualTotalSize = hlPosition ? Math.abs(Number(hlPosition.position.szi || 0)) : 0;
3063
- // Check if the total size matches (within tolerance)
3064
- const sizeDifference = Math.abs(actualTotalSize - totalPlatformSize);
3065
- const sizeDifferencePercentage = totalPlatformSize > 0
3066
- ? (sizeDifference / totalPlatformSize) * 100
3067
- : (actualTotalSize > 0 ? 100 : 0);
3068
- const isGroupExternallyModified = sizeDifferencePercentage > 0.1; // 0.1% tolerance
3069
- for (const asset of assets) {
3070
- const platformSize = asset.platformSize;
3071
- let actualSize = platformSize;
3072
- let isExternallyModified = isGroupExternallyModified;
3073
- let cumFunding = {
3074
- allTime: 0,
3075
- sinceChange: 0,
3076
- sinceOpen: 0
3077
- };
3078
- let unrealizedPnl = 0;
3079
- let liquidationPrice = 0;
3080
- if (hlPosition) {
3081
- // Proportionally distribute the actual size based on asset's contribution
3082
- const sizeRatio = totalPlatformSize > 0 ? platformSize / totalPlatformSize : 0;
3083
- actualSize = actualTotalSize * sizeRatio;
3084
- // Proportionally distribute funding and PnL based on asset's contribution
3085
- if (hlPosition.cumFunding) {
3086
- cumFunding = {
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
- // Position doesn't exist on HyperLiquid - it was closed externally
3097
- actualSize = 0;
3098
- isExternallyModified = true;
3087
+ else if (asset.side === exports.PositionSide.SHORT) {
3088
+ shortAssetStatuses.total++;
3089
+ if (syncResult.actualSize === 0) {
3090
+ shortAssetStatuses.closed++;
3091
+ }
3099
3092
  }
3100
- results.push({
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
- return results;
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 based on asset sync results
3152
+ * Determine sync status with sophisticated side-aware logic
3114
3153
  */
3115
- determineSyncStatus(syncResults) {
3116
- const hasExternalModifications = syncResults.some(result => result.isExternallyModified);
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
- else if (someAssetsClosed) {
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
- else if (hasExternalModifications) {
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 (weighted long entry value / weighted short entry value)
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
- // Calculate total position value at entry prices
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
- if (totalPositionValue === 0)
3186
- return 0;
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 weightedShortValue > 0 ? weightedLongValue / weightedShortValue : 0;
3231
+ return shortValue > 0 ? longValue / shortValue : 0;
3202
3232
  }
3203
3233
  /**
3204
- * Calculate mark ratio (weighted long mark value / weighted short mark value)
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
- // Calculate total position value at current market prices
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
- if (totalPositionValue === 0)
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
- const assetValue = result.actualSize * currentPrice;
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 weightedShortValue > 0 ? weightedLongValue / weightedShortValue : 0;
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 PositionCalculator(webData2, allMids);
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;