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