24: Calculate and expose liquidationPrice in JupiterPerpsPosition

This commit is contained in:
2026-08-05 10:12:38 +02:00
parent db20e68e93
commit f3e4947086
2 changed files with 82 additions and 3 deletions
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
* @param pnlPercent the profit and loss represented as a percentage
* @param leverage TODO
* @param entryPrice the entry price of the position, denominated in USDC
* @param liquidationPrice the estimated liquidation price of the position, denominated in USDC
* @param marketPrice the current spot price of the token
* @param collateral the amount of USD representing the collateral for this position
* @param totalFees the total amount of fees (TODO: is that including pending/due fees)
@@ -37,6 +38,7 @@ public record JupiterPerpsPosition(
BigDecimal pnlPercent,
BigDecimal leverage,
ΩUSDCPriceΩ entryPrice,
ΩUSDCPriceΩ liquidationPrice,
ΩUSDCPriceΩ marketPrice,
ΩUSDCAmountΩ collateral,
ΩUSDCAmountΩ totalFees,
@@ -1,6 +1,7 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain;
@@ -114,12 +115,34 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
ΩSPLMintAddressΩ mintAddress = custodyAccountInfo.mintAddress();
ΩJupiterPostionInterestΩ positionInterest = custodyAccountInfo.currentCumulativeInterestRate()
ΩJupiterPerpsCollateralCustodyAccountAddressΩ collateralCustodyAccountAddress =
perpsPositionInfo.collateralCustodyAccountAddress();
ΩJupiterCustodyAccountInfoEncodedΩ encodedCollateralCustodyAccountInfo =
solanaBlockChain.getAccountInfo(collateralCustodyAccountAddress);
if (encodedCollateralCustodyAccountInfo == null) {
String errorMsg = "Jupiter Perps collateral custody account does not exist: " + collateralCustodyAccountAddress;
throw new IllegalArgumentException(errorMsg);
}
JupiterCustodyAccountInfo collateralCustodyAccountInfo =
custodyDecoder.decode(encodedCollateralCustodyAccountInfo);
ΩJupiterPostionInterestΩ positionInterest = collateralCustodyAccountInfo.currentCumulativeInterestRate()
.subtract(perpsPositionInfo.cumulativeInterestSnapshot());
ΩUSDCAmountΩ positionSize = perpsPositionInfo.positionSize();
ΩUSDCAmountΩ borrowFeesDue = calculateBorrowFeesDue(positionInterest, positionSize);
ΩUSDCAmountΩ closeFeePending = calculateCloseFeePending(positionSize);
ΩUSDCPriceΩ liquidationPrice = calculateLiquidationPrice(
perpsPositionInfo.direction(),
perpsPositionInfo.entryPrice(),
perpsPositionInfo.collateral(),
closeFeePending,
borrowFeesDue,
positionSize
);
ΩUSDCAmountΩ value = ZERO; // TODO - Dummy
ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy
@@ -142,6 +165,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
pnlPercent,
leverage,
perpsPositionInfo.entryPrice(),
liquidationPrice,
marketPrice,
perpsPositionInfo.collateral(),
totalFees,
@@ -161,7 +185,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
.multiply(size)
.divide(
BigDecimal.valueOf(RATE_POWER),
6,
USDC_DECIMALS,
RoundingMode.CEILING
);
return fees;
@@ -172,19 +196,72 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
.multiply(BigDecimal.valueOf(CLOSE_FEE_BPS))
.divide(
BigDecimal.valueOf(BASIS_POINTS_DIVISOR),
6,
USDC_DECIMALS,
RoundingMode.CEILING
);
return fee;
}
private static ΩUSDCPriceΩ calculateLiquidationPrice(
JupiterPerpsPositionDirection direction,
ΩUSDCPriceΩ price,
ΩUSDCAmountΩ collateral,
ΩUSDCAmountΩ closeFeePending,
ΩUSDCAmountΩ borrowFeesDue,
ΩUSDCAmountΩ positionSize
) {
ΩUSDCAmountΩ minimumCollateral = positionSize.divide(
BigDecimal.valueOf(MAX_LEVERAGE),
USDC_DECIMALS,
RoundingMode.CEILING
);
ΩUSDCAmountΩ maxLoss = minimumCollateral
.add(closeFeePending)
.add(borrowFeesDue);
ΩUSDCAmountΩ maxPriceDiffAmount = maxLoss
.subtract(collateral)
.abs();
ΩUSDCPriceΩ maxPriceDiff = maxPriceDiffAmount
.multiply(price)
.divide(
positionSize,
USDC_DECIMALS,
RoundingMode.CEILING
);
boolean maxLossExceedsCollateral = maxLoss.compareTo(collateral) > 0;
if (direction == JupiterPerpsPositionDirection.LONG) {
if (maxLossExceedsCollateral) {
return price.add(maxPriceDiff);
}
return price.subtract(maxPriceDiff);
}
if (direction == JupiterPerpsPositionDirection.SHORT) {
if (maxLossExceedsCollateral) {
return price.subtract(maxPriceDiff);
}
return price.add(maxPriceDiff);
}
throw new IllegalArgumentException("Unsupported Jupiter Perps position direction: " + direction);
}
private static final long RATE_POWER = 1_000_000_000L;
private static final int CLOSE_FEE_BPS = 6;
private static final int BASIS_POINTS_DIVISOR = 10_000;
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
private static final int POSITION_OWNER_OFFSET = 8;
private static final int MAX_LEVERAGE = 500;
private static final int USDC_DECIMALS = 6;
private final SolanaBlockChain solanaBlockChain;
private final AnchorIdlJupiterPerpsPositionDecoder positionDecoder;