UNPKG

@drift-labs/common

Version:

Common functions for Drift

200 lines 9.02 kB
"use strict"; 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