reduce allocations in risk code
This commit is contained in:
parent
a552a20559
commit
4a5bfd9fca
|
@ -44,19 +44,23 @@ import { MarketIndex, Serum3Market, Serum3Side } from './serum3';
|
|||
// ██████████████████████████████████████████
|
||||
// warning: this code is copy pasta from rust, keep in sync with health.rs
|
||||
|
||||
|
||||
/**
|
||||
* WARNING this potentially modifies health & starting spot inplace
|
||||
*/
|
||||
function spotAmountTakenForHealthZero(
|
||||
health: I80F48,
|
||||
startingSpot: I80F48,
|
||||
assetWeightedPrice: I80F48,
|
||||
liabWeightedPrice: I80F48,
|
||||
): I80F48 {
|
||||
if (health.lte(ZERO_I80F48())) {
|
||||
if (!health.isPos()) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
||||
let takenSpot = ZERO_I80F48();
|
||||
if (startingSpot.gt(ZERO_I80F48())) {
|
||||
if (assetWeightedPrice.gt(ZERO_I80F48())) {
|
||||
if (startingSpot.isPos()) {
|
||||
if (assetWeightedPrice.isPos()) {
|
||||
const assetMax = health.div(assetWeightedPrice);
|
||||
if (assetMax.lte(startingSpot)) {
|
||||
return assetMax;
|
||||
|
@ -65,8 +69,8 @@ function spotAmountTakenForHealthZero(
|
|||
takenSpot = startingSpot;
|
||||
health.isub(startingSpot.mul(assetWeightedPrice));
|
||||
}
|
||||
if (health.gt(ZERO_I80F48())) {
|
||||
if (liabWeightedPrice.lte(ZERO_I80F48())) {
|
||||
if (health.isPos()) {
|
||||
if (!liabWeightedPrice.isPos()) {
|
||||
throw new Error('LiabWeightedPrice must be greater than 0!');
|
||||
}
|
||||
takenSpot.iadd(health.div(liabWeightedPrice));
|
||||
|
@ -74,20 +78,6 @@ function spotAmountTakenForHealthZero(
|
|||
return takenSpot;
|
||||
}
|
||||
|
||||
function spotAmountGivenForHealthZero(
|
||||
health: I80F48,
|
||||
startingSpot: I80F48,
|
||||
assetWeightedPrice: I80F48,
|
||||
liabWeightedPrice: I80F48,
|
||||
): I80F48 {
|
||||
return spotAmountTakenForHealthZero(
|
||||
health.neg(),
|
||||
startingSpot.neg(),
|
||||
liabWeightedPrice,
|
||||
assetWeightedPrice,
|
||||
);
|
||||
}
|
||||
|
||||
export class HealthCache {
|
||||
constructor(
|
||||
public tokenInfos: TokenInfo[],
|
||||
|
@ -193,7 +183,7 @@ export class HealthCache {
|
|||
quoteAsset.div(baseLiab),
|
||||
);
|
||||
let allReservedAsBase;
|
||||
if (!info.reservedQuoteAsBaseHighestBid.eq(ZERO_I80F48())) {
|
||||
if (!info.reservedQuoteAsBaseHighestBid.isZero()) {
|
||||
allReservedAsBase = reservedBase.add(
|
||||
reservedQuoteAsBaseOracle.min(info.reservedQuoteAsBaseHighestBid),
|
||||
);
|
||||
|
@ -207,7 +197,7 @@ export class HealthCache {
|
|||
baseAsset.div(quoteLiab),
|
||||
);
|
||||
let allReservedAsQuote;
|
||||
if (!info.reservedBaseAsQuoteLowestAsk.eq(ZERO_I80F48())) {
|
||||
if (!info.reservedBaseAsQuoteLowestAsk.isZero()) {
|
||||
allReservedAsQuote = reservedQuote.add(
|
||||
reservedBaseAsQuoteOracle.min(info.reservedBaseAsQuoteLowestAsk),
|
||||
);
|
||||
|
@ -249,7 +239,7 @@ export class HealthCache {
|
|||
);
|
||||
const perpSettleToken = tokenBalances[settleTokenIndex];
|
||||
const healthUnsettled = perpInfo.healthUnsettledPnl(healthType);
|
||||
if (!ignoreNegativePerp || healthUnsettled.gt(ZERO_I80F48())) {
|
||||
if (!ignoreNegativePerp || healthUnsettled.isPos()) {
|
||||
perpSettleToken.spotAndPerp.iadd(healthUnsettled);
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +277,7 @@ export class HealthCache {
|
|||
group.getMintDecimalsByTokenIndex(perpInfo.settleTokenIndex),
|
||||
),
|
||||
});
|
||||
if (!ignoreNegativePerp || healthUnsettled.gt(ZERO_I80F48())) {
|
||||
if (!ignoreNegativePerp || healthUnsettled.isPos()) {
|
||||
perpSettleToken.spotAndPerp.iadd(healthUnsettled);
|
||||
}
|
||||
}
|
||||
|
@ -509,11 +499,11 @@ export class HealthCache {
|
|||
|
||||
public healthRatio(healthType: HealthType): I80F48 {
|
||||
const res = this.healthAssetsAndLiabsStableLiabs(healthType);
|
||||
const hundred = I80F48.fromNumber(100);
|
||||
// console.log(`assets ${res.assets}`);
|
||||
// console.log(`liabs ${res.liabs}`);
|
||||
if (res.liabs.gt(I80F48.fromNumber(0.001))) {
|
||||
return hundred.mul(res.assets.sub(res.liabs)).div(res.liabs);
|
||||
const hundred = I80F48.fromNumber(100);
|
||||
return hundred.imul(res.assets.sub(res.liabs)).idiv(res.liabs);
|
||||
}
|
||||
return MAX_I80F48();
|
||||
}
|
||||
|
@ -931,10 +921,10 @@ export class HealthCache {
|
|||
targetFn: (cache) => I80F48,
|
||||
): I80F48 {
|
||||
if (
|
||||
sourceBank.initLiabWeight
|
||||
!sourceBank.initLiabWeight
|
||||
.sub(targetBank.initAssetWeight)
|
||||
.abs()
|
||||
.lte(ZERO_I80F48())
|
||||
.isPos()
|
||||
) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
@ -979,7 +969,7 @@ export class HealthCache {
|
|||
.mul(price),
|
||||
);
|
||||
|
||||
if (finalHealthSlope.gte(ZERO_I80F48())) {
|
||||
if (!finalHealthSlope.isNeg()) {
|
||||
return MAX_I80F48();
|
||||
}
|
||||
|
||||
|
@ -1044,9 +1034,9 @@ export class HealthCache {
|
|||
const healthAtMaxValue = cacheAfterSwap(amountForMaxValue).health(
|
||||
HealthType.init,
|
||||
);
|
||||
if (healthAtMaxValue.eq(ZERO_I80F48())) {
|
||||
if (healthAtMaxValue.isZero()) {
|
||||
return amountForMaxValue;
|
||||
} else if (healthAtMaxValue.lt(ZERO_I80F48())) {
|
||||
} else if (healthAtMaxValue.isNeg()) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
const zeroHealthEstimate = amountForMaxValue.sub(
|
||||
|
@ -1106,7 +1096,7 @@ export class HealthCache {
|
|||
const initialAmount = ZERO_I80F48();
|
||||
const initialHealth = this.health(HealthType.init);
|
||||
const initialRatio = this.healthRatio(HealthType.init);
|
||||
if (initialRatio.lte(ZERO_I80F48())) {
|
||||
if (!initialRatio.isPos()) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
||||
|
@ -1121,7 +1111,7 @@ export class HealthCache {
|
|||
// and when its a bid, then quote->bid
|
||||
let zeroAmount;
|
||||
if (side == Serum3Side.ask) {
|
||||
const quoteBorrows = quote.balanceSpot.lt(ZERO_I80F48())
|
||||
const quoteBorrows = quote.balanceSpot.isNeg()
|
||||
? quote.balanceSpot.abs().mul(quote.prices.liab(HealthType.init))
|
||||
: ZERO_I80F48();
|
||||
const max = base.balanceSpot.mul(base.prices.oracle).max(quoteBorrows);
|
||||
|
@ -1138,7 +1128,7 @@ export class HealthCache {
|
|||
// console.log(` - quoteBorrows ${quoteBorrows.toLocaleString()}`);
|
||||
// console.log(` - max ${max.toLocaleString()}`);
|
||||
} else {
|
||||
const baseBorrows = base.balanceSpot.lt(ZERO_I80F48())
|
||||
const baseBorrows = base.balanceSpot.isNeg()
|
||||
? base.balanceSpot.abs().mul(base.prices.liab(HealthType.init))
|
||||
: ZERO_I80F48();
|
||||
const max = quote.balanceSpot.mul(quote.prices.oracle).max(baseBorrows);
|
||||
|
@ -1221,7 +1211,7 @@ export class HealthCache {
|
|||
const healthCacheClone: HealthCache = deepClone<HealthCache>(this);
|
||||
|
||||
const initialRatio = this.healthRatio(HealthType.init);
|
||||
if (initialRatio.lt(ZERO_I80F48())) {
|
||||
if (initialRatio.isNeg()) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
||||
|
@ -1244,7 +1234,7 @@ export class HealthCache {
|
|||
.neg()
|
||||
.mul(prices.liab(HealthType.init))
|
||||
.add(price);
|
||||
if (finalHealthSlope.gte(ZERO_I80F48())) {
|
||||
if (!finalHealthSlope.isNeg()) {
|
||||
return MAX_I80F48();
|
||||
}
|
||||
finalHealthSlope.imul(settleInfo.liabWeightedPrice(HealthType.init));
|
||||
|
@ -1278,8 +1268,8 @@ export class HealthCache {
|
|||
// 1. We are increasing abs(baseLots)
|
||||
// 2. We are bringing the base position to 0, and then going to case 1.
|
||||
const hasCase2 =
|
||||
(initialBaseLots.gt(ZERO_I80F48()) && direction == -1) ||
|
||||
(initialBaseLots.lt(ZERO_I80F48()) && direction == 1);
|
||||
(initialBaseLots.isPos() && direction == -1) ||
|
||||
(initialBaseLots.isNeg() && direction == 1);
|
||||
|
||||
let case1Start: I80F48, case1StartRatio: I80F48;
|
||||
if (hasCase2) {
|
||||
|
@ -1310,7 +1300,7 @@ export class HealthCache {
|
|||
settleInfo.initAssetWeight = settleInfo.initLiabWeight;
|
||||
settleInfo.initScaledAssetWeight = settleInfo.initScaledLiabWeight;
|
||||
const startHealth = startCache.health(HealthType.init);
|
||||
if (startHealth.lte(ZERO_I80F48())) {
|
||||
if (!startHealth.isPos()) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
||||
|
@ -1373,7 +1363,7 @@ export class HealthCache {
|
|||
if (perpPosition.getBasePosition(perpMarket).isPos()) {
|
||||
const zero = ZERO_I80F48();
|
||||
const healthAtPriceZero = healthAfterPriceChange(zero);
|
||||
if (healthAtPriceZero.gt(ZERO_I80F48())) {
|
||||
if (healthAtPriceZero.isPos()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1794,7 +1784,7 @@ export class PerpInfo {
|
|||
if (this.settleTokenIndex !== settleToken.tokenIndex) {
|
||||
throw new Error('Settle token index should match!');
|
||||
}
|
||||
if (unweighted.gt(ZERO_I80F48())) {
|
||||
if (unweighted.isPos()) {
|
||||
return (
|
||||
healthType == HealthType.init
|
||||
? settleToken.initScaledAssetWeight
|
||||
|
@ -1820,7 +1810,7 @@ export class PerpInfo {
|
|||
unweighted: I80F48,
|
||||
healthType: HealthType | undefined,
|
||||
): I80F48 {
|
||||
if (unweighted.gt(ZERO_I80F48())) {
|
||||
if (unweighted.isPos()) {
|
||||
return (
|
||||
healthType == HealthType.init || healthType == HealthType.liquidationEnd
|
||||
? this.initOverallAssetWeight
|
||||
|
|
|
@ -311,10 +311,10 @@ export class MangoAccount {
|
|||
)) {
|
||||
const oo = this.serum3OosMapByMarketIndex.get(serum3Market.marketIndex);
|
||||
if (serum3Market.baseTokenIndex == bank.tokenIndex && oo) {
|
||||
bal.add(I80F48.fromI64(oo.baseTokenFree));
|
||||
bal.iadd(I80F48.fromI64(oo.baseTokenFree));
|
||||
}
|
||||
if (serum3Market.quoteTokenIndex == bank.tokenIndex && oo) {
|
||||
bal.add(I80F48.fromI64(oo.quoteTokenFree));
|
||||
bal.iadd(I80F48.fromI64(oo.quoteTokenFree));
|
||||
}
|
||||
}
|
||||
return bal;
|
||||
|
|
|
@ -7,6 +7,8 @@ import { HealthType, MangoAccount } from './accounts/mangoAccount';
|
|||
import { MangoClient } from './client';
|
||||
import { I80F48, ONE_I80F48, ZERO_I80F48 } from './numbers/I80F48';
|
||||
import { buildFetch, toUiDecimals, toUiDecimalsForQuote } from './utils';
|
||||
import { HealthCache } from './accounts/healthCache';
|
||||
import { ZERO } from '@raydium-io/raydium-sdk';
|
||||
|
||||
export interface LiqorPriceImpact {
|
||||
Coin: { val: string; highlight: boolean };
|
||||
|
@ -120,22 +122,24 @@ export async function getOnChainPriceForMints(
|
|||
);
|
||||
}
|
||||
|
||||
const maxLiabsValue = I80F48.fromNumber(99_999_999_999);
|
||||
|
||||
export function getPriceImpactForLiqor(
|
||||
group: Group,
|
||||
pis: PriceImpact[],
|
||||
mangoAccounts: MangoAccount[],
|
||||
): LiqorPriceImpact[] {
|
||||
const mangoAccounts_ = mangoAccounts.filter((a) =>
|
||||
a.getHealth(group, HealthType.maint).lt(ZERO_I80F48()),
|
||||
);
|
||||
|
||||
const mangoAccountsWithHealth = mangoAccounts_.map((a: MangoAccount) => {
|
||||
return {
|
||||
account: a,
|
||||
health: a.getHealth(group, HealthType.liquidationEnd),
|
||||
healthRatio: a.getHealthRatioUi(group, HealthType.liquidationEnd),
|
||||
};
|
||||
});
|
||||
|
||||
const mangoAccountsWithHealth = mangoAccounts.map((a: MangoAccount) => {
|
||||
const hc = HealthCache.fromMangoAccount(group, a);
|
||||
if (hc.health(HealthType.maint).isPos()) {
|
||||
return {
|
||||
account: a,
|
||||
health: hc.health(HealthType.liquidationEnd),
|
||||
};
|
||||
}
|
||||
}).filter(a => !!a);
|
||||
|
||||
const usdcBank = group.getFirstBankByTokenIndex(0 as TokenIndex);
|
||||
const usdcMint = usdcBank.mint;
|
||||
|
@ -196,7 +200,7 @@ export function getPriceImpactForLiqor(
|
|||
const maxLiab = a.health
|
||||
.min(ZERO_I80F48())
|
||||
.abs()
|
||||
.idiv(tokenLiabHealthContrib)
|
||||
.div(tokenLiabHealthContrib)
|
||||
.min(maxTokenLiab);
|
||||
|
||||
return sum.iadd(maxLiab);
|
||||
|
@ -207,7 +211,7 @@ export function getPriceImpactForLiqor(
|
|||
.mul(bank.price)
|
||||
.floor()
|
||||
// jup oddity
|
||||
.min(I80F48.fromNumber(99999999999));
|
||||
.min(maxLiabsValue);
|
||||
|
||||
// Sum of all assets which would be acquired in exchange for also acquiring
|
||||
// liabs by the liqor, who would immediately want to reduce to 0
|
||||
|
@ -256,8 +260,8 @@ export function getPriceImpactForLiqor(
|
|||
}, ZERO_I80F48());
|
||||
|
||||
const pi1 =
|
||||
!liabsInUsdc.eq(ZERO_I80F48()) &&
|
||||
usdcMint.toBase58() !== bank.mint.toBase58()
|
||||
!liabsInUsdc.isZero() &&
|
||||
!usdcMint.equals(bank.mint)
|
||||
? computePriceImpactOnJup(
|
||||
pis,
|
||||
toUiDecimalsForQuote(liabsInUsdc),
|
||||
|
@ -265,8 +269,8 @@ export function getPriceImpactForLiqor(
|
|||
)
|
||||
: 0;
|
||||
const pi2 =
|
||||
!assets.eq(ZERO_I80F48()) &&
|
||||
usdcMint.toBase58() !== bank.mint.toBase58()
|
||||
!assets.isZero() &&
|
||||
!usdcMint.equals(bank.mint)
|
||||
? computePriceImpactOnJup(
|
||||
pis,
|
||||
toUiDecimals(assets.mul(bank.price), bank.mintDecimals),
|
||||
|
@ -330,41 +334,47 @@ export function getPerpPositionsToBeLiquidated(
|
|||
return {
|
||||
account: a,
|
||||
health: a.getHealth(group, HealthType.liquidationEnd),
|
||||
healthRatio: a.getHealthRatioUi(group, HealthType.liquidationEnd),
|
||||
};
|
||||
});
|
||||
|
||||
return Array.from(group.perpMarketsMapByMarketIndex.values())
|
||||
.filter((pm) => !pm.name.includes('OLD'))
|
||||
.map((pm) => {
|
||||
|
||||
const assetHealthPerLot =
|
||||
I80F48.fromNumber(-1)
|
||||
.mul(pm.price)
|
||||
.mul(I80F48.fromU64(pm.baseLotSize))
|
||||
.mul(pm.initBaseAssetWeight)
|
||||
.add(
|
||||
I80F48.fromU64(pm.baseLotSize)
|
||||
.mul(pm.price)
|
||||
.mul(
|
||||
ONE_I80F48() // quoteInitAssetWeight
|
||||
.mul(ONE_I80F48().sub(pm.baseLiquidationFee)),
|
||||
),
|
||||
);
|
||||
|
||||
const liabWeightPerLot =
|
||||
pm.price
|
||||
.mul(I80F48.fromU64(pm.baseLotSize))
|
||||
.mul(pm.initBaseLiabWeight)
|
||||
.sub(
|
||||
I80F48.fromU64(pm.baseLotSize)
|
||||
.mul(pm.price)
|
||||
.mul(ONE_I80F48()) // quoteInitLiabWeight
|
||||
.mul(ONE_I80F48().add(pm.baseLiquidationFee)),
|
||||
);
|
||||
|
||||
const baseLots = mangoAccountsWithHealth
|
||||
.filter((a) => a.account.getPerpPosition(pm.perpMarketIndex))
|
||||
.reduce((sum, a) => {
|
||||
const baseLots = a.account.getPerpPosition(
|
||||
pm.perpMarketIndex,
|
||||
)!.basePositionLots;
|
||||
const unweightedHealthPerLot = baseLots.gt(new BN(0))
|
||||
? I80F48.fromNumber(-1)
|
||||
.mul(pm.price)
|
||||
.mul(I80F48.fromU64(pm.baseLotSize))
|
||||
.mul(pm.initBaseAssetWeight)
|
||||
.add(
|
||||
I80F48.fromU64(pm.baseLotSize)
|
||||
.mul(pm.price)
|
||||
.mul(
|
||||
ONE_I80F48() // quoteInitAssetWeight
|
||||
.mul(ONE_I80F48().sub(pm.baseLiquidationFee)),
|
||||
),
|
||||
)
|
||||
: pm.price
|
||||
.mul(I80F48.fromU64(pm.baseLotSize))
|
||||
.mul(pm.initBaseLiabWeight)
|
||||
.sub(
|
||||
I80F48.fromU64(pm.baseLotSize)
|
||||
.mul(pm.price)
|
||||
.mul(ONE_I80F48()) // quoteInitLiabWeight
|
||||
.mul(ONE_I80F48().add(pm.baseLiquidationFee)),
|
||||
);
|
||||
const unweightedHealthPerLot = baseLots.gt(ZERO)
|
||||
? assetHealthPerLot
|
||||
: liabWeightPerLot;
|
||||
|
||||
const maxBaseLots = a.health
|
||||
.min(ZERO_I80F48())
|
||||
|
@ -372,7 +382,7 @@ export function getPerpPositionsToBeLiquidated(
|
|||
.div(unweightedHealthPerLot.abs())
|
||||
.min(I80F48.fromU64(baseLots).abs());
|
||||
|
||||
return sum.add(maxBaseLots);
|
||||
return sum.iadd(maxBaseLots);
|
||||
}, ONE_I80F48());
|
||||
|
||||
const notionalPositionUi = toUiDecimalsForQuote(
|
||||
|
|
Loading…
Reference in New Issue