UNPKG

@drift-labs/sdk

Version:
226 lines (225 loc) 9.61 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.hasOpenOrders = exports.isEmptyPosition = exports.positionCurrentDirection = exports.findDirectionToClose = exports.calculateCostBasis = exports.calculateEntryPrice = exports.calculateBreakEvenPrice = exports.positionIsAvailable = exports.calculatePositionFundingPNL = exports.calculateUnsettledFundingPnl = exports.calculateFeesAndFundingPnl = exports.calculateClaimablePnl = exports.calculatePositionPNL = exports.calculateBaseAssetValue = void 0; const anchor_1 = require("@coral-xyz/anchor"); const numericConstants_1 = require("../constants/numericConstants"); const types_1 = require("../types"); const amm_1 = require("./amm"); const margin_1 = require("./margin"); const market_1 = require("./market"); /** * calculateBaseAssetValue * = market value of closing entire position * @param market * @param userPosition * @param oraclePriceData * @returns Base Asset Value. : Precision QUOTE_PRECISION */ function calculateBaseAssetValue(market, userPosition, mmOraclePriceData, useSpread = true, skipUpdate = false, latestSlot) { if (userPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } const directionToClose = findDirectionToClose(userPosition); let prepegAmm; if (!skipUpdate) { if (market.amm.baseSpread > 0 && useSpread) { const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, directionToClose, mmOraclePriceData, undefined, latestSlot); prepegAmm = { baseAssetReserve, quoteAssetReserve, sqrtK: sqrtK, pegMultiplier: newPeg, }; } else { prepegAmm = (0, amm_1.calculateUpdatedAMM)(market.amm, mmOraclePriceData); } } else { prepegAmm = market.amm; } const [newQuoteAssetReserve, _] = (0, amm_1.calculateAmmReservesAfterSwap)(prepegAmm, 'base', userPosition.baseAssetAmount.abs(), (0, amm_1.getSwapDirection)('base', directionToClose)); switch (directionToClose) { case types_1.PositionDirection.SHORT: return prepegAmm.quoteAssetReserve .sub(newQuoteAssetReserve) .mul(prepegAmm.pegMultiplier) .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO); case types_1.PositionDirection.LONG: return newQuoteAssetReserve .sub(prepegAmm.quoteAssetReserve) .mul(prepegAmm.pegMultiplier) .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO) .add(numericConstants_1.ONE); } } exports.calculateBaseAssetValue = calculateBaseAssetValue; /** * calculatePositionPNL * = BaseAssetAmount * (Avg Exit Price - Avg Entry Price) * @param market * @param PerpPosition * @param withFunding (adds unrealized funding payment pnl to result) * @param oraclePriceData * @returns BaseAssetAmount : Precision QUOTE_PRECISION */ function calculatePositionPNL(market, perpPosition, withFunding = false, oraclePriceData) { if (perpPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return perpPosition.quoteAssetAmount; } const baseAssetValue = (0, margin_1.calculateBaseAssetValueWithOracle)(market, perpPosition, oraclePriceData); const baseAssetValueSign = perpPosition.baseAssetAmount.isNeg() ? new anchor_1.BN(-1) : new anchor_1.BN(1); let pnl = baseAssetValue .mul(baseAssetValueSign) .add(perpPosition.quoteAssetAmount); if (withFunding) { const fundingRatePnL = calculateUnsettledFundingPnl(market, perpPosition); pnl = pnl.add(fundingRatePnL); } return pnl; } exports.calculatePositionPNL = calculatePositionPNL; function calculateClaimablePnl(market, spotMarket, perpPosition, oraclePriceData) { const unrealizedPnl = calculatePositionPNL(market, perpPosition, true, oraclePriceData); let unsettledPnl = unrealizedPnl; if (unrealizedPnl.gt(numericConstants_1.ZERO)) { const excessPnlPool = anchor_1.BN.max(numericConstants_1.ZERO, (0, market_1.calculateNetUserPnlImbalance)(market, spotMarket, oraclePriceData).mul(new anchor_1.BN(-1))); const maxPositivePnl = anchor_1.BN.max(perpPosition.quoteAssetAmount.sub(perpPosition.quoteEntryAmount), numericConstants_1.ZERO).add(excessPnlPool); unsettledPnl = anchor_1.BN.min(maxPositivePnl, unrealizedPnl); } return unsettledPnl; } exports.calculateClaimablePnl = calculateClaimablePnl; /** * Returns total fees and funding pnl for a position * * @param market * @param PerpPosition * @param includeUnsettled include unsettled funding in return value (default: true) * @returns — // QUOTE_PRECISION */ function calculateFeesAndFundingPnl(market, perpPosition, includeUnsettled = true) { const settledFundingAndFeesPnl = perpPosition.quoteBreakEvenAmount.sub(perpPosition.quoteEntryAmount); if (!includeUnsettled) { return settledFundingAndFeesPnl; } const unsettledFundingPnl = calculateUnsettledFundingPnl(market, perpPosition); return settledFundingAndFeesPnl.add(unsettledFundingPnl); } exports.calculateFeesAndFundingPnl = calculateFeesAndFundingPnl; /** * Returns unsettled funding pnl for the position * * To calculate all fees and funding pnl including settled, use calculateFeesAndFundingPnl * * @param market * @param PerpPosition * @returns // QUOTE_PRECISION */ function calculateUnsettledFundingPnl(market, perpPosition) { if (perpPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } let ammCumulativeFundingRate; if (perpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) { ammCumulativeFundingRate = market.amm.cumulativeFundingRateLong; } else { ammCumulativeFundingRate = market.amm.cumulativeFundingRateShort; } const perPositionFundingRate = ammCumulativeFundingRate .sub(perpPosition.lastCumulativeFundingRate) .mul(perpPosition.baseAssetAmount) .div(numericConstants_1.AMM_RESERVE_PRECISION) .div(numericConstants_1.FUNDING_RATE_BUFFER_PRECISION) .mul(new anchor_1.BN(-1)); return perPositionFundingRate; } exports.calculateUnsettledFundingPnl = calculateUnsettledFundingPnl; /** * @deprecated use calculateUnsettledFundingPnl or calculateFeesAndFundingPnl instead */ function calculatePositionFundingPNL(market, perpPosition) { return calculateUnsettledFundingPnl(market, perpPosition); } exports.calculatePositionFundingPNL = calculatePositionFundingPNL; function positionIsAvailable(position) { return (position.baseAssetAmount.eq(numericConstants_1.ZERO) && position.openOrders === 0 && position.quoteAssetAmount.eq(numericConstants_1.ZERO) && position.lpShares.eq(numericConstants_1.ZERO)); } exports.positionIsAvailable = positionIsAvailable; /** * * @param userPosition * @returns Precision: PRICE_PRECISION (10^6) */ function calculateBreakEvenPrice(userPosition) { if (userPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } return userPosition.quoteBreakEvenAmount .mul(numericConstants_1.PRICE_PRECISION) .mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO) .div(userPosition.baseAssetAmount) .abs(); } exports.calculateBreakEvenPrice = calculateBreakEvenPrice; /** * * @param userPosition * @returns Precision: PRICE_PRECISION (10^6) */ function calculateEntryPrice(userPosition) { if (userPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } return userPosition.quoteEntryAmount .mul(numericConstants_1.PRICE_PRECISION) .mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO) .div(userPosition.baseAssetAmount) .abs(); } exports.calculateEntryPrice = calculateEntryPrice; /** * * @param userPosition * @returns Precision: PRICE_PRECISION (10^10) */ function calculateCostBasis(userPosition, includeSettledPnl = false) { if (userPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } return userPosition.quoteAssetAmount .add(includeSettledPnl ? userPosition.settledPnl : numericConstants_1.ZERO) .mul(numericConstants_1.PRICE_PRECISION) .mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO) .div(userPosition.baseAssetAmount) .abs(); } exports.calculateCostBasis = calculateCostBasis; function findDirectionToClose(userPosition) { return userPosition.baseAssetAmount.gt(numericConstants_1.ZERO) ? types_1.PositionDirection.SHORT : types_1.PositionDirection.LONG; } exports.findDirectionToClose = findDirectionToClose; function positionCurrentDirection(userPosition) { return userPosition.baseAssetAmount.gte(numericConstants_1.ZERO) ? types_1.PositionDirection.LONG : types_1.PositionDirection.SHORT; } exports.positionCurrentDirection = positionCurrentDirection; function isEmptyPosition(userPosition) { return userPosition.baseAssetAmount.eq(numericConstants_1.ZERO) && userPosition.openOrders === 0; } exports.isEmptyPosition = isEmptyPosition; function hasOpenOrders(position) { return (position.openOrders != 0 || !position.openBids.eq(numericConstants_1.ZERO) || !position.openAsks.eq(numericConstants_1.ZERO)); } exports.hasOpenOrders = hasOpenOrders;