reduce allocations in risk code

This commit is contained in:
Maximilian Schneider 2024-08-13 20:42:40 +01:00
parent a552a20559
commit 4a5bfd9fca
3 changed files with 83 additions and 83 deletions

View File

@ -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

View File

@ -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;

View File

@ -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(