UNPKG

@drift-labs/sdk-browser

Version:
638 lines (637 loc) 31.8 kB
"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;