@drift-labs/sdk
Version:
SDK for Drift Protocol
671 lines (670 loc) • 36.9 kB
JavaScript
"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;