UNPKG

@drift-labs/sdk

Version:
243 lines (242 loc) • 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTriggerAuctionStartAndExecutionPrice = exports.getTriggerAuctionStartPrice = exports.deriveOracleAuctionParams = exports.getAuctionPriceForOracleOffsetAuction = exports.getAuctionPriceForFixedAuction = exports.getAuctionPrice = exports.isFallbackAvailableLiquiditySource = exports.isAuctionComplete = void 0; const types_1 = require("../types"); const anchor_1 = require("@coral-xyz/anchor"); const numericConstants_1 = require("../constants/numericConstants"); const types_2 = require("../types"); const tiers_1 = require("./tiers"); function isAuctionComplete(order, slot) { if (order.auctionDuration === 0) { return true; } return new anchor_1.BN(slot).sub(order.slot).gt(new anchor_1.BN(order.auctionDuration)); } exports.isAuctionComplete = isAuctionComplete; function isFallbackAvailableLiquiditySource(order, minAuctionDuration, slot) { if (minAuctionDuration === 0) { return true; } if ((order.bitFlags & types_2.OrderBitFlag.SafeTriggerOrder) !== 0) { return true; } return new anchor_1.BN(slot).sub(order.slot).gt(new anchor_1.BN(minAuctionDuration)); } exports.isFallbackAvailableLiquiditySource = isFallbackAvailableLiquiditySource; /** * * @param order * @param slot * @param oraclePrice Use MMOraclePriceData source for perp orders, OraclePriceData for spot * @returns BN */ function getAuctionPrice(order, slot, oraclePrice) { if ((0, types_1.isOneOfVariant)(order.orderType, ['market', 'triggerLimit']) || ((0, types_1.isVariant)(order.orderType, 'triggerMarket') && (order.bitFlags & types_2.OrderBitFlag.OracleTriggerMarket) === 0)) { return getAuctionPriceForFixedAuction(order, slot); } else if ((0, types_1.isVariant)(order.orderType, 'limit')) { if (order.oraclePriceOffset != null && order.oraclePriceOffset !== 0) { return getAuctionPriceForOracleOffsetAuction(order, slot, oraclePrice); } else { return getAuctionPriceForFixedAuction(order, slot); } } else if ((0, types_1.isVariant)(order.orderType, 'oracle') || ((0, types_1.isVariant)(order.orderType, 'triggerMarket') && (order.bitFlags & types_2.OrderBitFlag.OracleTriggerMarket) !== 0)) { return getAuctionPriceForOracleOffsetAuction(order, slot, oraclePrice); } else { throw Error(`Cant get auction price for order type ${(0, types_2.getVariant)(order.orderType)}`); } } exports.getAuctionPrice = getAuctionPrice; function getAuctionPriceForFixedAuction(order, slot) { const slotsElapsed = new anchor_1.BN(slot).sub(order.slot); const deltaDenominator = new anchor_1.BN(order.auctionDuration); const deltaNumerator = anchor_1.BN.min(slotsElapsed, deltaDenominator); if (deltaDenominator.eq(numericConstants_1.ZERO)) { return order.auctionEndPrice; } let priceDelta; if ((0, types_1.isVariant)(order.direction, 'long')) { priceDelta = order.auctionEndPrice .sub(order.auctionStartPrice) .mul(deltaNumerator) .div(deltaDenominator); } else { priceDelta = order.auctionStartPrice .sub(order.auctionEndPrice) .mul(deltaNumerator) .div(deltaDenominator); } let price; if ((0, types_1.isVariant)(order.direction, 'long')) { price = order.auctionStartPrice.add(priceDelta); } else { price = order.auctionStartPrice.sub(priceDelta); } return price; } exports.getAuctionPriceForFixedAuction = getAuctionPriceForFixedAuction; /** * * @param order * @param slot * @param oraclePrice Use MMOraclePriceData source for perp orders, OraclePriceData for spot * @returns */ function getAuctionPriceForOracleOffsetAuction(order, slot, oraclePrice) { const slotsElapsed = new anchor_1.BN(slot).sub(order.slot); const deltaDenominator = new anchor_1.BN(order.auctionDuration); const deltaNumerator = anchor_1.BN.min(slotsElapsed, deltaDenominator); if (deltaDenominator.eq(numericConstants_1.ZERO)) { return anchor_1.BN.max(oraclePrice.add(order.auctionEndPrice), numericConstants_1.ONE); } let priceOffsetDelta; if ((0, types_1.isVariant)(order.direction, 'long')) { priceOffsetDelta = order.auctionEndPrice .sub(order.auctionStartPrice) .mul(deltaNumerator) .div(deltaDenominator); } else { priceOffsetDelta = order.auctionStartPrice .sub(order.auctionEndPrice) .mul(deltaNumerator) .div(deltaDenominator); } let priceOffset; if ((0, types_1.isVariant)(order.direction, 'long')) { priceOffset = order.auctionStartPrice.add(priceOffsetDelta); } else { priceOffset = order.auctionStartPrice.sub(priceOffsetDelta); } return anchor_1.BN.max(oraclePrice.add(priceOffset), numericConstants_1.ONE); } exports.getAuctionPriceForOracleOffsetAuction = getAuctionPriceForOracleOffsetAuction; function deriveOracleAuctionParams({ direction, oraclePrice, auctionStartPrice, auctionEndPrice, limitPrice, auctionPriceCaps, }) { let oraclePriceOffset; if (limitPrice.eq(numericConstants_1.ZERO) || oraclePrice.eq(numericConstants_1.ZERO)) { oraclePriceOffset = numericConstants_1.ZERO; } else { oraclePriceOffset = limitPrice.sub(oraclePrice); } if (oraclePriceOffset.eq(numericConstants_1.ZERO)) { oraclePriceOffset = (0, types_1.isVariant)(direction, 'long') ? auctionEndPrice.sub(oraclePrice).add(numericConstants_1.ONE) : auctionEndPrice.sub(oraclePrice).sub(numericConstants_1.ONE); } let oraclePriceOffsetNum; try { oraclePriceOffsetNum = oraclePriceOffset.toNumber(); } catch (e) { oraclePriceOffsetNum = 0; } if (auctionPriceCaps) { auctionStartPrice = anchor_1.BN.min(anchor_1.BN.max(auctionStartPrice, auctionPriceCaps.min), auctionPriceCaps.max); auctionEndPrice = anchor_1.BN.min(anchor_1.BN.max(auctionEndPrice, auctionPriceCaps.min), auctionPriceCaps.max); } return { auctionStartPrice: auctionStartPrice.sub(oraclePrice), auctionEndPrice: auctionEndPrice.sub(oraclePrice), oraclePriceOffset: oraclePriceOffsetNum, }; } exports.deriveOracleAuctionParams = deriveOracleAuctionParams; /** * * @param params Use OraclePriceData.price for oraclePrice param * @returns */ function getTriggerAuctionStartPrice(params) { const { perpMarket, direction, oraclePrice, limitPrice } = params; const twapMismatch = perpMarket.amm.historicalOracleData.lastOraclePriceTwapTs .sub(perpMarket.amm.lastMarkPriceTwapTs) .abs() .gte(new anchor_1.BN(60)) || perpMarket.amm.volume24H.lte(new anchor_1.BN(100000).mul(numericConstants_1.QUOTE_PRECISION)); let baselineStartOffset; if (twapMismatch) { const contractTierNumber = (0, tiers_1.getPerpMarketTierNumber)(perpMarket); const priceDivisor = contractTierNumber <= 1 ? 500 : 100; baselineStartOffset = (0, types_1.isVariant)(direction, 'long') ? perpMarket.amm.lastBidPriceTwap.divn(priceDivisor) : perpMarket.amm.lastAskPriceTwap.divn(priceDivisor).neg(); } else { const markTwapSlow = (0, types_1.isVariant)(direction, 'long') ? perpMarket.amm.lastBidPriceTwap : perpMarket.amm.lastAskPriceTwap; const markTwapFast = perpMarket.amm.lastMarkPriceTwap5Min; const oracleTwapSlow = perpMarket.amm.historicalOracleData.lastOraclePriceTwap; const oracleTwapFast = perpMarket.amm.historicalOracleData.lastOraclePriceTwap5Min; const offsetSlow = markTwapSlow.sub(oracleTwapSlow); const offsetFast = markTwapFast.sub(oracleTwapFast); const fracOfLongSpreadInPrice = new anchor_1.BN(perpMarket.amm.longSpread) .mul(markTwapSlow) .div(numericConstants_1.PRICE_PRECISION.muln(10)); // divide by 10x for safety const fracOfShortSpreadInPrice = new anchor_1.BN(perpMarket.amm.shortSpread) .mul(markTwapSlow) .div(numericConstants_1.PRICE_PRECISION.muln(10)); // divide by 10x for safety baselineStartOffset = (0, types_1.isVariant)(direction, 'long') ? anchor_1.BN.min(offsetSlow.add(fracOfLongSpreadInPrice), offsetFast.sub(fracOfShortSpreadInPrice)) : anchor_1.BN.max(offsetSlow.sub(fracOfShortSpreadInPrice), offsetFast.add(fracOfLongSpreadInPrice)); } let startBuffer = -3500; if ((0, types_1.isVariant)(perpMarket.contractTier, 'a') || (0, types_1.isVariant)(perpMarket.contractTier, 'b')) { startBuffer = -500; } // Apply start buffer (in BPS) const startBufferPrice = oraclePrice .mul(new anchor_1.BN(startBuffer)) .div(new anchor_1.BN(numericConstants_1.PRICE_PRECISION)); let auctionStartPrice = (0, types_1.isVariant)(direction, 'long') ? oraclePrice.add(baselineStartOffset).sub(startBufferPrice) : oraclePrice.add(baselineStartOffset).add(startBufferPrice); if (limitPrice) { if ((0, types_1.isVariant)(direction, 'long')) { auctionStartPrice = anchor_1.BN.min(auctionStartPrice, limitPrice); } else { auctionStartPrice = anchor_1.BN.max(auctionStartPrice, limitPrice); } } return auctionStartPrice; } exports.getTriggerAuctionStartPrice = getTriggerAuctionStartPrice; /** * * @param params Use OraclePriceData.price for oraclePrice param and MMOraclePriceData.price for mmOraclePrice * @returns */ function getTriggerAuctionStartAndExecutionPrice(params) { const { perpMarket, direction, oraclePrice, limitPrice, mmOraclePrice } = params; const startPrice = getTriggerAuctionStartPrice({ perpMarket, direction, oraclePrice, limitPrice, }); const offsetPlusBuffer = startPrice.sub(oraclePrice); let executionPrice = mmOraclePrice.add(offsetPlusBuffer); if (limitPrice) { if ((0, types_1.isVariant)(direction, 'long')) { executionPrice = anchor_1.BN.min(executionPrice, limitPrice); } else { executionPrice = anchor_1.BN.max(executionPrice, limitPrice); } } return { startPrice, executionPrice }; } exports.getTriggerAuctionStartAndExecutionPrice = getTriggerAuctionStartAndExecutionPrice;