diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava index 80feaeb..9af2cc8 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava @@ -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, diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava index 6f4de74..65c23af 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava @@ -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;