Health from health components in ts/client (#104)

This commit is contained in:
microwavedcola1 2022-07-13 19:18:55 +02:00 committed by GitHub
parent 03882e6dd3
commit bc4c57911a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 578 additions and 8 deletions

View File

@ -1,11 +1,11 @@
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use crate::state::{PerpMarketIndex, TokenIndex};
use crate::state::{HealthCache, PerpMarketIndex, TokenIndex};
#[event]
#[derive(Debug)]
pub struct MangoAccountData {
pub health_cache: HealthCache,
pub init_health: I80F48,
pub maint_health: I80F48,
pub equity: Equity,

View File

@ -24,9 +24,10 @@ pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
let equity = compute_equity(&account, &account_retriever)?;
emit!(MangoAccountData {
health_cache,
init_health,
maint_health,
equity
equity,
});
Ok(())

View File

@ -410,7 +410,8 @@ pub fn compute_health(
Ok(new_health_cache(account, retriever)?.health(health_type))
}
struct TokenInfo {
#[derive(AnchorDeserialize, AnchorSerialize)]
pub struct TokenInfo {
token_index: TokenIndex,
maint_asset_weight: I80F48,
init_asset_weight: I80F48,
@ -451,7 +452,8 @@ impl TokenInfo {
}
}
struct Serum3Info {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Serum3Info {
reserved: I80F48,
base_index: usize,
quote_index: usize,
@ -496,7 +498,8 @@ impl Serum3Info {
}
}
struct PerpInfo {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PerpInfo {
maint_asset_weight: I80F48,
init_asset_weight: I80F48,
maint_liab_weight: I80F48,
@ -535,6 +538,7 @@ impl PerpInfo {
}
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct HealthCache {
token_infos: Vec<TokenInfo>,
serum3_infos: Vec<Serum3Info>,

View File

@ -0,0 +1,224 @@
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) {
let contrib = tokenInfo.healthContribution(healthType);
health = health.add(contrib);
}
for (const serum3Info of this.serum3Infos) {
let contrib = serum3Info.healthContribution(healthType, this.tokenInfos);
health = health.add(contrib);
}
for (const perpInfo of this.perpInfos) {
let 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 {
let baseInfo = tokenInfos[this.baseIndex];
let quoteInfo = tokenInfos[this.quoteIndex];
let 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?
let 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`.
let 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);
}
let assetWeight = tokenInfo.assetWeight(healthType);
let liabWeight = tokenInfo.liabWeight(healthType);
return assetWeight.mul(assetPart).add(liabWeight.mul(liabPart));
};
let reservedAsBase = computeHealthEffect(baseInfo);
let 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;
}

View File

@ -5,6 +5,7 @@ import { MangoClient } from '../client';
import { nativeI80F48ToUi } from '../utils';
import { Bank, QUOTE_DECIMALS } from './bank';
import { Group } from './group';
import { HealthCache, HealthCacheDto } from './healthCache';
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from './I80F48';
export class MangoAccount {
public tokens: TokenPosition[];
@ -422,12 +423,14 @@ export class HealthType {
export class MangoAccountData {
constructor(
public healthCache: HealthCache,
public initHealth: I80F48,
public maintHealth: I80F48,
public equity: Equity,
) {}
static from(event: {
healthCache: HealthCacheDto;
initHealth: I80F48Dto;
maintHealth: I80F48Dto;
equity: {
@ -438,6 +441,7 @@ export class MangoAccountData {
tokenAssets: any;
}) {
return new MangoAccountData(
new HealthCache(event.healthCache),
I80F48.from(event.initHealth),
I80F48.from(event.maintHealth),
Equity.from(event.equity),

View File

@ -3427,6 +3427,158 @@ export type MangoV4 = {
]
}
},
{
"name": "TokenInfo",
"type": {
"kind": "struct",
"fields": [
{
"name": "tokenIndex",
"type": "u16"
},
{
"name": "maintAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "oraclePrice",
"type": {
"defined": "I80F48"
}
},
{
"name": "balance",
"type": {
"defined": "I80F48"
}
},
{
"name": "serum3MaxReserved",
"type": {
"defined": "I80F48"
}
}
]
}
},
{
"name": "Serum3Info",
"type": {
"kind": "struct",
"fields": [
{
"name": "reserved",
"type": {
"defined": "I80F48"
}
},
{
"name": "baseIndex",
"type": "u64"
},
{
"name": "quoteIndex",
"type": "u64"
}
]
}
},
{
"name": "PerpInfo",
"type": {
"kind": "struct",
"fields": [
{
"name": "maintAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "base",
"type": {
"defined": "I80F48"
}
},
{
"name": "quote",
"type": {
"defined": "I80F48"
}
}
]
}
},
{
"name": "HealthCache",
"type": {
"kind": "struct",
"fields": [
{
"name": "tokenInfos",
"type": {
"vec": {
"defined": "TokenInfo"
}
}
},
{
"name": "serum3Infos",
"type": {
"vec": {
"defined": "Serum3Info"
}
}
},
{
"name": "perpInfos",
"type": {
"vec": {
"defined": "PerpInfo"
}
}
}
]
}
},
{
"name": "TokenPosition",
"type": {
@ -4046,6 +4198,13 @@ export type MangoV4 = {
{
"name": "MangoAccountData",
"fields": [
{
"name": "healthCache",
"type": {
"defined": "HealthCache"
},
"index": false
},
{
"name": "initHealth",
"type": {
@ -8048,6 +8207,158 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "TokenInfo",
"type": {
"kind": "struct",
"fields": [
{
"name": "tokenIndex",
"type": "u16"
},
{
"name": "maintAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "oraclePrice",
"type": {
"defined": "I80F48"
}
},
{
"name": "balance",
"type": {
"defined": "I80F48"
}
},
{
"name": "serum3MaxReserved",
"type": {
"defined": "I80F48"
}
}
]
}
},
{
"name": "Serum3Info",
"type": {
"kind": "struct",
"fields": [
{
"name": "reserved",
"type": {
"defined": "I80F48"
}
},
{
"name": "baseIndex",
"type": "u64"
},
{
"name": "quoteIndex",
"type": "u64"
}
]
}
},
{
"name": "PerpInfo",
"type": {
"kind": "struct",
"fields": [
{
"name": "maintAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "initLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "base",
"type": {
"defined": "I80F48"
}
},
{
"name": "quote",
"type": {
"defined": "I80F48"
}
}
]
}
},
{
"name": "HealthCache",
"type": {
"kind": "struct",
"fields": [
{
"name": "tokenInfos",
"type": {
"vec": {
"defined": "TokenInfo"
}
}
},
{
"name": "serum3Infos",
"type": {
"vec": {
"defined": "Serum3Info"
}
}
},
{
"name": "perpInfos",
"type": {
"vec": {
"defined": "PerpInfo"
}
}
}
]
}
},
{
"name": "TokenPosition",
"type": {
@ -8667,6 +8978,13 @@ export const IDL: MangoV4 = {
{
"name": "MangoAccountData",
"fields": [
{
"name": "healthCache",
"type": {
"defined": "HealthCache"
},
"index": false
},
{
"name": "initHealth",
"type": {

View File

@ -1,6 +1,7 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { HealthType } from '../accounts/mangoAccount';
import { OrderType, Side } from '../accounts/perp';
import {
Serum3OrderType,
@ -67,12 +68,22 @@ async function main() {
const randomKey = new PublicKey(
'4ZkS7ZZkxfsC3GtvvsHP3DFcUeByU9zzZELS4r8HCELo',
);
await client.editMangoAccount(group, mangoAccount, 'my_changed_name', randomKey);
await client.editMangoAccount(
group,
mangoAccount,
'my_changed_name',
randomKey,
);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());
console.log(`...resetting mango account name, and re-setting a delegate`);
await client.editMangoAccount(group, mangoAccount, 'my_mango_account', PublicKey.default);
await client.editMangoAccount(
group,
mangoAccount,
'my_mango_account',
PublicKey.default,
);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());
}
@ -199,6 +210,14 @@ async function main() {
'...mangoAccount.getCollateralValue() ' +
toUiDecimals(mangoAccount.getCollateralValue().toNumber()),
);
console.log(
'...mangoAccount.accountData["healthCache"].health(HealthType.init) ' +
toUiDecimals(
mangoAccount.accountData['healthCache']
.health(HealthType.init)
.toNumber(),
),
);
console.log(
'...mangoAccount.getAssetsVal() ' +
toUiDecimals(mangoAccount.getAssetsVal().toNumber()),