UNPKG

@drift-labs/sdk

Version:
202 lines (201 loc) • 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateUserMaxPerpOrderSize = exports.calculateLiquidationPrice = exports.calculateCollateralValueOfDeposit = exports.calculateCollateralDepositRequiredForTrade = exports.calculateMarginUSDCRequiredForTrade = exports.calculatePerpLiabilityValue = exports.calculateWorstCasePerpLiabilityValue = exports.calculateWorstCaseBaseAssetAmount = exports.calculateBaseAssetValueWithOracle = exports.calculateOraclePriceForPerpMargin = exports.calculateSizeDiscountAssetWeight = exports.calculateSizePremiumLiabilityWeight = void 0; const utils_1 = require("./utils"); const numericConstants_1 = require("../constants/numericConstants"); const anchor_1 = require("@coral-xyz/anchor"); const market_1 = require("./market"); const spotBalance_1 = require("./spotBalance"); const oneShotUserAccountSubscriber_1 = require("../accounts/oneShotUserAccountSubscriber"); const user_1 = require("../user"); const types_1 = require("../types"); const assert_1 = require("../assert/assert"); function calculateSizePremiumLiabilityWeight(size, // AMM_RESERVE_PRECISION imfFactor, liabilityWeight, precision) { if (imfFactor.eq(numericConstants_1.ZERO)) { return liabilityWeight; } const sizeSqrt = (0, utils_1.squareRootBN)(size.abs().mul(new anchor_1.BN(10)).add(new anchor_1.BN(1))); //1e9 -> 1e10 -> 1e5 const liabilityWeightNumerator = liabilityWeight.sub(liabilityWeight.div(new anchor_1.BN(5))); const denom = new anchor_1.BN(100000).mul(numericConstants_1.SPOT_MARKET_IMF_PRECISION).div(precision); (0, assert_1.assert)(denom.gt(numericConstants_1.ZERO)); const sizePremiumLiabilityWeight = liabilityWeightNumerator.add(sizeSqrt // 1e5 .mul(imfFactor) .div(denom) // 1e5 ); const maxLiabilityWeight = anchor_1.BN.max(liabilityWeight, sizePremiumLiabilityWeight); return maxLiabilityWeight; } exports.calculateSizePremiumLiabilityWeight = calculateSizePremiumLiabilityWeight; function calculateSizeDiscountAssetWeight(size, // AMM_RESERVE_PRECISION imfFactor, assetWeight) { if (imfFactor.eq(numericConstants_1.ZERO)) { return assetWeight; } const sizeSqrt = (0, utils_1.squareRootBN)(size.abs().mul(new anchor_1.BN(10)).add(new anchor_1.BN(1))); //1e9 -> 1e10 -> 1e5 const imfNumerator = numericConstants_1.SPOT_MARKET_IMF_PRECISION.add(numericConstants_1.SPOT_MARKET_IMF_PRECISION.div(new anchor_1.BN(10))); const sizeDiscountAssetWeight = imfNumerator .mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION) .div(numericConstants_1.SPOT_MARKET_IMF_PRECISION.add(sizeSqrt // 1e5 .mul(imfFactor) .div(new anchor_1.BN(100000)) // 1e5 )); const minAssetWeight = anchor_1.BN.min(assetWeight, sizeDiscountAssetWeight); return minAssetWeight; } exports.calculateSizeDiscountAssetWeight = calculateSizeDiscountAssetWeight; function calculateOraclePriceForPerpMargin(perpPosition, market, oraclePriceData) { const oraclePriceOffset = anchor_1.BN.min(new anchor_1.BN(market.amm.maxSpread) .mul(oraclePriceData.price) .div(numericConstants_1.BID_ASK_SPREAD_PRECISION), oraclePriceData.confidence.add(new anchor_1.BN(market.amm.baseSpread) .mul(oraclePriceData.price) .div(numericConstants_1.BID_ASK_SPREAD_PRECISION))); let marginPrice; if (perpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) { marginPrice = oraclePriceData.price.sub(oraclePriceOffset); } else { marginPrice = oraclePriceData.price.add(oraclePriceOffset); } return marginPrice; } exports.calculateOraclePriceForPerpMargin = calculateOraclePriceForPerpMargin; /** * This is _not_ the same as liability value as for prediction markets, the liability for the short in prediction market is (1 - oracle price) * base * See {@link calculatePerpLiabilityValue} to get the liabiltiy value * @param market * @param perpPosition * @param oraclePriceData * @param includeOpenOrders */ function calculateBaseAssetValueWithOracle(market, perpPosition, oraclePriceData, includeOpenOrders = false) { let price = oraclePriceData.price; if ((0, types_1.isVariant)(market.status, 'settlement')) { price = market.expiryPrice; } const baseAssetAmount = includeOpenOrders ? calculateWorstCaseBaseAssetAmount(perpPosition, market, oraclePriceData.price) : perpPosition.baseAssetAmount; return baseAssetAmount.abs().mul(price).div(numericConstants_1.AMM_RESERVE_PRECISION); } exports.calculateBaseAssetValueWithOracle = calculateBaseAssetValueWithOracle; function calculateWorstCaseBaseAssetAmount(perpPosition, perpMarket, oraclePrice) { return calculateWorstCasePerpLiabilityValue(perpPosition, perpMarket, oraclePrice).worstCaseBaseAssetAmount; } exports.calculateWorstCaseBaseAssetAmount = calculateWorstCaseBaseAssetAmount; function calculateWorstCasePerpLiabilityValue(perpPosition, perpMarket, oraclePrice) { const allBids = perpPosition.baseAssetAmount.add(perpPosition.openBids); const allAsks = perpPosition.baseAssetAmount.add(perpPosition.openAsks); const isPredictionMarket = (0, types_1.isVariant)(perpMarket.contractType, 'prediction'); const allBidsLiabilityValue = calculatePerpLiabilityValue(allBids, oraclePrice, isPredictionMarket); const allAsksLiabilityValue = calculatePerpLiabilityValue(allAsks, oraclePrice, isPredictionMarket); if (allAsksLiabilityValue.gte(allBidsLiabilityValue)) { return { worstCaseBaseAssetAmount: allAsks, worstCaseLiabilityValue: allAsksLiabilityValue, }; } else { return { worstCaseBaseAssetAmount: allBids, worstCaseLiabilityValue: allBidsLiabilityValue, }; } } exports.calculateWorstCasePerpLiabilityValue = calculateWorstCasePerpLiabilityValue; function calculatePerpLiabilityValue(baseAssetAmount, price, isPredictionMarket) { if (isPredictionMarket) { if (baseAssetAmount.gt(numericConstants_1.ZERO)) { return baseAssetAmount.mul(price).div(numericConstants_1.BASE_PRECISION); } else { return baseAssetAmount .abs() .mul(numericConstants_1.MAX_PREDICTION_PRICE.sub(price)) .div(numericConstants_1.BASE_PRECISION); } } else { return baseAssetAmount.abs().mul(price).div(numericConstants_1.BASE_PRECISION); } } exports.calculatePerpLiabilityValue = calculatePerpLiabilityValue; /** * Calculates the margin required to open a trade, in quote amount. Only accounts for the trade size as a scalar value, does not account for the trade direction or current open positions and whether the trade would _actually_ be risk-increasing and use any extra collateral. * @param targetMarketIndex * @param baseSize * @returns */ function calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio, userHighLeverageMode, entryPrice) { const targetMarket = driftClient.getPerpMarketAccount(targetMarketIndex); const price = entryPrice !== null && entryPrice !== void 0 ? entryPrice : driftClient.getOracleDataForPerpMarket(targetMarket.marketIndex).price; const perpLiabilityValue = calculatePerpLiabilityValue(baseSize, price, (0, types_1.isVariant)(targetMarket.contractType, 'prediction')); const marginRequired = new anchor_1.BN((0, market_1.calculateMarketMarginRatio)(targetMarket, baseSize.abs(), 'Initial', userMaxMarginRatio, userHighLeverageMode)) .mul(perpLiabilityValue) .div(numericConstants_1.MARGIN_PRECISION); return marginRequired; } exports.calculateMarginUSDCRequiredForTrade = calculateMarginUSDCRequiredForTrade; /** * Similar to calculatetMarginUSDCRequiredForTrade, but calculates how much of a given collateral is required to cover the margin requirements for a given trade. Basically does the same thing as getMarginUSDCRequiredForTrade but also accounts for asset weight of the selected collateral. * * Returns collateral required in the precision of the target collateral market. */ function calculateCollateralDepositRequiredForTrade(driftClient, targetMarketIndex, baseSize, collateralIndex, userMaxMarginRatio, userHighLeverageMode, estEntryPrice) { const marginRequiredUsdc = calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio, userHighLeverageMode, estEntryPrice); const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex); const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex); const scaledAssetWeight = (0, spotBalance_1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price); // Base amount required to deposit = (marginRequiredUsdc / priceOfAsset) / assetWeight .. (E.g. $100 required / $10000 price / 0.5 weight) const baseAmountRequired = driftClient .convertToSpotPrecision(collateralIndex, marginRequiredUsdc) .mul(numericConstants_1.PRICE_PRECISION) // adjust for division by oracle price .mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION) // adjust for division by scaled asset weight .div(collateralOracleData.price) .div(scaledAssetWeight) .div(numericConstants_1.QUOTE_PRECISION); // adjust for marginRequiredUsdc value's QUOTE_PRECISION // TODO : Round by step size? return baseAmountRequired; } exports.calculateCollateralDepositRequiredForTrade = calculateCollateralDepositRequiredForTrade; function calculateCollateralValueOfDeposit(driftClient, collateralIndex, baseSize) { const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex); const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex); const scaledAssetWeight = (0, spotBalance_1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price); // CollateralBaseValue = oracle price * collateral base amount (and shift to QUOTE_PRECISION) const collateralBaseValue = collateralOracleData.price .mul(baseSize) .mul(numericConstants_1.QUOTE_PRECISION) .div(numericConstants_1.PRICE_PRECISION) .div(new anchor_1.BN(10).pow(new anchor_1.BN(collateralMarket.decimals))); const depositCollateralValue = collateralBaseValue .mul(scaledAssetWeight) .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION); return depositCollateralValue; } exports.calculateCollateralValueOfDeposit = calculateCollateralValueOfDeposit; function calculateLiquidationPrice(freeCollateral, freeCollateralDelta, oraclePrice) { const liqPriceDelta = freeCollateral .mul(numericConstants_1.QUOTE_PRECISION) .div(freeCollateralDelta); const liqPrice = oraclePrice.sub(liqPriceDelta); if (liqPrice.lt(numericConstants_1.ZERO)) { return new anchor_1.BN(-1); } return liqPrice; } exports.calculateLiquidationPrice = calculateLiquidationPrice; function calculateUserMaxPerpOrderSize(driftClient, userAccountKey, userAccount, targetMarketIndex, tradeSide) { const userAccountSubscriber = new oneShotUserAccountSubscriber_1.OneShotUserAccountSubscriber(driftClient.program, userAccountKey, userAccount); const user = new user_1.User({ driftClient, userAccountPublicKey: userAccountKey, accountSubscription: { type: 'custom', userAccountSubscriber: userAccountSubscriber, }, }); user.isSubscribed = true; return user.getMaxTradeSizeUSDCForPerp(targetMarketIndex, tradeSide); } exports.calculateUserMaxPerpOrderSize = calculateUserMaxPerpOrderSize;