UNPKG

@drift-labs/sdk-browser

Version:
250 lines (249 loc) 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTriggerPrice = exports.calculatePerpMarketBaseLiquidatorFee = exports.calculateAvailablePerpLiquidity = exports.calculateNetUserPnlImbalance = exports.calculateNetUserPnl = exports.calculateMarketMaxAvailableInsurance = exports.calculateMarketAvailablePNL = exports.calculateUnrealizedAssetWeight = exports.calculateMarketMarginRatio = exports.calculateOracleSpread = exports.calculateOracleReserveSpread = exports.calculateNewMarketAfterTrade = exports.calculateAskPrice = exports.calculateBidPrice = exports.calculateReservePrice = void 0; const anchor_1 = require("@coral-xyz/anchor"); const types_1 = require("../types"); const amm_1 = require("./amm"); const margin_1 = require("./margin"); const numericConstants_1 = require("../constants/numericConstants"); const spotBalance_1 = require("./spotBalance"); const assert_1 = require("../assert/assert"); /** * Calculates market mark price * * @param market * @return markPrice : Precision PRICE_PRECISION */ function calculateReservePrice(market, mmOraclePriceData) { const newAmm = (0, amm_1.calculateUpdatedAMM)(market.amm, mmOraclePriceData); return (0, amm_1.calculatePrice)(newAmm.baseAssetReserve, newAmm.quoteAssetReserve, newAmm.pegMultiplier); } exports.calculateReservePrice = calculateReservePrice; /** * Calculates market bid price * * @param market * @return bidPrice : Precision PRICE_PRECISION */ function calculateBidPrice(market, mmOraclePriceData, latestSlot) { const { baseAssetReserve, quoteAssetReserve, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, types_1.PositionDirection.SHORT, mmOraclePriceData, undefined, latestSlot); return (0, amm_1.calculatePrice)(baseAssetReserve, quoteAssetReserve, newPeg); } exports.calculateBidPrice = calculateBidPrice; /** * Calculates market ask price * * @param market * @return askPrice : Precision PRICE_PRECISION */ function calculateAskPrice(market, mmOraclePriceData, latestSlot) { const { baseAssetReserve, quoteAssetReserve, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, types_1.PositionDirection.LONG, mmOraclePriceData, undefined, latestSlot); return (0, amm_1.calculatePrice)(baseAssetReserve, quoteAssetReserve, newPeg); } exports.calculateAskPrice = calculateAskPrice; function calculateNewMarketAfterTrade(baseAssetAmount, direction, market) { const [newQuoteAssetReserve, newBaseAssetReserve] = (0, amm_1.calculateAmmReservesAfterSwap)(market.amm, 'base', baseAssetAmount.abs(), (0, amm_1.getSwapDirection)('base', direction)); const newAmm = Object.assign({}, market.amm); const newMarket = Object.assign({}, market); newMarket.amm = newAmm; newMarket.amm.quoteAssetReserve = newQuoteAssetReserve; newMarket.amm.baseAssetReserve = newBaseAssetReserve; return newMarket; } exports.calculateNewMarketAfterTrade = calculateNewMarketAfterTrade; function calculateOracleReserveSpread(market, mmOraclePriceData) { const reservePrice = calculateReservePrice(market, mmOraclePriceData); return calculateOracleSpread(reservePrice, mmOraclePriceData); } exports.calculateOracleReserveSpread = calculateOracleReserveSpread; function calculateOracleSpread(price, oraclePriceData) { return price.sub(oraclePriceData.price); } exports.calculateOracleSpread = calculateOracleSpread; function calculateMarketMarginRatio(market, size, marginCategory, customMarginRatio = 0, userHighLeverageMode = false) { let marginRationInitial; let marginRatioMaintenance; if (userHighLeverageMode && market.highLeverageMarginRatioInitial > 0 && market.highLeverageMarginRatioMaintenance) { marginRationInitial = market.highLeverageMarginRatioInitial; marginRatioMaintenance = market.highLeverageMarginRatioMaintenance; } else { marginRationInitial = market.marginRatioInitial; marginRatioMaintenance = market.marginRatioMaintenance; } let marginRatio; switch (marginCategory) { case 'Initial': { // use lowest leverage between max allowed and optional user custom max marginRatio = Math.max((0, margin_1.calculateSizePremiumLiabilityWeight)(size, new anchor_1.BN(market.imfFactor), new anchor_1.BN(marginRationInitial), numericConstants_1.MARGIN_PRECISION).toNumber(), customMarginRatio); break; } case 'Maintenance': { marginRatio = (0, margin_1.calculateSizePremiumLiabilityWeight)(size, new anchor_1.BN(market.imfFactor), new anchor_1.BN(marginRatioMaintenance), numericConstants_1.MARGIN_PRECISION).toNumber(); break; } } return marginRatio; } exports.calculateMarketMarginRatio = calculateMarketMarginRatio; function calculateUnrealizedAssetWeight(market, quoteSpotMarket, unrealizedPnl, marginCategory, oraclePriceData) { let assetWeight; switch (marginCategory) { case 'Initial': assetWeight = new anchor_1.BN(market.unrealizedPnlInitialAssetWeight); if (market.unrealizedPnlMaxImbalance.gt(numericConstants_1.ZERO)) { const netUnsettledPnl = calculateNetUserPnlImbalance(market, quoteSpotMarket, oraclePriceData); if (netUnsettledPnl.gt(market.unrealizedPnlMaxImbalance)) { assetWeight = assetWeight .mul(market.unrealizedPnlMaxImbalance) .div(netUnsettledPnl); } } assetWeight = (0, margin_1.calculateSizeDiscountAssetWeight)(unrealizedPnl, new anchor_1.BN(market.unrealizedPnlImfFactor), assetWeight); break; case 'Maintenance': assetWeight = new anchor_1.BN(market.unrealizedPnlMaintenanceAssetWeight); break; } return assetWeight; } exports.calculateUnrealizedAssetWeight = calculateUnrealizedAssetWeight; function calculateMarketAvailablePNL(perpMarket, spotMarket) { return (0, spotBalance_1.getTokenAmount)(perpMarket.pnlPool.scaledBalance, spotMarket, types_1.SpotBalanceType.DEPOSIT); } exports.calculateMarketAvailablePNL = calculateMarketAvailablePNL; function calculateMarketMaxAvailableInsurance(perpMarket, spotMarket) { (0, assert_1.assert)(spotMarket.marketIndex == numericConstants_1.QUOTE_SPOT_MARKET_INDEX); // todo: insuranceFundAllocation technically not guaranteed to be in Insurance Fund const insuranceFundAllocation = perpMarket.insuranceClaim.quoteMaxInsurance.sub(perpMarket.insuranceClaim.quoteSettledInsurance); const ammFeePool = (0, spotBalance_1.getTokenAmount)(perpMarket.amm.feePool.scaledBalance, spotMarket, types_1.SpotBalanceType.DEPOSIT); return insuranceFundAllocation.add(ammFeePool); } exports.calculateMarketMaxAvailableInsurance = calculateMarketMaxAvailableInsurance; function calculateNetUserPnl(perpMarket, oraclePriceData) { const netUserPositionValue = perpMarket.amm.baseAssetAmountWithAmm .add(perpMarket.amm.baseAssetAmountWithUnsettledLp) .mul(oraclePriceData.price) .div(numericConstants_1.BASE_PRECISION) .div(numericConstants_1.PRICE_TO_QUOTE_PRECISION); const netUserCostBasis = perpMarket.amm.quoteAssetAmount .add(perpMarket.amm.quoteAssetAmountWithUnsettledLp) .add(perpMarket.amm.netUnsettledFundingPnl); const netUserPnl = netUserPositionValue.add(netUserCostBasis); return netUserPnl; } exports.calculateNetUserPnl = calculateNetUserPnl; function calculateNetUserPnlImbalance(perpMarket, spotMarket, oraclePriceData, applyFeePoolDiscount = true) { const netUserPnl = calculateNetUserPnl(perpMarket, oraclePriceData); const pnlPool = (0, spotBalance_1.getTokenAmount)(perpMarket.pnlPool.scaledBalance, spotMarket, types_1.SpotBalanceType.DEPOSIT); let feePool = (0, spotBalance_1.getTokenAmount)(perpMarket.amm.feePool.scaledBalance, spotMarket, types_1.SpotBalanceType.DEPOSIT); if (applyFeePoolDiscount) { feePool = feePool.div(new anchor_1.BN(5)); } const imbalance = netUserPnl.sub(pnlPool.add(feePool)); return imbalance; } exports.calculateNetUserPnlImbalance = calculateNetUserPnlImbalance; function calculateAvailablePerpLiquidity(market, mmOraclePriceData, dlob, slot) { let [bids, asks] = (0, amm_1.calculateMarketOpenBidAsk)(market.amm.baseAssetReserve, market.amm.minBaseAssetReserve, market.amm.maxBaseAssetReserve, market.amm.orderStepSize); asks = asks.abs(); for (const bid of dlob.getRestingLimitBids(market.marketIndex, slot, types_1.MarketType.PERP, mmOraclePriceData)) { bids = bids.add(bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled)); } for (const ask of dlob.getRestingLimitAsks(market.marketIndex, slot, types_1.MarketType.PERP, mmOraclePriceData)) { asks = asks.add(ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled)); } return { bids: bids, asks: asks, }; } exports.calculateAvailablePerpLiquidity = calculateAvailablePerpLiquidity; function calculatePerpMarketBaseLiquidatorFee(market, userHighLeverageMode) { if (userHighLeverageMode && market.highLeverageMarginRatioMaintenance > 0) { const marginRatio = market.highLeverageMarginRatioMaintenance * 100; // min(liquidator_fee, .8 * high_leverage_margin_ratio_maintenance) return Math.min(market.liquidatorFee, marginRatio - Math.floor(marginRatio / 5)); } else { return market.liquidatorFee; } } exports.calculatePerpMarketBaseLiquidatorFee = calculatePerpMarketBaseLiquidatorFee; /** * Calculates trigger price for a perp market based on oracle price and current time * Implements the same logic as the Rust get_trigger_price function * * @param market - The perp market account * @param oraclePrice - Current oracle price (precision: PRICE_PRECISION) * @param now - Current timestamp in seconds * @returns trigger price (precision: PRICE_PRECISION) */ function getTriggerPrice(market, oraclePrice, now, useMedianPrice) { if (!useMedianPrice) { return oraclePrice.abs(); } const lastFillPrice = market.lastFillPrice; // Calculate 5-minute basis const markPrice5minTwap = market.amm.lastMarkPriceTwap5Min; const lastOraclePriceTwap5min = market.amm.historicalOracleData.lastOraclePriceTwap5Min; const basis5min = markPrice5minTwap.sub(lastOraclePriceTwap5min); const oraclePlusBasis5min = oraclePrice.add(basis5min); // Calculate funding basis const lastFundingBasis = getLastFundingBasis(market, oraclePrice, now); const oraclePlusFundingBasis = oraclePrice.add(lastFundingBasis); const prices = [ lastFillPrice.gt(numericConstants_1.ZERO) ? lastFillPrice : oraclePrice, oraclePlusFundingBasis, oraclePlusBasis5min, ].sort((a, b) => a.cmp(b)); const medianPrice = prices[1]; return clampTriggerPrice(market, oraclePrice.abs(), medianPrice); } exports.getTriggerPrice = getTriggerPrice; /** * Calculates the last funding basis for trigger price calculation * Implements the same logic as the Rust get_last_funding_basis function */ function getLastFundingBasis(market, oraclePrice, now) { if (market.amm.lastFundingOracleTwap.gt(numericConstants_1.ZERO)) { const lastFundingRate = market.amm.lastFundingRate .mul(numericConstants_1.PRICE_PRECISION) .div(market.amm.lastFundingOracleTwap) .muln(24); const lastFundingRatePreAdj = lastFundingRate.sub(numericConstants_1.FUNDING_RATE_PRECISION.div(new anchor_1.BN(5000)) // FUNDING_RATE_OFFSET_PERCENTAGE ); const timeLeftUntilFundingUpdate = anchor_1.BN.min(anchor_1.BN.max(now.sub(market.amm.lastFundingRateTs), numericConstants_1.ZERO), market.amm.fundingPeriod); const lastFundingBasis = oraclePrice .mul(lastFundingRatePreAdj) .div(numericConstants_1.PERCENTAGE_PRECISION) .mul(market.amm.fundingPeriod.sub(timeLeftUntilFundingUpdate)) .div(market.amm.fundingPeriod) .div(new anchor_1.BN(1000)); // FUNDING_RATE_BUFFER return lastFundingBasis; } else { return numericConstants_1.ZERO; } } /** * Clamps trigger price based on contract tier * Implements the same logic as the Rust clamp_trigger_price function */ function clampTriggerPrice(market, oraclePrice, medianPrice) { let maxBpsDiff; const tier = market.contractTier; if ((0, types_1.isVariant)(tier, 'a') || (0, types_1.isVariant)(tier, 'b')) { maxBpsDiff = new anchor_1.BN(500); // 20 BPS } else if ((0, types_1.isVariant)(tier, 'c')) { maxBpsDiff = new anchor_1.BN(100); // 100 BPS } else { maxBpsDiff = new anchor_1.BN(40); // 250 BPS } const maxOracleDiff = oraclePrice.div(maxBpsDiff); return anchor_1.BN.min(anchor_1.BN.max(medianPrice, oraclePrice.sub(maxOracleDiff)), oraclePrice.add(maxOracleDiff)); }