@drift-labs/common
Version:
Common functions for Drift
200 lines • 9.02 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.TRADING_UTILS = exports.isEntirePositionOrder = void 0;
const sdk_1 = require("@drift-labs/sdk");
const calculatePnlPctFromPosition = (pnl, quoteEntryAmount, leverage) => {
if (!quoteEntryAmount || quoteEntryAmount.eq(sdk_1.ZERO))
return 0;
return (sdk_1.BigNum.from(pnl, sdk_1.QUOTE_PRECISION_EXP)
.shift(5)
.div(sdk_1.BigNum.from(quoteEntryAmount.abs(), sdk_1.QUOTE_PRECISION_EXP))
.toNum() *
100 *
(leverage !== null && leverage !== void 0 ? leverage : 1));
};
const POTENTIAL_PROFIT_DEFAULT_STATE = {
estimatedProfit: sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP),
estimatedProfitBeforeFees: sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP),
estimatedTakerFee: sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP),
notionalSizeAtEntry: sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP),
notionalSizeAtExit: sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP),
};
const calculatePotentialProfit = (props) => {
let estimatedProfit = sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP);
let estimatedProfitBeforeFees = sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP);
let estimatedTakerFee = sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP);
let notionalSizeAtEntry = sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP);
let notionalSizeAtExit = sdk_1.BigNum.zero(sdk_1.PRICE_PRECISION_EXP);
const isClosingLong = (0, sdk_1.isVariant)(props.currentPositionDirection, 'long') &&
(0, sdk_1.isVariant)(props.tradeDirection, 'short');
const isClosingShort = (0, sdk_1.isVariant)(props.currentPositionDirection, 'short') &&
(0, sdk_1.isVariant)(props.tradeDirection, 'long');
if (!isClosingLong && !isClosingShort)
return POTENTIAL_PROFIT_DEFAULT_STATE;
if (!props.exitBaseSize)
return POTENTIAL_PROFIT_DEFAULT_STATE;
if (props.exitBaseSize.eqZero() ||
props.currentPositionSize.lt(props.exitBaseSize)) {
return POTENTIAL_PROFIT_DEFAULT_STATE;
}
const baseSizeBeingClosed = props.exitBaseSize.lte(props.currentPositionSize)
? props.exitBaseSize
: props.currentPositionSize;
// Notional size of amount being closed at entry and exit
notionalSizeAtEntry = baseSizeBeingClosed.mul(props.currentPositionEntryPrice.shiftTo(baseSizeBeingClosed.precision));
notionalSizeAtExit = baseSizeBeingClosed.mul(props.exitPrice.shiftTo(baseSizeBeingClosed.precision));
if (isClosingLong) {
estimatedProfitBeforeFees = notionalSizeAtExit.sub(notionalSizeAtEntry);
}
else if (isClosingShort) {
estimatedProfitBeforeFees = notionalSizeAtEntry.sub(notionalSizeAtExit);
}
// subtract takerFee if applicable
if (props.takerFeeBps > 0) {
const takerFeeDenominator = Math.floor(100 / (props.takerFeeBps * 0.01));
estimatedTakerFee = notionalSizeAtExit.scale(1, takerFeeDenominator);
estimatedProfit = estimatedProfitBeforeFees.sub(estimatedTakerFee.shiftTo(estimatedProfitBeforeFees.precision));
}
else {
estimatedProfit = estimatedProfitBeforeFees;
}
return {
estimatedProfit,
estimatedProfitBeforeFees,
estimatedTakerFee,
notionalSizeAtEntry,
notionalSizeAtExit,
};
};
/**
* Check if the order type is a market order or oracle market order
*/
const checkIsMarketOrderType = (orderType) => {
return orderType === 'market' || orderType === 'oracle';
};
/**
* Calculate the liquidation price of a position after a trade. Requires DriftClient to be subscribed.
* If the order type is limit order, a limit price must be provided.
*/
const calculateLiquidationPriceAfterPerpTrade = ({ estEntryPrice, orderType, perpMarketIndex, tradeBaseSize, isLong, userClient, oraclePrice, limitPrice, offsetCollateral, precision = 2, isEnteringHighLeverageMode, capLiqPrice, }) => {
var _a, _b;
const ALLOWED_ORDER_TYPES = [
'limit',
'market',
'oracle',
'stopMarket',
'stopLimit',
'oracleLimit',
];
if (!ALLOWED_ORDER_TYPES.includes(orderType)) {
console.error('Invalid order type for perp trade liquidation price calculation', orderType);
return 0;
}
if (orderType === 'limit' && !limitPrice) {
console.error('Limit order must have a limit price for perp trade liquidation price calculation');
return 0;
}
const signedBaseSize = isLong ? tradeBaseSize : tradeBaseSize.neg();
const priceToUse = [
'limit',
'stopMarket',
'stopLimit',
'oracleLimit',
].includes(orderType)
? limitPrice
: estEntryPrice;
const liqPriceBn = userClient.liquidationPrice(perpMarketIndex, signedBaseSize, priceToUse, undefined, undefined, // we can exclude open orders since open orders will be cancelled first (which results in reducing account leverage) before actual liquidation
offsetCollateral, isEnteringHighLeverageMode);
if (liqPriceBn.isNeg()) {
// means no liquidation price
return 0;
}
// Check if user has a spot position using the same oracle as the perp market
// If so, force capLiqPrice to be false to avoid incorrect price capping
// Technically in this case, liq price could be lower for a short or higher for a long
const perpMarketOracle = (_b = (_a = userClient.driftClient.getPerpMarketAccount(perpMarketIndex)) === null || _a === void 0 ? void 0 : _a.amm) === null || _b === void 0 ? void 0 : _b.oracle;
const spotMarketWithSameOracle = userClient.driftClient
.getSpotMarketAccounts()
.find((market) => market.oracle.equals(perpMarketOracle));
let hasSpotPositionWithSameOracle = false;
if (spotMarketWithSameOracle) {
const spotPosition = userClient.getSpotPosition(spotMarketWithSameOracle.marketIndex);
hasSpotPositionWithSameOracle = !!spotPosition;
}
const effectiveCapLiqPrice = hasSpotPositionWithSameOracle
? false
: capLiqPrice;
const cappedLiqPriceBn = effectiveCapLiqPrice
? isLong
? sdk_1.BN.min(liqPriceBn, oraclePrice)
: sdk_1.BN.max(liqPriceBn, oraclePrice)
: liqPriceBn;
const liqPriceBigNum = sdk_1.BigNum.from(cappedLiqPriceBn, sdk_1.PRICE_PRECISION_EXP);
const liqPriceNum = Math.round(liqPriceBigNum.toNum() * 10 ** precision) / 10 ** precision;
return liqPriceNum;
};
const convertLeverageToMarginRatio = (leverage) => {
if (!leverage)
return undefined;
return (1 / leverage) * sdk_1.MARGIN_PRECISION.toNumber();
};
const getMarketTickSize = (driftClient, marketId) => {
const marketAccount = marketId.isPerp
? driftClient.getPerpMarketAccount(marketId.marketIndex)
: driftClient.getSpotMarketAccount(marketId.marketIndex);
if (!marketAccount)
return sdk_1.ZERO;
if (marketId.isPerp) {
return marketAccount.amm.orderTickSize;
}
else {
return marketAccount.orderTickSize;
}
};
const getMarketTickSizeDecimals = (driftClient, marketId) => {
const tickSize = getMarketTickSize(driftClient, marketId);
const decimalPlaces = Math.max(0, Math.floor(Math.log10(sdk_1.PRICE_PRECISION.div(tickSize.eq(sdk_1.ZERO) ? sdk_1.ONE : tickSize).toNumber())));
return decimalPlaces;
};
const getMarketStepSize = (driftClient, marketId) => {
const marketAccount = marketId.isPerp
? driftClient.getPerpMarketAccount(marketId.marketIndex)
: driftClient.getSpotMarketAccount(marketId.marketIndex);
if (!marketAccount)
return sdk_1.ZERO;
if (marketId.isPerp) {
return marketAccount.amm.orderStepSize;
}
else {
return marketAccount.orderStepSize;
}
};
const getMarketStepSizeDecimals = (driftClient, marketId) => {
const stepSize = getMarketStepSize(driftClient, marketId);
const decimalPlaces = Math.max(0, Math.floor(Math.log10(sdk_1.AMM_RESERVE_PRECISION.div(stepSize.eq(sdk_1.ZERO) ? sdk_1.ONE : stepSize).toNumber())));
return decimalPlaces;
};
/**
* Checks if a given order amount represents an entire position order
* by comparing it with MAX_LEVERAGE_ORDER_SIZE
* @param orderAmount - The BigNum order amount to check
* @returns true if the order is for the entire position, false otherwise
*/
const isEntirePositionOrder = (orderAmount) => {
const maxLeverageSize = new sdk_1.BigNum(sdk_1.MAX_LEVERAGE_ORDER_SIZE, orderAmount.precision);
return Math.abs(maxLeverageSize.sub(orderAmount).toNum()) < 1;
};
exports.isEntirePositionOrder = isEntirePositionOrder;
exports.TRADING_UTILS = {
calculatePnlPctFromPosition,
calculatePotentialProfit,
calculateLiquidationPriceAfterPerpTrade,
checkIsMarketOrderType,
convertLeverageToMarginRatio,
getMarketTickSize,
getMarketTickSizeDecimals,
getMarketStepSize,
getMarketStepSizeDecimals,
isEntirePositionOrder: exports.isEntirePositionOrder,
};
//# sourceMappingURL=trading.js.map
;