@drift-labs/sdk
Version:
SDK for Drift Protocol
202 lines (201 loc) • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateUserMaxPerpOrderSize = exports.calculateLiquidationPrice = exports.calculateCollateralValueOfDeposit = exports.calculateCollateralDepositRequiredForTrade = exports.calculateMarginUSDCRequiredForTrade = exports.calculatePerpLiabilityValue = exports.calculateWorstCasePerpLiabilityValue = exports.calculateWorstCaseBaseAssetAmount = exports.calculateBaseAssetValueWithOracle = exports.calculateOraclePriceForPerpMargin = exports.calculateSizeDiscountAssetWeight = exports.calculateSizePremiumLiabilityWeight = void 0;
const utils_1 = require("./utils");
const numericConstants_1 = require("../constants/numericConstants");
const anchor_1 = require("@coral-xyz/anchor");
const market_1 = require("./market");
const spotBalance_1 = require("./spotBalance");
const oneShotUserAccountSubscriber_1 = require("../accounts/oneShotUserAccountSubscriber");
const user_1 = require("../user");
const types_1 = require("../types");
const assert_1 = require("../assert/assert");
function calculateSizePremiumLiabilityWeight(size, // AMM_RESERVE_PRECISION
imfFactor, liabilityWeight, precision) {
if (imfFactor.eq(numericConstants_1.ZERO)) {
return liabilityWeight;
}
const sizeSqrt = (0, utils_1.squareRootBN)(size.abs().mul(new anchor_1.BN(10)).add(new anchor_1.BN(1))); //1e9 -> 1e10 -> 1e5
const liabilityWeightNumerator = liabilityWeight.sub(liabilityWeight.div(new anchor_1.BN(5)));
const denom = new anchor_1.BN(100000).mul(numericConstants_1.SPOT_MARKET_IMF_PRECISION).div(precision);
(0, assert_1.assert)(denom.gt(numericConstants_1.ZERO));
const sizePremiumLiabilityWeight = liabilityWeightNumerator.add(sizeSqrt // 1e5
.mul(imfFactor)
.div(denom) // 1e5
);
const maxLiabilityWeight = anchor_1.BN.max(liabilityWeight, sizePremiumLiabilityWeight);
return maxLiabilityWeight;
}
exports.calculateSizePremiumLiabilityWeight = calculateSizePremiumLiabilityWeight;
function calculateSizeDiscountAssetWeight(size, // AMM_RESERVE_PRECISION
imfFactor, assetWeight) {
if (imfFactor.eq(numericConstants_1.ZERO)) {
return assetWeight;
}
const sizeSqrt = (0, utils_1.squareRootBN)(size.abs().mul(new anchor_1.BN(10)).add(new anchor_1.BN(1))); //1e9 -> 1e10 -> 1e5
const imfNumerator = numericConstants_1.SPOT_MARKET_IMF_PRECISION.add(numericConstants_1.SPOT_MARKET_IMF_PRECISION.div(new anchor_1.BN(10)));
const sizeDiscountAssetWeight = imfNumerator
.mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION)
.div(numericConstants_1.SPOT_MARKET_IMF_PRECISION.add(sizeSqrt // 1e5
.mul(imfFactor)
.div(new anchor_1.BN(100000)) // 1e5
));
const minAssetWeight = anchor_1.BN.min(assetWeight, sizeDiscountAssetWeight);
return minAssetWeight;
}
exports.calculateSizeDiscountAssetWeight = calculateSizeDiscountAssetWeight;
function calculateOraclePriceForPerpMargin(perpPosition, market, oraclePriceData) {
const oraclePriceOffset = anchor_1.BN.min(new anchor_1.BN(market.amm.maxSpread)
.mul(oraclePriceData.price)
.div(numericConstants_1.BID_ASK_SPREAD_PRECISION), oraclePriceData.confidence.add(new anchor_1.BN(market.amm.baseSpread)
.mul(oraclePriceData.price)
.div(numericConstants_1.BID_ASK_SPREAD_PRECISION)));
let marginPrice;
if (perpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
marginPrice = oraclePriceData.price.sub(oraclePriceOffset);
}
else {
marginPrice = oraclePriceData.price.add(oraclePriceOffset);
}
return marginPrice;
}
exports.calculateOraclePriceForPerpMargin = calculateOraclePriceForPerpMargin;
/**
* This is _not_ the same as liability value as for prediction markets, the liability for the short in prediction market is (1 - oracle price) * base
* See {@link calculatePerpLiabilityValue} to get the liabiltiy value
* @param market
* @param perpPosition
* @param oraclePriceData
* @param includeOpenOrders
*/
function calculateBaseAssetValueWithOracle(market, perpPosition, oraclePriceData, includeOpenOrders = false) {
let price = oraclePriceData.price;
if ((0, types_1.isVariant)(market.status, 'settlement')) {
price = market.expiryPrice;
}
const baseAssetAmount = includeOpenOrders
? calculateWorstCaseBaseAssetAmount(perpPosition, market, oraclePriceData.price)
: perpPosition.baseAssetAmount;
return baseAssetAmount.abs().mul(price).div(numericConstants_1.AMM_RESERVE_PRECISION);
}
exports.calculateBaseAssetValueWithOracle = calculateBaseAssetValueWithOracle;
function calculateWorstCaseBaseAssetAmount(perpPosition, perpMarket, oraclePrice) {
return calculateWorstCasePerpLiabilityValue(perpPosition, perpMarket, oraclePrice).worstCaseBaseAssetAmount;
}
exports.calculateWorstCaseBaseAssetAmount = calculateWorstCaseBaseAssetAmount;
function calculateWorstCasePerpLiabilityValue(perpPosition, perpMarket, oraclePrice) {
const allBids = perpPosition.baseAssetAmount.add(perpPosition.openBids);
const allAsks = perpPosition.baseAssetAmount.add(perpPosition.openAsks);
const isPredictionMarket = (0, types_1.isVariant)(perpMarket.contractType, 'prediction');
const allBidsLiabilityValue = calculatePerpLiabilityValue(allBids, oraclePrice, isPredictionMarket);
const allAsksLiabilityValue = calculatePerpLiabilityValue(allAsks, oraclePrice, isPredictionMarket);
if (allAsksLiabilityValue.gte(allBidsLiabilityValue)) {
return {
worstCaseBaseAssetAmount: allAsks,
worstCaseLiabilityValue: allAsksLiabilityValue,
};
}
else {
return {
worstCaseBaseAssetAmount: allBids,
worstCaseLiabilityValue: allBidsLiabilityValue,
};
}
}
exports.calculateWorstCasePerpLiabilityValue = calculateWorstCasePerpLiabilityValue;
function calculatePerpLiabilityValue(baseAssetAmount, price, isPredictionMarket) {
if (isPredictionMarket) {
if (baseAssetAmount.gt(numericConstants_1.ZERO)) {
return baseAssetAmount.mul(price).div(numericConstants_1.BASE_PRECISION);
}
else {
return baseAssetAmount
.abs()
.mul(numericConstants_1.MAX_PREDICTION_PRICE.sub(price))
.div(numericConstants_1.BASE_PRECISION);
}
}
else {
return baseAssetAmount.abs().mul(price).div(numericConstants_1.BASE_PRECISION);
}
}
exports.calculatePerpLiabilityValue = calculatePerpLiabilityValue;
/**
* Calculates the margin required to open a trade, in quote amount. Only accounts for the trade size as a scalar value, does not account for the trade direction or current open positions and whether the trade would _actually_ be risk-increasing and use any extra collateral.
* @param targetMarketIndex
* @param baseSize
* @returns
*/
function calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio, userHighLeverageMode, entryPrice) {
const targetMarket = driftClient.getPerpMarketAccount(targetMarketIndex);
const price = entryPrice !== null && entryPrice !== void 0 ? entryPrice : driftClient.getOracleDataForPerpMarket(targetMarket.marketIndex).price;
const perpLiabilityValue = calculatePerpLiabilityValue(baseSize, price, (0, types_1.isVariant)(targetMarket.contractType, 'prediction'));
const marginRequired = new anchor_1.BN((0, market_1.calculateMarketMarginRatio)(targetMarket, baseSize.abs(), 'Initial', userMaxMarginRatio, userHighLeverageMode))
.mul(perpLiabilityValue)
.div(numericConstants_1.MARGIN_PRECISION);
return marginRequired;
}
exports.calculateMarginUSDCRequiredForTrade = calculateMarginUSDCRequiredForTrade;
/**
* Similar to calculatetMarginUSDCRequiredForTrade, but calculates how much of a given collateral is required to cover the margin requirements for a given trade. Basically does the same thing as getMarginUSDCRequiredForTrade but also accounts for asset weight of the selected collateral.
*
* Returns collateral required in the precision of the target collateral market.
*/
function calculateCollateralDepositRequiredForTrade(driftClient, targetMarketIndex, baseSize, collateralIndex, userMaxMarginRatio, userHighLeverageMode, estEntryPrice) {
const marginRequiredUsdc = calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio, userHighLeverageMode, estEntryPrice);
const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex);
const scaledAssetWeight = (0, spotBalance_1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price);
// Base amount required to deposit = (marginRequiredUsdc / priceOfAsset) / assetWeight .. (E.g. $100 required / $10000 price / 0.5 weight)
const baseAmountRequired = driftClient
.convertToSpotPrecision(collateralIndex, marginRequiredUsdc)
.mul(numericConstants_1.PRICE_PRECISION) // adjust for division by oracle price
.mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION) // adjust for division by scaled asset weight
.div(collateralOracleData.price)
.div(scaledAssetWeight)
.div(numericConstants_1.QUOTE_PRECISION); // adjust for marginRequiredUsdc value's QUOTE_PRECISION
// TODO : Round by step size?
return baseAmountRequired;
}
exports.calculateCollateralDepositRequiredForTrade = calculateCollateralDepositRequiredForTrade;
function calculateCollateralValueOfDeposit(driftClient, collateralIndex, baseSize) {
const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex);
const scaledAssetWeight = (0, spotBalance_1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price);
// CollateralBaseValue = oracle price * collateral base amount (and shift to QUOTE_PRECISION)
const collateralBaseValue = collateralOracleData.price
.mul(baseSize)
.mul(numericConstants_1.QUOTE_PRECISION)
.div(numericConstants_1.PRICE_PRECISION)
.div(new anchor_1.BN(10).pow(new anchor_1.BN(collateralMarket.decimals)));
const depositCollateralValue = collateralBaseValue
.mul(scaledAssetWeight)
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
return depositCollateralValue;
}
exports.calculateCollateralValueOfDeposit = calculateCollateralValueOfDeposit;
function calculateLiquidationPrice(freeCollateral, freeCollateralDelta, oraclePrice) {
const liqPriceDelta = freeCollateral
.mul(numericConstants_1.QUOTE_PRECISION)
.div(freeCollateralDelta);
const liqPrice = oraclePrice.sub(liqPriceDelta);
if (liqPrice.lt(numericConstants_1.ZERO)) {
return new anchor_1.BN(-1);
}
return liqPrice;
}
exports.calculateLiquidationPrice = calculateLiquidationPrice;
function calculateUserMaxPerpOrderSize(driftClient, userAccountKey, userAccount, targetMarketIndex, tradeSide) {
const userAccountSubscriber = new oneShotUserAccountSubscriber_1.OneShotUserAccountSubscriber(driftClient.program, userAccountKey, userAccount);
const user = new user_1.User({
driftClient,
userAccountPublicKey: userAccountKey,
accountSubscription: {
type: 'custom',
userAccountSubscriber: userAccountSubscriber,
},
});
user.isSubscribed = true;
return user.getMaxTradeSizeUSDCForPerp(targetMarketIndex, tradeSide);
}
exports.calculateUserMaxPerpOrderSize = calculateUserMaxPerpOrderSize;