UNPKG

@ox-fun/drift-sdk

Version:

SDK for Drift Protocol

314 lines (281 loc) 7.36 kB
import { BN } from '@coral-xyz/anchor'; import { PerpMarketAccount, PositionDirection, MarginCategory, SpotMarketAccount, SpotBalanceType, MarketType, } from '../types'; import { calculateAmmReservesAfterSwap, calculatePrice, calculateUpdatedAMMSpreadReserves, getSwapDirection, calculateUpdatedAMM, calculateMarketOpenBidAsk, } from './amm'; import { calculateSizeDiscountAssetWeight, calculateSizePremiumLiabilityWeight, } from './margin'; import { OraclePriceData } from '../oracles/types'; import { BASE_PRECISION, MARGIN_PRECISION, PRICE_TO_QUOTE_PRECISION, ZERO, QUOTE_SPOT_MARKET_INDEX, } from '../constants/numericConstants'; import { getTokenAmount } from './spotBalance'; import { DLOB } from '../dlob/DLOB'; import { assert } from '../assert/assert'; /** * Calculates market mark price * * @param market * @return markPrice : Precision PRICE_PRECISION */ export function calculateReservePrice( market: PerpMarketAccount, oraclePriceData: OraclePriceData ): BN { const newAmm = calculateUpdatedAMM(market.amm, oraclePriceData); return calculatePrice( newAmm.baseAssetReserve, newAmm.quoteAssetReserve, newAmm.pegMultiplier ); } /** * Calculates market bid price * * @param market * @return bidPrice : Precision PRICE_PRECISION */ export function calculateBidPrice( market: PerpMarketAccount, oraclePriceData: OraclePriceData ): BN { const { baseAssetReserve, quoteAssetReserve, newPeg } = calculateUpdatedAMMSpreadReserves( market.amm, PositionDirection.SHORT, oraclePriceData ); return calculatePrice(baseAssetReserve, quoteAssetReserve, newPeg); } /** * Calculates market ask price * * @param market * @return askPrice : Precision PRICE_PRECISION */ export function calculateAskPrice( market: PerpMarketAccount, oraclePriceData: OraclePriceData ): BN { const { baseAssetReserve, quoteAssetReserve, newPeg } = calculateUpdatedAMMSpreadReserves( market.amm, PositionDirection.LONG, oraclePriceData ); return calculatePrice(baseAssetReserve, quoteAssetReserve, newPeg); } export function calculateNewMarketAfterTrade( baseAssetAmount: BN, direction: PositionDirection, market: PerpMarketAccount ): PerpMarketAccount { const [newQuoteAssetReserve, newBaseAssetReserve] = calculateAmmReservesAfterSwap( market.amm, 'base', baseAssetAmount.abs(), 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; } export function calculateOracleReserveSpread( market: PerpMarketAccount, oraclePriceData: OraclePriceData ): BN { const reservePrice = calculateReservePrice(market, oraclePriceData); return calculateOracleSpread(reservePrice, oraclePriceData); } export function calculateOracleSpread( price: BN, oraclePriceData: OraclePriceData ): BN { return price.sub(oraclePriceData.price); } export function calculateMarketMarginRatio( market: PerpMarketAccount, size: BN, marginCategory: MarginCategory, customMarginRatio = 0 ): number { let marginRatio; switch (marginCategory) { case 'Initial': { // use lowest leverage between max allowed and optional user custom max marginRatio = Math.max( calculateSizePremiumLiabilityWeight( size, new BN(market.imfFactor), new BN(market.marginRatioInitial), MARGIN_PRECISION ).toNumber(), customMarginRatio ); break; } case 'Maintenance': { marginRatio = calculateSizePremiumLiabilityWeight( size, new BN(market.imfFactor), new BN(market.marginRatioMaintenance), MARGIN_PRECISION ).toNumber(); break; } } return marginRatio; } export function calculateUnrealizedAssetWeight( market: PerpMarketAccount, quoteSpotMarket: SpotMarketAccount, unrealizedPnl: BN, marginCategory: MarginCategory, oraclePriceData: OraclePriceData ): BN { let assetWeight: BN; switch (marginCategory) { case 'Initial': assetWeight = new BN(market.unrealizedPnlInitialAssetWeight); if (market.unrealizedPnlMaxImbalance.gt(ZERO)) { const netUnsettledPnl = calculateNetUserPnlImbalance( market, quoteSpotMarket, oraclePriceData ); if (netUnsettledPnl.gt(market.unrealizedPnlMaxImbalance)) { assetWeight = assetWeight .mul(market.unrealizedPnlMaxImbalance) .div(netUnsettledPnl); } } assetWeight = calculateSizeDiscountAssetWeight( unrealizedPnl, new BN(market.unrealizedPnlImfFactor), assetWeight ); break; case 'Maintenance': assetWeight = new BN(market.unrealizedPnlMaintenanceAssetWeight); break; } return assetWeight; } export function calculateMarketAvailablePNL( perpMarket: PerpMarketAccount, spotMarket: SpotMarketAccount ): BN { return getTokenAmount( perpMarket.pnlPool.scaledBalance, spotMarket, SpotBalanceType.DEPOSIT ); } export function calculateMarketMaxAvailableInsurance( perpMarket: PerpMarketAccount, spotMarket: SpotMarketAccount ): BN { assert(spotMarket.marketIndex == 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 = getTokenAmount( perpMarket.amm.feePool.scaledBalance, spotMarket, SpotBalanceType.DEPOSIT ); return insuranceFundAllocation.add(ammFeePool); } export function calculateNetUserPnl( perpMarket: PerpMarketAccount, oraclePriceData: OraclePriceData ): BN { const netUserPositionValue = perpMarket.amm.baseAssetAmountWithAmm .mul(oraclePriceData.price) .div(BASE_PRECISION) .div(PRICE_TO_QUOTE_PRECISION); const netUserCostBasis = perpMarket.amm.quoteAssetAmount; const netUserPnl = netUserPositionValue.add(netUserCostBasis); return netUserPnl; } export function calculateNetUserPnlImbalance( perpMarket: PerpMarketAccount, spotMarket: SpotMarketAccount, oraclePriceData: OraclePriceData ): BN { const netUserPnl = calculateNetUserPnl(perpMarket, oraclePriceData); const pnlPool = getTokenAmount( perpMarket.pnlPool.scaledBalance, spotMarket, SpotBalanceType.DEPOSIT ); const feePool = getTokenAmount( perpMarket.amm.feePool.scaledBalance, spotMarket, SpotBalanceType.DEPOSIT ); const imbalance = netUserPnl.sub(pnlPool.add(feePool)); return imbalance; } export function calculateAvailablePerpLiquidity( market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number ): { bids: BN; asks: BN } { let [bids, asks] = 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, MarketType.PERP, oraclePriceData )) { bids = bids.add( bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled) ); } for (const ask of dlob.getRestingLimitAsks( market.marketIndex, slot, MarketType.PERP, oraclePriceData )) { asks = asks.add( ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled) ); } return { bids: bids, asks: asks, }; }