@drift-labs/sdk
Version:
SDK for Drift Protocol
226 lines (225 loc) • 9.61 kB
JavaScript
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;
;