@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
638 lines (637 loc) • 31.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUser30dRollingVolumeEstimate = exports.calculateEstimatedEntryPriceWithL2 = exports.calculateEstimatedSpotEntryPrice = exports.calculateEstimatedPerpEntryPrice = exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
const types_1 = require("../types");
const anchor_1 = require("@coral-xyz/anchor");
const assert_1 = require("../assert/assert");
const numericConstants_1 = require("../constants/numericConstants");
const market_1 = require("./market");
const amm_1 = require("./amm");
const utils_1 = require("./utils");
const types_2 = require("../types");
const MAXPCT = new anchor_1.BN(1000); //percentage units are [0,1000] => [0,1]
/**
* Calculates avg/max slippage (price impact) for candidate trade
*
* @deprecated use calculateEstimatedPerpEntryPrice instead
*
* @param direction
* @param amount
* @param market
* @param inputAssetType which asset is being traded
* @param useSpread whether to consider spread with calculating slippage
* @return [pctAvgSlippage, pctMaxSlippage, entryPrice, newPrice]
*
* 'pctAvgSlippage' => the percentage change to entryPrice (average est slippage in execution) : Precision PRICE_PRECISION
*
* 'pctMaxSlippage' => the percentage change to maxPrice (highest est slippage in execution) : Precision PRICE_PRECISION
*
* 'entryPrice' => the average price of the trade : Precision PRICE_PRECISION
*
* 'newPrice' => the price of the asset after the trade : Precision PRICE_PRECISION
*/
function calculateTradeSlippage(direction, amount, market, inputAssetType = 'quote', mmOraclePriceData, useSpread = true, latestSlot) {
let oldPrice;
if (useSpread && market.amm.baseSpread > 0) {
if ((0, types_2.isVariant)(direction, 'long')) {
oldPrice = (0, market_1.calculateAskPrice)(market, mmOraclePriceData);
}
else {
oldPrice = (0, market_1.calculateBidPrice)(market, mmOraclePriceData);
}
}
else {
oldPrice = (0, market_1.calculateReservePrice)(market, mmOraclePriceData);
}
if (amount.eq(numericConstants_1.ZERO)) {
return [numericConstants_1.ZERO, numericConstants_1.ZERO, oldPrice, oldPrice];
}
const [acquiredBaseReserve, acquiredQuoteReserve, acquiredQuoteAssetAmount] = calculateTradeAcquiredAmounts(direction, amount, market, inputAssetType, mmOraclePriceData, useSpread);
const entryPrice = acquiredQuoteAssetAmount
.mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO)
.mul(numericConstants_1.PRICE_PRECISION)
.div(acquiredBaseReserve.abs());
let amm;
if (useSpread && market.amm.baseSpread > 0) {
const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, mmOraclePriceData, undefined, latestSlot);
amm = {
baseAssetReserve,
quoteAssetReserve,
sqrtK: sqrtK,
pegMultiplier: newPeg,
};
}
else {
amm = market.amm;
}
const newPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve.sub(acquiredBaseReserve), amm.quoteAssetReserve.sub(acquiredQuoteReserve), amm.pegMultiplier);
if (direction == types_1.PositionDirection.SHORT) {
(0, assert_1.assert)(newPrice.lte(oldPrice));
}
else {
(0, assert_1.assert)(oldPrice.lte(newPrice));
}
const pctMaxSlippage = newPrice
.sub(oldPrice)
.mul(numericConstants_1.PRICE_PRECISION)
.div(oldPrice)
.abs();
const pctAvgSlippage = entryPrice
.sub(oldPrice)
.mul(numericConstants_1.PRICE_PRECISION)
.div(oldPrice)
.abs();
return [pctAvgSlippage, pctMaxSlippage, entryPrice, newPrice];
}
exports.calculateTradeSlippage = calculateTradeSlippage;
/**
* Calculates acquired amounts for trade executed
* @param direction
* @param amount
* @param market
* @param inputAssetType
* @param useSpread
* @return
* | 'acquiredBase' => positive/negative change in user's base : BN AMM_RESERVE_PRECISION
* | 'acquiredQuote' => positive/negative change in user's quote : BN TODO-PRECISION
*/
function calculateTradeAcquiredAmounts(direction, amount, market, inputAssetType = 'quote', mmOraclePriceData, useSpread = true, latestSlot) {
if (amount.eq(numericConstants_1.ZERO)) {
return [numericConstants_1.ZERO, numericConstants_1.ZERO, numericConstants_1.ZERO];
}
const swapDirection = (0, amm_1.getSwapDirection)(inputAssetType, direction);
let amm;
if (useSpread && market.amm.baseSpread > 0) {
const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, mmOraclePriceData, undefined, latestSlot);
amm = {
baseAssetReserve,
quoteAssetReserve,
sqrtK: sqrtK,
pegMultiplier: newPeg,
};
}
else {
amm = market.amm;
}
const [newQuoteAssetReserve, newBaseAssetReserve] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, inputAssetType, amount, swapDirection);
const acquiredBase = amm.baseAssetReserve.sub(newBaseAssetReserve);
const acquiredQuote = amm.quoteAssetReserve.sub(newQuoteAssetReserve);
const acquiredQuoteAssetAmount = (0, amm_1.calculateQuoteAssetAmountSwapped)(acquiredQuote.abs(), amm.pegMultiplier, swapDirection);
return [acquiredBase, acquiredQuote, acquiredQuoteAssetAmount];
}
exports.calculateTradeAcquiredAmounts = calculateTradeAcquiredAmounts;
/**
* calculateTargetPriceTrade
* simple function for finding arbitraging trades
*
* @deprecated
*
* @param market
* @param targetPrice
* @param pct optional default is 100% gap filling, can set smaller.
* @param outputAssetType which asset to trade.
* @param useSpread whether or not to consider the spread when calculating the trade size
* @returns trade direction/size in order to push price to a targetPrice,
*
* [
* direction => direction of trade required, PositionDirection
* tradeSize => size of trade required, TODO-PRECISION
* entryPrice => the entry price for the trade, PRICE_PRECISION
* targetPrice => the target price PRICE_PRECISION
* ]
*/
function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAssetType = 'quote', mmOraclePriceData, useSpread = true, latestSlot) {
(0, assert_1.assert)(market.amm.baseAssetReserve.gt(numericConstants_1.ZERO));
(0, assert_1.assert)(targetPrice.gt(numericConstants_1.ZERO));
(0, assert_1.assert)(pct.lte(MAXPCT) && pct.gt(numericConstants_1.ZERO));
const reservePriceBefore = (0, market_1.calculateReservePrice)(market, mmOraclePriceData);
const bidPriceBefore = (0, market_1.calculateBidPrice)(market, mmOraclePriceData);
const askPriceBefore = (0, market_1.calculateAskPrice)(market, mmOraclePriceData);
let direction;
if (targetPrice.gt(reservePriceBefore)) {
const priceGap = targetPrice.sub(reservePriceBefore);
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
targetPrice = reservePriceBefore.add(priceGapScaled);
direction = types_1.PositionDirection.LONG;
}
else {
const priceGap = reservePriceBefore.sub(targetPrice);
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
targetPrice = reservePriceBefore.sub(priceGapScaled);
direction = types_1.PositionDirection.SHORT;
}
let tradeSize;
let baseSize;
let baseAssetReserveBefore;
let quoteAssetReserveBefore;
let peg = market.amm.pegMultiplier;
if (useSpread && market.amm.baseSpread > 0) {
const { baseAssetReserve, quoteAssetReserve, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, mmOraclePriceData, undefined, latestSlot);
baseAssetReserveBefore = baseAssetReserve;
quoteAssetReserveBefore = quoteAssetReserve;
peg = newPeg;
}
else {
baseAssetReserveBefore = market.amm.baseAssetReserve;
quoteAssetReserveBefore = market.amm.quoteAssetReserve;
}
const invariant = market.amm.sqrtK.mul(market.amm.sqrtK);
const k = invariant.mul(numericConstants_1.PRICE_PRECISION);
let baseAssetReserveAfter;
let quoteAssetReserveAfter;
const biasModifier = new anchor_1.BN(1);
let markPriceAfter;
if (useSpread &&
targetPrice.lt(askPriceBefore) &&
targetPrice.gt(bidPriceBefore)) {
// no trade, market is at target
if (reservePriceBefore.gt(targetPrice)) {
direction = types_1.PositionDirection.SHORT;
}
else {
direction = types_1.PositionDirection.LONG;
}
tradeSize = numericConstants_1.ZERO;
return [direction, tradeSize, targetPrice, targetPrice];
}
else if (reservePriceBefore.gt(targetPrice)) {
// overestimate y2
baseAssetReserveAfter = (0, utils_1.squareRootBN)(k.div(targetPrice).mul(peg).div(numericConstants_1.PEG_PRECISION).sub(biasModifier)).sub(new anchor_1.BN(1));
quoteAssetReserveAfter = k.div(numericConstants_1.PRICE_PRECISION).div(baseAssetReserveAfter);
markPriceAfter = (0, amm_1.calculatePrice)(baseAssetReserveAfter, quoteAssetReserveAfter, peg);
direction = types_1.PositionDirection.SHORT;
tradeSize = quoteAssetReserveBefore
.sub(quoteAssetReserveAfter)
.mul(peg)
.div(numericConstants_1.PEG_PRECISION)
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
baseSize = baseAssetReserveAfter.sub(baseAssetReserveBefore);
}
else if (reservePriceBefore.lt(targetPrice)) {
// underestimate y2
baseAssetReserveAfter = (0, utils_1.squareRootBN)(k.div(targetPrice).mul(peg).div(numericConstants_1.PEG_PRECISION).add(biasModifier)).add(new anchor_1.BN(1));
quoteAssetReserveAfter = k.div(numericConstants_1.PRICE_PRECISION).div(baseAssetReserveAfter);
markPriceAfter = (0, amm_1.calculatePrice)(baseAssetReserveAfter, quoteAssetReserveAfter, peg);
direction = types_1.PositionDirection.LONG;
tradeSize = quoteAssetReserveAfter
.sub(quoteAssetReserveBefore)
.mul(peg)
.div(numericConstants_1.PEG_PRECISION)
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
baseSize = baseAssetReserveBefore.sub(baseAssetReserveAfter);
}
else {
// no trade, market is at target
direction = types_1.PositionDirection.LONG;
tradeSize = numericConstants_1.ZERO;
return [direction, tradeSize, targetPrice, targetPrice];
}
let tp1 = targetPrice;
let tp2 = markPriceAfter;
let originalDiff = targetPrice.sub(reservePriceBefore);
if (direction == types_1.PositionDirection.SHORT) {
tp1 = markPriceAfter;
tp2 = targetPrice;
originalDiff = reservePriceBefore.sub(targetPrice);
}
const entryPrice = tradeSize
.mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO)
.mul(numericConstants_1.PRICE_PRECISION)
.div(baseSize.abs());
(0, assert_1.assert)(tp1.sub(tp2).lte(originalDiff), 'Target Price Calculation incorrect');
(0, assert_1.assert)(tp2.lte(tp1) || tp2.sub(tp1).abs() < 100000, 'Target Price Calculation incorrect' +
tp2.toString() +
'>=' +
tp1.toString() +
'err: ' +
tp2.sub(tp1).abs().toString());
if (outputAssetType == 'quote') {
return [direction, tradeSize, entryPrice, targetPrice];
}
else {
return [direction, baseSize, entryPrice, targetPrice];
}
}
exports.calculateTargetPriceTrade = calculateTargetPriceTrade;
/**
* Calculates the estimated entry price and price impact of order, in base or quote
* Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
*
* @param assetType
* @param amount
* @param direction
* @param market
* @param oraclePriceData
* @param dlob
* @param slot
* @param usersToSkip
*/
function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, mmOraclePriceData, dlob, slot, usersToSkip = new Map()) {
if (amount.eq(numericConstants_1.ZERO)) {
return {
entryPrice: numericConstants_1.ZERO,
priceImpact: numericConstants_1.ZERO,
bestPrice: numericConstants_1.ZERO,
worstPrice: numericConstants_1.ZERO,
baseFilled: numericConstants_1.ZERO,
quoteFilled: numericConstants_1.ZERO,
};
}
const takerIsLong = (0, types_2.isVariant)(direction, 'long');
const limitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, mmOraclePriceData);
const swapDirection = (0, amm_1.getSwapDirection)(assetType, direction);
const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, mmOraclePriceData, undefined, new anchor_1.BN(slot));
const amm = {
baseAssetReserve,
quoteAssetReserve,
sqrtK: sqrtK,
pegMultiplier: newPeg,
};
const [ammBids, ammAsks] = (0, amm_1.calculateMarketOpenBidAsk)(market.amm.baseAssetReserve, market.amm.minBaseAssetReserve, market.amm.maxBaseAssetReserve, market.amm.orderStepSize);
let ammLiquidity;
if (assetType === 'base') {
ammLiquidity = takerIsLong ? ammAsks.abs() : ammBids;
}
else {
const [afterSwapQuoteReserves, _] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'base', takerIsLong ? ammAsks.abs() : ammBids, (0, amm_1.getSwapDirection)('base', direction));
ammLiquidity = (0, amm_1.calculateQuoteAssetAmountSwapped)(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
}
const invariant = amm.sqrtK.mul(amm.sqrtK);
let bestPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
let cumulativeBaseFilled = numericConstants_1.ZERO;
let cumulativeQuoteFilled = numericConstants_1.ZERO;
let limitOrder = limitOrders.next().value;
if (limitOrder) {
const limitOrderPrice = limitOrder.getPrice(mmOraclePriceData, slot);
bestPrice = takerIsLong
? anchor_1.BN.min(limitOrderPrice, bestPrice)
: anchor_1.BN.max(limitOrderPrice, bestPrice);
}
let worstPrice = bestPrice;
if (assetType === 'base') {
while (!cumulativeBaseFilled.eq(amount) &&
(ammLiquidity.gt(numericConstants_1.ZERO) || limitOrder)) {
const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(mmOraclePriceData, slot);
let maxAmmFill;
if (limitOrderPrice) {
const newBaseReserves = (0, utils_1.squareRootBN)(invariant
.mul(numericConstants_1.PRICE_PRECISION)
.mul(amm.pegMultiplier)
.div(limitOrderPrice)
.div(numericConstants_1.PEG_PRECISION));
// will be zero if the limit order price is better than the amm price
maxAmmFill = takerIsLong
? amm.baseAssetReserve.sub(newBaseReserves)
: newBaseReserves.sub(amm.baseAssetReserve);
}
else {
maxAmmFill = amount.sub(cumulativeBaseFilled);
}
maxAmmFill = anchor_1.BN.min(maxAmmFill, ammLiquidity);
if (maxAmmFill.gt(numericConstants_1.ZERO)) {
const baseFilled = anchor_1.BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'base', baseFilled, swapDirection);
ammLiquidity = ammLiquidity.sub(baseFilled);
const quoteFilled = (0, amm_1.calculateQuoteAssetAmountSwapped)(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
amm.baseAssetReserve = afterSwapBaseReserves;
amm.quoteAssetReserve = afterSwapQuoteReserves;
worstPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
if (cumulativeBaseFilled.eq(amount)) {
break;
}
}
if (!limitOrder) {
continue;
}
if (usersToSkip.has(limitOrder.userAccount)) {
continue;
}
const baseFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount.sub(limitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
const quoteFilled = baseFilled.mul(limitOrderPrice).div(numericConstants_1.BASE_PRECISION);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = limitOrderPrice;
if (cumulativeBaseFilled.eq(amount)) {
break;
}
limitOrder = limitOrders.next().value;
}
}
else {
while (!cumulativeQuoteFilled.eq(amount) &&
(ammLiquidity.gt(numericConstants_1.ZERO) || limitOrder)) {
const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(mmOraclePriceData, slot);
let maxAmmFill;
if (limitOrderPrice) {
const newQuoteReserves = (0, utils_1.squareRootBN)(invariant
.mul(numericConstants_1.PEG_PRECISION)
.mul(limitOrderPrice)
.div(amm.pegMultiplier)
.div(numericConstants_1.PRICE_PRECISION));
// will be zero if the limit order price is better than the amm price
maxAmmFill = takerIsLong
? newQuoteReserves.sub(amm.quoteAssetReserve)
: amm.quoteAssetReserve.sub(newQuoteReserves);
}
else {
maxAmmFill = amount.sub(cumulativeQuoteFilled);
}
maxAmmFill = anchor_1.BN.min(maxAmmFill, ammLiquidity);
if (maxAmmFill.gt(numericConstants_1.ZERO)) {
const quoteFilled = anchor_1.BN.min(amount.sub(cumulativeQuoteFilled), maxAmmFill);
const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'quote', quoteFilled, swapDirection);
ammLiquidity = ammLiquidity.sub(quoteFilled);
const baseFilled = afterSwapBaseReserves
.sub(amm.baseAssetReserve)
.abs();
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
amm.baseAssetReserve = afterSwapBaseReserves;
amm.quoteAssetReserve = afterSwapQuoteReserves;
worstPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
if (cumulativeQuoteFilled.eq(amount)) {
break;
}
}
if (!limitOrder) {
continue;
}
if (usersToSkip.has(limitOrder.userAccount)) {
continue;
}
const quoteFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount
.sub(limitOrder.order.baseAssetAmountFilled)
.mul(limitOrderPrice)
.div(numericConstants_1.BASE_PRECISION), amount.sub(cumulativeQuoteFilled));
const baseFilled = quoteFilled.mul(numericConstants_1.BASE_PRECISION).div(limitOrderPrice);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = limitOrderPrice;
if (cumulativeQuoteFilled.eq(amount)) {
break;
}
limitOrder = limitOrders.next().value;
}
}
const entryPrice = cumulativeBaseFilled && cumulativeBaseFilled.gt(numericConstants_1.ZERO)
? cumulativeQuoteFilled.mul(numericConstants_1.BASE_PRECISION).div(cumulativeBaseFilled)
: numericConstants_1.ZERO;
const priceImpact = bestPrice && bestPrice.gt(numericConstants_1.ZERO)
? entryPrice.sub(bestPrice).mul(numericConstants_1.PRICE_PRECISION).div(bestPrice).abs()
: numericConstants_1.ZERO;
return {
entryPrice,
priceImpact,
bestPrice,
worstPrice,
baseFilled: cumulativeBaseFilled,
quoteFilled: cumulativeQuoteFilled,
};
}
exports.calculateEstimatedPerpEntryPrice = calculateEstimatedPerpEntryPrice;
/**
* Calculates the estimated entry price and price impact of order, in base or quote
* Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or serum)
*
* @param assetType
* @param amount
* @param direction
* @param market
* @param oraclePriceData
* @param dlob
* @param serumBids
* @param serumAsks
* @param slot
* @param usersToSkip
*/
function calculateEstimatedSpotEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, serumBids, serumAsks, slot, usersToSkip = new Map()) {
if (amount.eq(numericConstants_1.ZERO)) {
return {
entryPrice: numericConstants_1.ZERO,
priceImpact: numericConstants_1.ZERO,
bestPrice: numericConstants_1.ZERO,
worstPrice: numericConstants_1.ZERO,
baseFilled: numericConstants_1.ZERO,
quoteFilled: numericConstants_1.ZERO,
};
}
const basePrecision = new anchor_1.BN(Math.pow(10, market.decimals));
const takerIsLong = (0, types_2.isVariant)(direction, 'long');
const dlobLimitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.SPOT, oraclePriceData);
const serumLimitOrders = takerIsLong
? serumAsks.getL2(100)
: serumBids.getL2(100);
let cumulativeBaseFilled = numericConstants_1.ZERO;
let cumulativeQuoteFilled = numericConstants_1.ZERO;
let dlobLimitOrder = dlobLimitOrders.next().value;
let serumLimitOrder = serumLimitOrders.shift();
const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
const serumLimitOrderPrice = serumLimitOrder
? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
: undefined;
const bestPrice = takerIsLong
? anchor_1.BN.min(serumLimitOrderPrice || numericConstants_1.BN_MAX, dlobLimitOrderPrice || numericConstants_1.BN_MAX)
: anchor_1.BN.max(serumLimitOrderPrice || numericConstants_1.ZERO, dlobLimitOrderPrice || numericConstants_1.ZERO);
let worstPrice = bestPrice;
if (assetType === 'base') {
while (!cumulativeBaseFilled.eq(amount) &&
(dlobLimitOrder || serumLimitOrder)) {
const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
const serumLimitOrderPrice = serumLimitOrder
? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
: undefined;
const useSerum = takerIsLong
? (serumLimitOrderPrice || numericConstants_1.BN_MAX).lt(dlobLimitOrderPrice || numericConstants_1.BN_MAX)
: (serumLimitOrderPrice || numericConstants_1.ZERO).gt(dlobLimitOrderPrice || numericConstants_1.ZERO);
if (!useSerum) {
if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
continue;
}
const baseFilled = anchor_1.BN.min(dlobLimitOrder.order.baseAssetAmount.sub(dlobLimitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
const quoteFilled = baseFilled
.mul(dlobLimitOrderPrice)
.div(basePrecision);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = dlobLimitOrderPrice;
dlobLimitOrder = dlobLimitOrders.next().value;
}
else {
const baseFilled = anchor_1.BN.min(new anchor_1.BN(serumLimitOrder[1] * basePrecision.toNumber()), amount.sub(cumulativeBaseFilled));
const quoteFilled = baseFilled
.mul(serumLimitOrderPrice)
.div(basePrecision);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = serumLimitOrderPrice;
serumLimitOrder = serumLimitOrders.shift();
}
}
}
else {
while (!cumulativeQuoteFilled.eq(amount) &&
(dlobLimitOrder || serumLimitOrder)) {
const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
const serumLimitOrderPrice = serumLimitOrder
? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
: undefined;
const useSerum = takerIsLong
? (serumLimitOrderPrice || numericConstants_1.BN_MAX).lt(dlobLimitOrderPrice || numericConstants_1.BN_MAX)
: (serumLimitOrderPrice || numericConstants_1.ZERO).gt(dlobLimitOrderPrice || numericConstants_1.ZERO);
if (!useSerum) {
if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
continue;
}
const quoteFilled = anchor_1.BN.min(dlobLimitOrder.order.baseAssetAmount
.sub(dlobLimitOrder.order.baseAssetAmountFilled)
.mul(dlobLimitOrderPrice)
.div(basePrecision), amount.sub(cumulativeQuoteFilled));
const baseFilled = quoteFilled
.mul(basePrecision)
.div(dlobLimitOrderPrice);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = dlobLimitOrderPrice;
dlobLimitOrder = dlobLimitOrders.next().value;
}
else {
const serumOrderBaseAmount = new anchor_1.BN(serumLimitOrder[1] * basePrecision.toNumber());
const quoteFilled = anchor_1.BN.min(serumOrderBaseAmount.mul(serumLimitOrderPrice).div(basePrecision), amount.sub(cumulativeQuoteFilled));
const baseFilled = quoteFilled
.mul(basePrecision)
.div(serumLimitOrderPrice);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
worstPrice = serumLimitOrderPrice;
serumLimitOrder = serumLimitOrders.shift();
}
}
}
const entryPrice = cumulativeBaseFilled && cumulativeBaseFilled.gt(numericConstants_1.ZERO)
? cumulativeQuoteFilled.mul(basePrecision).div(cumulativeBaseFilled)
: numericConstants_1.ZERO;
const priceImpact = bestPrice && bestPrice.gt(numericConstants_1.ZERO)
? entryPrice.sub(bestPrice).mul(numericConstants_1.PRICE_PRECISION).div(bestPrice).abs()
: numericConstants_1.ZERO;
return {
entryPrice,
priceImpact,
bestPrice,
worstPrice,
baseFilled: cumulativeBaseFilled,
quoteFilled: cumulativeQuoteFilled,
};
}
exports.calculateEstimatedSpotEntryPrice = calculateEstimatedSpotEntryPrice;
function calculateEstimatedEntryPriceWithL2(assetType, amount, direction, basePrecision, l2) {
const takerIsLong = (0, types_2.isVariant)(direction, 'long');
let cumulativeBaseFilled = numericConstants_1.ZERO;
let cumulativeQuoteFilled = numericConstants_1.ZERO;
const levels = [...(takerIsLong ? l2.asks : l2.bids)];
let nextLevel = levels.shift();
let bestPrice;
let worstPrice;
if (nextLevel) {
bestPrice = nextLevel.price;
worstPrice = nextLevel.price;
}
else {
bestPrice = takerIsLong ? numericConstants_1.BN_MAX : numericConstants_1.ZERO;
worstPrice = bestPrice;
}
if (assetType === 'base') {
while (!cumulativeBaseFilled.eq(amount) && nextLevel) {
const price = nextLevel.price;
const size = nextLevel.size;
worstPrice = price;
const baseFilled = anchor_1.BN.min(size, amount.sub(cumulativeBaseFilled));
const quoteFilled = baseFilled.mul(price).div(basePrecision);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
nextLevel = levels.shift();
}
}
else {
while (!cumulativeQuoteFilled.eq(amount) && nextLevel) {
const price = nextLevel.price;
const size = nextLevel.size;
worstPrice = price;
const quoteFilled = anchor_1.BN.min(size.mul(price).div(basePrecision), amount.sub(cumulativeQuoteFilled));
const baseFilled = quoteFilled.mul(basePrecision).div(price);
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
nextLevel = levels.shift();
}
}
const entryPrice = cumulativeBaseFilled && cumulativeBaseFilled.gt(numericConstants_1.ZERO)
? cumulativeQuoteFilled.mul(basePrecision).div(cumulativeBaseFilled)
: numericConstants_1.ZERO;
const priceImpact = bestPrice && bestPrice.gt(numericConstants_1.ZERO)
? entryPrice.sub(bestPrice).mul(numericConstants_1.PRICE_PRECISION).div(bestPrice).abs()
: numericConstants_1.ZERO;
return {
entryPrice,
priceImpact,
bestPrice,
worstPrice,
baseFilled: cumulativeBaseFilled,
quoteFilled: cumulativeQuoteFilled,
};
}
exports.calculateEstimatedEntryPriceWithL2 = calculateEstimatedEntryPriceWithL2;
function getUser30dRollingVolumeEstimate(userStatsAccount, now) {
now = now || new anchor_1.BN(new Date().getTime() / 1000);
const sinceLastTaker = anchor_1.BN.max(now.sub(userStatsAccount.lastTakerVolume30DTs), numericConstants_1.ZERO);
const sinceLastMaker = anchor_1.BN.max(now.sub(userStatsAccount.lastMakerVolume30DTs), numericConstants_1.ZERO);
const thirtyDaysInSeconds = new anchor_1.BN(60 * 60 * 24 * 30);
const last30dVolume = userStatsAccount.takerVolume30D
.mul(anchor_1.BN.max(thirtyDaysInSeconds.sub(sinceLastTaker), numericConstants_1.ZERO))
.div(thirtyDaysInSeconds)
.add(userStatsAccount.makerVolume30D
.mul(anchor_1.BN.max(thirtyDaysInSeconds.sub(sinceLastMaker), numericConstants_1.ZERO))
.div(thirtyDaysInSeconds));
return last30dVolume;
}
exports.getUser30dRollingVolumeEstimate = getUser30dRollingVolumeEstimate;