mango-v4/ts/client/src/accounts/healthCache.ts

228 lines
8.8 KiB
TypeScript

import { I80F48, I80F48Dto, ZERO_I80F48 } from './I80F48';
import { HealthType } from './mangoAccount';
// ░░░░
//
// ██
// ██░░██
// ░░ ░░ ██░░░░░░██ ░░░░
// ██░░░░░░░░░░██
// ██░░░░░░░░░░██
// ██░░░░░░░░░░░░░░██
// ██░░░░░░██████░░░░░░██
// ██░░░░░░██████░░░░░░██
// ██░░░░░░░░██████░░░░░░░░██
// ██░░░░░░░░██████░░░░░░░░██
// ██░░░░░░░░░░██████░░░░░░░░░░██
// ██░░░░░░░░░░░░██████░░░░░░░░░░░░██
// ██░░░░░░░░░░░░██████░░░░░░░░░░░░██
// ██░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░██
// ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
// ██░░░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░░░██
// ██░░░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░░░██
// ██░░░░░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░░░░░██
// ░░ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
// ██████████████████████████████████████████
// warning: this code is copy pasta from rust, keep in sync with health.rs
export class HealthCache {
tokenInfos: TokenInfo[];
serum3Infos: Serum3Info[];
perpInfos: PerpInfo[];
constructor(dto: HealthCacheDto) {
this.tokenInfos = dto.tokenInfos.map((dto) => new TokenInfo(dto));
this.serum3Infos = dto.serum3Infos.map((dto) => new Serum3Info(dto));
this.perpInfos = dto.perpInfos.map((dto) => new PerpInfo(dto));
}
public health(healthType: HealthType): I80F48 {
let health = ZERO_I80F48;
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(healthType);
health = health.add(contrib);
}
for (const serum3Info of this.serum3Infos) {
const contrib = serum3Info.healthContribution(
healthType,
this.tokenInfos,
);
health = health.add(contrib);
}
for (const perpInfo of this.perpInfos) {
const contrib = perpInfo.healthContribution(healthType);
health = health.add(contrib);
}
return health;
}
}
export class TokenInfo {
constructor(dto: TokenInfoDto) {
this.tokenIndex = dto.tokenIndex;
this.maintAssetWeight = I80F48.from(dto.maintAssetWeight);
this.initAssetWeight = I80F48.from(dto.initAssetWeight);
this.maintLiabWeight = I80F48.from(dto.maintLiabWeight);
this.initLiabWeight = I80F48.from(dto.initLiabWeight);
this.oraclePrice = I80F48.from(dto.oraclePrice);
this.balance = I80F48.from(dto.balance);
this.serum3MaxReserved = I80F48.from(dto.serum3MaxReserved);
}
tokenIndex: number;
maintAssetWeight: I80F48;
initAssetWeight: I80F48;
maintLiabWeight: I80F48;
initLiabWeight: I80F48;
oraclePrice: I80F48; // native/native
// in health-reference-token native units
balance: I80F48;
// in health-reference-token native units
serum3MaxReserved: I80F48;
assetWeight(healthType: HealthType): I80F48 {
return healthType == HealthType.init
? this.initAssetWeight
: this.maintAssetWeight;
}
liabWeight(healthType: HealthType): I80F48 {
return healthType == HealthType.init
? this.initLiabWeight
: this.maintLiabWeight;
}
healthContribution(healthType: HealthType): I80F48 {
return (
this.balance.isNeg()
? this.liabWeight(healthType)
: this.assetWeight(healthType)
).mul(this.balance);
}
}
export class Serum3Info {
constructor(dto: Serum3InfoDto) {
this.reserved = I80F48.from(dto.reserved);
this.baseIndex = dto.baseIndex;
this.quoteIndex = dto.quoteIndex;
}
reserved: I80F48;
baseIndex: number;
quoteIndex: number;
healthContribution(healthType: HealthType, tokenInfos: TokenInfo[]): I80F48 {
const baseInfo = tokenInfos[this.baseIndex];
const quoteInfo = tokenInfos[this.quoteIndex];
const reserved = this.reserved;
if (reserved.isZero()) {
return ZERO_I80F48;
}
// How much the health would increase if the reserved balance were applied to the passed
// token info?
const computeHealthEffect = function (tokenInfo: TokenInfo) {
// This balance includes all possible reserved funds from markets that relate to the
// token, including this market itself: `reserved` is already included in `max_balance`.
const maxBalance = tokenInfo.balance.add(tokenInfo.serum3MaxReserved);
// Assuming `reserved` was added to `max_balance` last (because that gives the smallest
// health effects): how much did health change because of it?
let assetPart, liabPart;
if (maxBalance.gte(reserved)) {
assetPart = reserved;
liabPart = ZERO_I80F48;
} else if (maxBalance.isNeg()) {
assetPart = ZERO_I80F48;
liabPart = reserved;
} else {
assetPart = maxBalance;
liabPart = reserved.sub(maxBalance);
}
const assetWeight = tokenInfo.assetWeight(healthType);
const liabWeight = tokenInfo.liabWeight(healthType);
return assetWeight.mul(assetPart).add(liabWeight.mul(liabPart));
};
const reservedAsBase = computeHealthEffect(baseInfo);
const reservedAsQuote = computeHealthEffect(quoteInfo);
return reservedAsBase.min(reservedAsQuote);
}
}
export class PerpInfo {
constructor(dto: PerpInfoDto) {
this.maintAssetWeight = I80F48.from(dto.maintAssetWeight);
this.initAssetWeight = I80F48.from(dto.initAssetWeight);
this.maintLiabWeight = I80F48.from(dto.maintLiabWeight);
this.initLiabWeight = I80F48.from(dto.initLiabWeight);
this.base = I80F48.from(dto.base);
this.quote = I80F48.from(dto.quote);
}
maintAssetWeight: I80F48;
initAssetWeight: I80F48;
maintLiabWeight: I80F48;
initLiabWeight: I80F48;
// in health-reference-token native units, needs scaling by asset/liab
base: I80F48;
// in health-reference-token native units, no asset/liab factor needed
quote: I80F48;
healthContribution(healthType: HealthType): I80F48 {
let weight;
if (healthType == HealthType.init && this.base.isNeg()) {
weight = this.initLiabWeight;
} else if (healthType == HealthType.init && !this.base.isNeg()) {
weight = this.initAssetWeight;
}
if (healthType == HealthType.maint && this.base.isNeg()) {
weight = this.maintLiabWeight;
}
if (healthType == HealthType.maint && !this.base.isNeg()) {
weight = this.maintAssetWeight;
}
// FUTURE: Allow v3-style "reliable" markets where we can return
// `self.quote + weight * self.base` here
return this.quote.add(weight.mul(this.base)).min(ZERO_I80F48);
}
}
export class HealthCacheDto {
tokenInfos: TokenInfoDto[];
serum3Infos: Serum3InfoDto[];
perpInfos: PerpInfoDto[];
}
export class TokenInfoDto {
tokenIndex: number;
maintAssetWeight: I80F48Dto;
initAssetWeight: I80F48Dto;
maintLiabWeight: I80F48Dto;
initLiabWeight: I80F48Dto;
oraclePrice: I80F48Dto; // native/native
// in health-reference-token native units
balance: I80F48Dto;
// in health-reference-token native units
serum3MaxReserved: I80F48Dto;
}
export class Serum3InfoDto {
reserved: I80F48Dto;
baseIndex: number;
quoteIndex: number;
}
export class PerpInfoDto {
maintAssetWeight: I80F48Dto;
initAssetWeight: I80F48Dto;
maintLiabWeight: I80F48Dto;
initLiabWeight: I80F48Dto;
// in health-reference-token native units, needs scaling by asset/liab
base: I80F48Dto;
// in health-reference-token native units, no asset/liab factor needed
quote: I80F48Dto;
}