UNPKG

@drift-labs/sdk

Version:
671 lines (670 loc) • 36.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.getQuoteAssetReservePredictionMarketBounds = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateReferencePriceOffset = exports.calculateInventoryScale = exports.calculateInventoryLiquidityRatio = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0; const anchor_1 = require("@coral-xyz/anchor"); const numericConstants_1 = require("../constants/numericConstants"); const types_1 = require("../types"); const assert_1 = require("../assert/assert"); const utils_1 = require("./utils"); const orders_1 = require("./orders"); const repeg_1 = require("./repeg"); const oracles_1 = require("./oracles"); function calculatePegFromTargetPrice(targetPrice, baseAssetReserve, quoteAssetReserve) { return anchor_1.BN.max(targetPrice .mul(baseAssetReserve) .div(quoteAssetReserve) .add(numericConstants_1.PRICE_DIV_PEG.div(new anchor_1.BN(2))) .div(numericConstants_1.PRICE_DIV_PEG), numericConstants_1.ONE); } exports.calculatePegFromTargetPrice = calculatePegFromTargetPrice; function calculateOptimalPegAndBudget(amm, mmOraclePriceData) { const reservePriceBefore = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier); const targetPrice = mmOraclePriceData.price; const newPeg = calculatePegFromTargetPrice(targetPrice, amm.baseAssetReserve, amm.quoteAssetReserve); const prePegCost = (0, repeg_1.calculateRepegCost)(amm, newPeg); const totalFeeLB = amm.totalExchangeFee.div(new anchor_1.BN(2)); const budget = anchor_1.BN.max(numericConstants_1.ZERO, amm.totalFeeMinusDistributions.sub(totalFeeLB)); let checkLowerBound = true; if (budget.lt(prePegCost)) { const halfMaxPriceSpread = new anchor_1.BN(amm.maxSpread) .div(new anchor_1.BN(2)) .mul(targetPrice) .div(numericConstants_1.BID_ASK_SPREAD_PRECISION); let newTargetPrice; let newOptimalPeg; let newBudget; const targetPriceGap = reservePriceBefore.sub(targetPrice); if (targetPriceGap.abs().gt(halfMaxPriceSpread)) { const markAdj = targetPriceGap.abs().sub(halfMaxPriceSpread); if (targetPriceGap.lt(new anchor_1.BN(0))) { newTargetPrice = reservePriceBefore.add(markAdj); } else { newTargetPrice = reservePriceBefore.sub(markAdj); } newOptimalPeg = calculatePegFromTargetPrice(newTargetPrice, amm.baseAssetReserve, amm.quoteAssetReserve); newBudget = (0, repeg_1.calculateRepegCost)(amm, newOptimalPeg); checkLowerBound = false; return [newTargetPrice, newOptimalPeg, newBudget, false]; } else if (amm.totalFeeMinusDistributions.lt(amm.totalExchangeFee.div(new anchor_1.BN(2)))) { checkLowerBound = false; } } return [targetPrice, newPeg, budget, checkLowerBound]; } exports.calculateOptimalPegAndBudget = calculateOptimalPegAndBudget; function calculateNewAmm(amm, mmOraclePriceData) { let pKNumer = new anchor_1.BN(1); let pKDenom = new anchor_1.BN(1); const [targetPrice, _newPeg, budget, _checkLowerBound] = calculateOptimalPegAndBudget(amm, mmOraclePriceData); let prePegCost = (0, repeg_1.calculateRepegCost)(amm, _newPeg); let newPeg = _newPeg; if (prePegCost.gte(budget) && prePegCost.gt(numericConstants_1.ZERO)) { [pKNumer, pKDenom] = [new anchor_1.BN(999), new anchor_1.BN(1000)]; const deficitMadeup = (0, repeg_1.calculateAdjustKCost)(amm, pKNumer, pKDenom); (0, assert_1.assert)(deficitMadeup.lte(new anchor_1.BN(0))); prePegCost = budget.add(deficitMadeup.abs()); const newAmm = Object.assign({}, amm); newAmm.baseAssetReserve = newAmm.baseAssetReserve.mul(pKNumer).div(pKDenom); newAmm.sqrtK = newAmm.sqrtK.mul(pKNumer).div(pKDenom); const invariant = newAmm.sqrtK.mul(newAmm.sqrtK); newAmm.quoteAssetReserve = invariant.div(newAmm.baseAssetReserve); const directionToClose = amm.baseAssetAmountWithAmm.gt(numericConstants_1.ZERO) ? types_1.PositionDirection.SHORT : types_1.PositionDirection.LONG; const [newQuoteAssetReserve, _newBaseAssetReserve] = calculateAmmReservesAfterSwap(newAmm, 'base', amm.baseAssetAmountWithAmm.abs(), getSwapDirection('base', directionToClose)); newAmm.terminalQuoteAssetReserve = newQuoteAssetReserve; newPeg = (0, repeg_1.calculateBudgetedPeg)(newAmm, prePegCost, targetPrice); prePegCost = (0, repeg_1.calculateRepegCost)(newAmm, newPeg); } return [prePegCost, pKNumer, pKDenom, newPeg]; } exports.calculateNewAmm = calculateNewAmm; function calculateUpdatedAMM(amm, mmOraclePriceData) { if (amm.curveUpdateIntensity == 0 || mmOraclePriceData === undefined) { return amm; } const newAmm = Object.assign({}, amm); const [prepegCost, pKNumer, pKDenom, newPeg] = calculateNewAmm(amm, mmOraclePriceData); newAmm.baseAssetReserve = newAmm.baseAssetReserve.mul(pKNumer).div(pKDenom); newAmm.sqrtK = newAmm.sqrtK.mul(pKNumer).div(pKDenom); const invariant = newAmm.sqrtK.mul(newAmm.sqrtK); newAmm.quoteAssetReserve = invariant.div(newAmm.baseAssetReserve); newAmm.pegMultiplier = newPeg; const directionToClose = amm.baseAssetAmountWithAmm.gt(numericConstants_1.ZERO) ? types_1.PositionDirection.SHORT : types_1.PositionDirection.LONG; const [newQuoteAssetReserve, _newBaseAssetReserve] = calculateAmmReservesAfterSwap(newAmm, 'base', amm.baseAssetAmountWithAmm.abs(), getSwapDirection('base', directionToClose)); newAmm.terminalQuoteAssetReserve = newQuoteAssetReserve; newAmm.totalFeeMinusDistributions = newAmm.totalFeeMinusDistributions.sub(prepegCost); newAmm.netRevenueSinceLastFunding = newAmm.netRevenueSinceLastFunding.sub(prepegCost); return newAmm; } exports.calculateUpdatedAMM = calculateUpdatedAMM; function calculateUpdatedAMMSpreadReserves(amm, direction, mmOraclePriceData, isPrediction = false, latestSlot) { const newAmm = calculateUpdatedAMM(amm, mmOraclePriceData); const [shortReserves, longReserves] = calculateSpreadReserves(newAmm, mmOraclePriceData, undefined, isPrediction, latestSlot); const dirReserves = (0, types_1.isVariant)(direction, 'long') ? longReserves : shortReserves; const result = { baseAssetReserve: dirReserves.baseAssetReserve, quoteAssetReserve: dirReserves.quoteAssetReserve, sqrtK: newAmm.sqrtK, newPeg: newAmm.pegMultiplier, }; return result; } exports.calculateUpdatedAMMSpreadReserves = calculateUpdatedAMMSpreadReserves; function calculateBidAskPrice(amm, mmOraclePriceData, withUpdate = true, isPrediction = false, latestSlot) { let newAmm; if (withUpdate) { newAmm = calculateUpdatedAMM(amm, mmOraclePriceData); } else { newAmm = amm; } const [bidReserves, askReserves] = calculateSpreadReserves(newAmm, mmOraclePriceData, undefined, isPrediction, latestSlot); const askPrice = calculatePrice(askReserves.baseAssetReserve, askReserves.quoteAssetReserve, newAmm.pegMultiplier); const bidPrice = calculatePrice(bidReserves.baseAssetReserve, bidReserves.quoteAssetReserve, newAmm.pegMultiplier); return [bidPrice, askPrice]; } exports.calculateBidAskPrice = calculateBidAskPrice; /** * Calculates a price given an arbitrary base and quote amount (they must have the same precision) * * @param baseAssetReserves * @param quoteAssetReserves * @param pegMultiplier * @returns price : Precision PRICE_PRECISION */ function calculatePrice(baseAssetReserves, quoteAssetReserves, pegMultiplier) { if (baseAssetReserves.abs().lte(numericConstants_1.ZERO)) { return new anchor_1.BN(0); } return quoteAssetReserves .mul(numericConstants_1.PRICE_PRECISION) .mul(pegMultiplier) .div(numericConstants_1.PEG_PRECISION) .div(baseAssetReserves); } exports.calculatePrice = calculatePrice; /** * Calculates what the amm reserves would be after swapping a quote or base asset amount. * * @param amm * @param inputAssetType * @param swapAmount * @param swapDirection * @returns quoteAssetReserve and baseAssetReserve after swap. : Precision AMM_RESERVE_PRECISION */ function calculateAmmReservesAfterSwap(amm, inputAssetType, swapAmount, swapDirection) { (0, assert_1.assert)(swapAmount.gte(numericConstants_1.ZERO), 'swapAmount must be greater than 0'); let newQuoteAssetReserve; let newBaseAssetReserve; if (inputAssetType === 'quote') { swapAmount = swapAmount .mul(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO) .div(amm.pegMultiplier); [newQuoteAssetReserve, newBaseAssetReserve] = calculateSwapOutput(amm.quoteAssetReserve, swapAmount, swapDirection, amm.sqrtK.mul(amm.sqrtK)); } else { [newBaseAssetReserve, newQuoteAssetReserve] = calculateSwapOutput(amm.baseAssetReserve, swapAmount, swapDirection, amm.sqrtK.mul(amm.sqrtK)); } return [newQuoteAssetReserve, newBaseAssetReserve]; } exports.calculateAmmReservesAfterSwap = calculateAmmReservesAfterSwap; function calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, stepSize) { // open orders let openAsks; if (minBaseAssetReserve.lt(baseAssetReserve)) { openAsks = baseAssetReserve.sub(minBaseAssetReserve).mul(new anchor_1.BN(-1)); if (stepSize && openAsks.abs().div(numericConstants_1.TWO).lt(stepSize)) { openAsks = numericConstants_1.ZERO; } } else { openAsks = numericConstants_1.ZERO; } let openBids; if (maxBaseAssetReserve.gt(baseAssetReserve)) { openBids = maxBaseAssetReserve.sub(baseAssetReserve); if (stepSize && openBids.div(numericConstants_1.TWO).lt(stepSize)) { openBids = numericConstants_1.ZERO; } } else { openBids = numericConstants_1.ZERO; } return [openBids, openAsks]; } exports.calculateMarketOpenBidAsk = calculateMarketOpenBidAsk; function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve) { // inventory skew const [openBids, openAsks] = calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve); const minSideLiquidity = anchor_1.BN.min(openBids.abs(), openAsks.abs()); const inventoryScaleBN = anchor_1.BN.min(baseAssetAmountWithAmm .mul(numericConstants_1.PERCENTAGE_PRECISION) .div(anchor_1.BN.max(minSideLiquidity, numericConstants_1.ONE)) .abs(), numericConstants_1.PERCENTAGE_PRECISION); return inventoryScaleBN; } exports.calculateInventoryLiquidityRatio = calculateInventoryLiquidityRatio; function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) { if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) { return 1; } const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10)); const inventoryScaleBN = calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve); const inventoryScaleMaxBN = anchor_1.BN.max(MAX_BID_ASK_INVENTORY_SKEW_FACTOR, new anchor_1.BN(maxSpread) .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION) .div(new anchor_1.BN(Math.max(directionalSpread, 1)))); const inventoryScaleCapped = anchor_1.BN.min(inventoryScaleMaxBN, numericConstants_1.BID_ASK_SPREAD_PRECISION.add(inventoryScaleMaxBN.mul(inventoryScaleBN).div(numericConstants_1.PERCENTAGE_PRECISION))).toNumber() / numericConstants_1.BID_ASK_SPREAD_PRECISION.toNumber(); return inventoryScaleCapped; } exports.calculateInventoryScale = calculateInventoryScale; function calculateReferencePriceOffset(reservePrice, last24hAvgFundingRate, liquidityFraction, oracleTwapFast, markTwapFast, oracleTwapSlow, markTwapSlow, maxOffsetPct) { if (last24hAvgFundingRate.eq(numericConstants_1.ZERO)) { return numericConstants_1.ZERO; } const maxOffsetInPrice = new anchor_1.BN(maxOffsetPct) .mul(reservePrice) .div(numericConstants_1.PERCENTAGE_PRECISION); // Calculate quote denominated market premium const markPremiumMinute = (0, utils_1.clampBN)(markTwapFast.sub(oracleTwapFast), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice); const markPremiumHour = (0, utils_1.clampBN)(markTwapSlow.sub(oracleTwapSlow), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice); // Convert last24hAvgFundingRate to quote denominated premium const markPremiumDay = (0, utils_1.clampBN)(last24hAvgFundingRate.div(numericConstants_1.FUNDING_RATE_BUFFER_PRECISION).mul(new anchor_1.BN(24)), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice); // Take average clamped premium as the price-based offset const markPremiumAvg = markPremiumMinute .add(markPremiumHour) .add(markPremiumDay) .div(new anchor_1.BN(3)); const markPremiumAvgPct = markPremiumAvg .mul(numericConstants_1.PRICE_PRECISION) .div(reservePrice); const inventoryPct = (0, utils_1.clampBN)(liquidityFraction.mul(new anchor_1.BN(maxOffsetPct)).div(numericConstants_1.PERCENTAGE_PRECISION), new anchor_1.BN(maxOffsetPct).mul(new anchor_1.BN(-1)), new anchor_1.BN(maxOffsetPct)); // Only apply when inventory is consistent with recent and 24h market premium let offsetPct = markPremiumAvgPct.add(inventoryPct); if (!(0, utils_1.sigNum)(inventoryPct).eq((0, utils_1.sigNum)(markPremiumAvgPct))) { offsetPct = numericConstants_1.ZERO; } const clampedOffsetPct = (0, utils_1.clampBN)(offsetPct, new anchor_1.BN(-maxOffsetPct), new anchor_1.BN(maxOffsetPct)); return clampedOffsetPct; } exports.calculateReferencePriceOffset = calculateReferencePriceOffset; function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) { // vAMM skew const netBaseAssetValue = quoteAssetReserve .sub(terminalQuoteAssetReserve) .mul(pegMultiplier) .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO); const localBaseAssetValue = netBaseAssetAmount .mul(reservePrice) .div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO.mul(numericConstants_1.PRICE_PRECISION)); const effectiveGap = Math.max(0, localBaseAssetValue.sub(netBaseAssetValue).toNumber()); const effectiveLeverage = effectiveGap / (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) + 1 / numericConstants_1.QUOTE_PRECISION.toNumber(); return effectiveLeverage; } exports.calculateEffectiveLeverage = calculateEffectiveLeverage; function calculateMaxSpread(marginRatioInitial) { const maxTargetSpread = new anchor_1.BN(marginRatioInitial) .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(numericConstants_1.MARGIN_PRECISION)) .toNumber(); return maxTargetSpread; } exports.calculateMaxSpread = calculateMaxSpread; function calculateVolSpreadBN(lastOracleConfPct, reservePrice, markStd, oracleStd, longIntensity, shortIntensity, volume24H) { const marketAvgStdPct = markStd .add(oracleStd) .mul(numericConstants_1.PERCENTAGE_PRECISION) .div(reservePrice) .div(new anchor_1.BN(4)); const volSpread = anchor_1.BN.max(lastOracleConfPct, marketAvgStdPct.div(new anchor_1.BN(2))); const clampMin = numericConstants_1.PERCENTAGE_PRECISION.div(new anchor_1.BN(100)); const clampMax = numericConstants_1.PERCENTAGE_PRECISION; const longVolSpreadFactor = (0, utils_1.clampBN)(longIntensity.mul(numericConstants_1.PERCENTAGE_PRECISION).div(anchor_1.BN.max(numericConstants_1.ONE, volume24H)), clampMin, clampMax); const shortVolSpreadFactor = (0, utils_1.clampBN)(shortIntensity.mul(numericConstants_1.PERCENTAGE_PRECISION).div(anchor_1.BN.max(numericConstants_1.ONE, volume24H)), clampMin, clampMax); // only consider confidence interval at full value when above 25 bps let confComponent = lastOracleConfPct; if (lastOracleConfPct.lte(numericConstants_1.PRICE_PRECISION.div(new anchor_1.BN(400)))) { confComponent = lastOracleConfPct.div(new anchor_1.BN(20)); } const longVolSpread = anchor_1.BN.max(confComponent, volSpread.mul(longVolSpreadFactor).div(numericConstants_1.PERCENTAGE_PRECISION)); const shortVolSpread = anchor_1.BN.max(confComponent, volSpread.mul(shortVolSpreadFactor).div(numericConstants_1.PERCENTAGE_PRECISION)); return [longVolSpread, shortVolSpread]; } exports.calculateVolSpreadBN = calculateVolSpreadBN; function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOracleConfPct, maxSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, baseAssetAmountWithAmm, reservePrice, totalFeeMinusDistributions, netRevenueSinceLastFunding, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, markStd, oracleStd, longIntensity, shortIntensity, volume24H, ammInventorySpreadAdjustment, returnTerms = false) { (0, assert_1.assert)(Number.isInteger(baseSpread)); (0, assert_1.assert)(Number.isInteger(maxSpread)); const spreadTerms = { longVolSpread: 0, shortVolSpread: 0, longSpreadwPS: 0, shortSpreadwPS: 0, maxTargetSpread: 0, inventorySpreadScale: 0, longSpreadwInvScale: 0, shortSpreadwInvScale: 0, effectiveLeverage: 0, effectiveLeverageCapped: 0, longSpreadwEL: 0, shortSpreadwEL: 0, revenueRetreatAmount: 0, halfRevenueRetreatAmount: 0, longSpreadwRevRetreat: 0, shortSpreadwRevRetreat: 0, longSpreadwOffsetShrink: 0, shortSpreadwOffsetShrink: 0, totalSpread: 0, longSpread: 0, shortSpread: 0, }; const [longVolSpread, shortVolSpread] = calculateVolSpreadBN(lastOracleConfPct, reservePrice, markStd, oracleStd, longIntensity, shortIntensity, volume24H); spreadTerms.longVolSpread = longVolSpread.toNumber(); spreadTerms.shortVolSpread = shortVolSpread.toNumber(); let longSpread = Math.max(baseSpread / 2, longVolSpread.toNumber()); let shortSpread = Math.max(baseSpread / 2, shortVolSpread.toNumber()); if (lastOracleReservePriceSpreadPct.gt(numericConstants_1.ZERO)) { shortSpread = Math.max(shortSpread, lastOracleReservePriceSpreadPct.abs().toNumber() + shortVolSpread.toNumber()); } else if (lastOracleReservePriceSpreadPct.lt(numericConstants_1.ZERO)) { longSpread = Math.max(longSpread, lastOracleReservePriceSpreadPct.abs().toNumber() + longVolSpread.toNumber()); } spreadTerms.longSpreadwPS = longSpread; spreadTerms.shortSpreadwPS = shortSpread; const maxSpreadBaseline = Math.min(Math.max(lastOracleReservePriceSpreadPct.abs().toNumber(), lastOracleConfPct.muln(2).toNumber(), anchor_1.BN.max(markStd, oracleStd) .mul(numericConstants_1.PERCENTAGE_PRECISION) .div(reservePrice) .toNumber()), numericConstants_1.BID_ASK_SPREAD_PRECISION.toNumber()); const maxTargetSpread = Math.floor(Math.max(maxSpread, maxSpreadBaseline)); const inventorySpreadScale = calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, baseAssetAmountWithAmm.gt(numericConstants_1.ZERO) ? longSpread : shortSpread, maxTargetSpread); if (baseAssetAmountWithAmm.gt(numericConstants_1.ZERO)) { longSpread *= inventorySpreadScale; } else if (baseAssetAmountWithAmm.lt(numericConstants_1.ZERO)) { shortSpread *= inventorySpreadScale; } spreadTerms.maxTargetSpread = maxTargetSpread; spreadTerms.inventorySpreadScale = inventorySpreadScale; spreadTerms.longSpreadwInvScale = longSpread; spreadTerms.shortSpreadwInvScale = shortSpread; const MAX_SPREAD_SCALE = 10; if (totalFeeMinusDistributions.gt(numericConstants_1.ZERO)) { const effectiveLeverage = calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, baseAssetAmountWithAmm, reservePrice, totalFeeMinusDistributions); spreadTerms.effectiveLeverage = effectiveLeverage; const spreadScale = Math.min(MAX_SPREAD_SCALE, 1 + effectiveLeverage); spreadTerms.effectiveLeverageCapped = spreadScale; if (baseAssetAmountWithAmm.gt(numericConstants_1.ZERO)) { longSpread *= spreadScale; longSpread = Math.floor(longSpread); } else { shortSpread *= spreadScale; shortSpread = Math.floor(shortSpread); } } else { longSpread *= MAX_SPREAD_SCALE; shortSpread *= MAX_SPREAD_SCALE; } spreadTerms.longSpreadwEL = longSpread; spreadTerms.shortSpreadwEL = shortSpread; if (netRevenueSinceLastFunding.lt(numericConstants_1.DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT)) { const maxRetreat = maxTargetSpread / 10; let revenueRetreatAmount = maxRetreat; if (netRevenueSinceLastFunding.gte(numericConstants_1.DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.mul(new anchor_1.BN(1000)))) { revenueRetreatAmount = Math.min(maxRetreat, Math.floor((baseSpread * netRevenueSinceLastFunding.abs().toNumber()) / numericConstants_1.DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.abs().toNumber())); } const halfRevenueRetreatAmount = Math.floor(revenueRetreatAmount / 2); spreadTerms.revenueRetreatAmount = revenueRetreatAmount; spreadTerms.halfRevenueRetreatAmount = halfRevenueRetreatAmount; if (baseAssetAmountWithAmm.gt(numericConstants_1.ZERO)) { longSpread += revenueRetreatAmount; shortSpread += halfRevenueRetreatAmount; } else if (baseAssetAmountWithAmm.lt(numericConstants_1.ZERO)) { longSpread += halfRevenueRetreatAmount; shortSpread += revenueRetreatAmount; } else { longSpread += halfRevenueRetreatAmount; shortSpread += halfRevenueRetreatAmount; } } spreadTerms.longSpreadwRevRetreat = longSpread; spreadTerms.shortSpreadwRevRetreat = shortSpread; if (ammInventorySpreadAdjustment < 0) { const adjustment = Math.abs(ammInventorySpreadAdjustment); const shrunkLong = Math.max(1, longSpread - Math.floor((longSpread * adjustment) / 100)); const shrunkShort = Math.max(1, shortSpread - Math.floor((shortSpread * adjustment) / 100)); longSpread = Math.max(longVolSpread.toNumber(), shrunkLong); shortSpread = Math.max(shortVolSpread.toNumber(), shrunkShort); } else if (ammInventorySpreadAdjustment > 0) { const adjustment = ammInventorySpreadAdjustment; const grownLong = Math.max(1, longSpread + Math.ceil((longSpread * adjustment) / 100)); const grownShort = Math.max(1, shortSpread + Math.ceil((shortSpread * adjustment) / 100)); longSpread = Math.max(longVolSpread.toNumber(), grownLong); shortSpread = Math.max(shortVolSpread.toNumber(), grownShort); } const totalSpread = longSpread + shortSpread; if (totalSpread > maxTargetSpread) { if (longSpread > shortSpread) { longSpread = Math.ceil((longSpread * maxTargetSpread) / totalSpread); shortSpread = Math.floor(maxTargetSpread - longSpread); } else { shortSpread = Math.ceil((shortSpread * maxTargetSpread) / totalSpread); longSpread = Math.floor(maxTargetSpread - shortSpread); } } spreadTerms.totalSpread = totalSpread; spreadTerms.longSpread = longSpread; spreadTerms.shortSpread = shortSpread; if (returnTerms) { return spreadTerms; } return [longSpread, shortSpread]; } exports.calculateSpreadBN = calculateSpreadBN; function calculateSpread(amm, oraclePriceData, now, reservePrice) { if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) { return [amm.baseSpread / 2, amm.baseSpread / 2]; } if (!reservePrice) { reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier); } const targetPrice = (oraclePriceData === null || oraclePriceData === void 0 ? void 0 : oraclePriceData.price) || reservePrice; const targetMarkSpreadPct = reservePrice .sub(targetPrice) .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION) .div(reservePrice); now = now || new anchor_1.BN(new Date().getTime() / 1000); //todo const liveOracleStd = (0, oracles_1.calculateLiveOracleStd)(amm, oraclePriceData, now); const confIntervalPct = (0, oracles_1.getNewOracleConfPct)(amm, oraclePriceData, reservePrice, now); const spreads = calculateSpreadBN(amm.baseSpread, targetMarkSpreadPct, confIntervalPct, amm.maxSpread, amm.quoteAssetReserve, amm.terminalQuoteAssetReserve, amm.pegMultiplier, amm.baseAssetAmountWithAmm, reservePrice, amm.totalFeeMinusDistributions, amm.netRevenueSinceLastFunding, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve, amm.markStd, liveOracleStd, amm.longIntensityVolume, amm.shortIntensityVolume, amm.volume24H, amm.ammInventorySpreadAdjustment); let longSpread = spreads[0]; let shortSpread = spreads[1]; if (amm.ammSpreadAdjustment > 0) { longSpread = Math.max(longSpread + (longSpread * amm.ammSpreadAdjustment) / 100, 1); shortSpread = Math.max(shortSpread + (shortSpread * amm.ammSpreadAdjustment) / 100, 1); } else if (amm.ammSpreadAdjustment < 0) { longSpread = Math.max(longSpread - (longSpread * -amm.ammSpreadAdjustment) / 100, 1); shortSpread = Math.max(shortSpread - (shortSpread * -amm.ammSpreadAdjustment) / 100, 1); } return [longSpread, shortSpread]; } exports.calculateSpread = calculateSpread; function getQuoteAssetReservePredictionMarketBounds(amm, direction) { let quoteAssetReserveLowerBound = numericConstants_1.ZERO; const pegSqrt = (0, utils_1.squareRootBN)(amm.pegMultiplier.mul(numericConstants_1.PEG_PRECISION).addn(1)).addn(1); let quoteAssetReserveUpperBound = amm.sqrtK .mul(pegSqrt) .div(amm.pegMultiplier); if (direction === types_1.PositionDirection.LONG) { quoteAssetReserveLowerBound = amm.sqrtK .muln(22361) .mul(pegSqrt) .divn(100000) .div(amm.pegMultiplier); } else { quoteAssetReserveUpperBound = amm.sqrtK .muln(97467) .mul(pegSqrt) .divn(100000) .div(amm.pegMultiplier); } return [quoteAssetReserveLowerBound, quoteAssetReserveUpperBound]; } exports.getQuoteAssetReservePredictionMarketBounds = getQuoteAssetReservePredictionMarketBounds; function calculateSpreadReserves(amm, mmOraclePriceData, now, isPrediction = false, latestSlot) { function calculateSpreadReserve(spread, direction, amm) { if (spread === 0) { return { baseAssetReserve: amm.baseAssetReserve, quoteAssetReserve: amm.quoteAssetReserve, }; } let spreadFraction = new anchor_1.BN(spread).div(new anchor_1.BN(2)); // make non-zero if (spreadFraction.eq(numericConstants_1.ZERO)) { spreadFraction = spread >= 0 ? new anchor_1.BN(1) : new anchor_1.BN(-1); } const quoteAssetReserveDelta = amm.quoteAssetReserve.div(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(spreadFraction)); let quoteAssetReserve; if (quoteAssetReserveDelta.gte(numericConstants_1.ZERO)) { quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta.abs()); } else { quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta.abs()); } if (isPrediction) { const [qarLower, qarUpper] = getQuoteAssetReservePredictionMarketBounds(amm, direction); quoteAssetReserve = (0, utils_1.clampBN)(quoteAssetReserve, qarLower, qarUpper); } const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve); return { baseAssetReserve, quoteAssetReserve, }; } const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier); // always allow 10 bps of price offset, up to a half of the market's max_spread let maxOffset = 0; let referencePriceOffset = 0; if (amm.curveUpdateIntensity > 100) { maxOffset = Math.max(amm.maxSpread / 2, (numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 10000) * (amm.curveUpdateIntensity - 100)); const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve); const liquidityFractionSigned = liquidityFraction.mul((0, utils_1.sigNum)(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp))); referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFractionSigned, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset).toNumber(); } let [longSpread, shortSpread] = calculateSpread(amm, mmOraclePriceData, now, reservePrice); const doReferencePricOffsetSmooth = Math.sign(referencePriceOffset) !== Math.sign(amm.referencePriceOffset) && amm.curveUpdateIntensity > 100; if (doReferencePricOffsetSmooth) { const slotsPassed = latestSlot != null ? anchor_1.BN.max(latestSlot.sub(amm.lastUpdateSlot), numericConstants_1.ZERO).toNumber() : 0; const fullOffsetDelta = referencePriceOffset - amm.referencePriceOffset; const raw = Math.trunc(Math.min(Math.abs(fullOffsetDelta), slotsPassed * 1000) / 10); const maxAllowed = Math.abs(amm.referencePriceOffset) || Math.abs(referencePriceOffset); const magnitude = Math.min(Math.max(raw, 10), maxAllowed); const referencePriceDelta = Math.sign(fullOffsetDelta) * magnitude; referencePriceOffset = amm.referencePriceOffset + referencePriceDelta; if (referencePriceDelta < 0) { longSpread += Math.abs(referencePriceDelta); shortSpread += Math.abs(referencePriceOffset); } else { shortSpread += Math.abs(referencePriceDelta); longSpread += Math.abs(referencePriceOffset); } } const askReserves = calculateSpreadReserve(longSpread + referencePriceOffset, types_1.PositionDirection.LONG, amm); const bidReserves = calculateSpreadReserve(-shortSpread + referencePriceOffset, types_1.PositionDirection.SHORT, amm); return [bidReserves, askReserves]; } exports.calculateSpreadReserves = calculateSpreadReserves; /** * Helper function calculating constant product curve output. Agnostic to whether input asset is quote or base * * @param inputAssetReserve * @param swapAmount * @param swapDirection * @param invariant * @returns newInputAssetReserve and newOutputAssetReserve after swap. : Precision AMM_RESERVE_PRECISION */ function calculateSwapOutput(inputAssetReserve, swapAmount, swapDirection, invariant) { let newInputAssetReserve; if (swapDirection === types_1.SwapDirection.ADD) { newInputAssetReserve = inputAssetReserve.add(swapAmount); } else { newInputAssetReserve = inputAssetReserve.sub(swapAmount); } const newOutputAssetReserve = invariant.div(newInputAssetReserve); return [newInputAssetReserve, newOutputAssetReserve]; } exports.calculateSwapOutput = calculateSwapOutput; /** * Translate long/shorting quote/base asset into amm operation * * @param inputAssetType * @param positionDirection */ function getSwapDirection(inputAssetType, positionDirection) { if ((0, types_1.isVariant)(positionDirection, 'long') && inputAssetType === 'base') { return types_1.SwapDirection.REMOVE; } if ((0, types_1.isVariant)(positionDirection, 'short') && inputAssetType === 'quote') { return types_1.SwapDirection.REMOVE; } return types_1.SwapDirection.ADD; } exports.getSwapDirection = getSwapDirection; /** * Helper function calculating terminal price of amm * * @param market * @returns cost : Precision PRICE_PRECISION */ function calculateTerminalPrice(market) { const directionToClose = market.amm.baseAssetAmountWithAmm.gt(numericConstants_1.ZERO) ? types_1.PositionDirection.SHORT : types_1.PositionDirection.LONG; const [newQuoteAssetReserve, newBaseAssetReserve] = calculateAmmReservesAfterSwap(market.amm, 'base', market.amm.baseAssetAmountWithAmm.abs(), getSwapDirection('base', directionToClose)); const terminalPrice = newQuoteAssetReserve .mul(numericConstants_1.PRICE_PRECISION) .mul(market.amm.pegMultiplier) .div(numericConstants_1.PEG_PRECISION) .div(newBaseAssetReserve); return terminalPrice; } exports.calculateTerminalPrice = calculateTerminalPrice; function calculateMaxBaseAssetAmountToTrade(amm, limit_price, direction, mmOraclePriceData, now, isPrediction = false) { const invariant = amm.sqrtK.mul(amm.sqrtK); const newBaseAssetReserveSquared = invariant .mul(numericConstants_1.PRICE_PRECISION) .mul(amm.pegMultiplier) .div(limit_price) .div(numericConstants_1.PEG_PRECISION); const newBaseAssetReserve = (0, utils_1.squareRootBN)(newBaseAssetReserveSquared); const [shortSpreadReserves, longSpreadReserves] = calculateSpreadReserves(amm, mmOraclePriceData, now, isPrediction); const baseAssetReserveBefore = (0, types_1.isVariant)(direction, 'long') ? longSpreadReserves.baseAssetReserve : shortSpreadReserves.baseAssetReserve; if (newBaseAssetReserve.gt(baseAssetReserveBefore)) { return [ newBaseAssetReserve.sub(baseAssetReserveBefore), types_1.PositionDirection.SHORT, ]; } else if (newBaseAssetReserve.lt(baseAssetReserveBefore)) { return [ baseAssetReserveBefore.sub(newBaseAssetReserve), types_1.PositionDirection.LONG, ]; } else { console.log('tradeSize Too Small'); return [new anchor_1.BN(0), types_1.PositionDirection.LONG]; } } exports.calculateMaxBaseAssetAmountToTrade = calculateMaxBaseAssetAmountToTrade; function calculateQuoteAssetAmountSwapped(quoteAssetReserves, pegMultiplier, swapDirection) { if ((0, types_1.isVariant)(swapDirection, 'remove')) { quoteAssetReserves = quoteAssetReserves.add(numericConstants_1.ONE); } let quoteAssetAmount = quoteAssetReserves .mul(pegMultiplier) .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO); if ((0, types_1.isVariant)(swapDirection, 'remove')) { quoteAssetAmount = quoteAssetAmount.add(numericConstants_1.ONE); } return quoteAssetAmount; } exports.calculateQuoteAssetAmountSwapped = calculateQuoteAssetAmountSwapped; function calculateMaxBaseAssetAmountFillable(amm, orderDirection) { const maxFillSize = amm.baseAssetReserve.div(new anchor_1.BN(amm.maxFillReserveFraction)); let maxBaseAssetAmountOnSide; if ((0, types_1.isVariant)(orderDirection, 'long')) { maxBaseAssetAmountOnSide = anchor_1.BN.max(numericConstants_1.ZERO, amm.baseAssetReserve.sub(amm.minBaseAssetReserve)); } else { maxBaseAssetAmountOnSide = anchor_1.BN.max(numericConstants_1.ZERO, amm.maxBaseAssetReserve.sub(amm.baseAssetReserve)); } return (0, orders_1.standardizeBaseAssetAmount)(anchor_1.BN.min(maxFillSize, maxBaseAssetAmountOnSide), amm.orderStepSize); } exports.calculateMaxBaseAssetAmountFillable = calculateMaxBaseAssetAmountFillable;