From 14147cd3958952044a14400016f60ee17d747f32 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Tue, 12 Jul 2022 12:05:19 +0200 Subject: [PATCH] client functions 2 (#103) Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 94 ++++++++++++++++++- ts/client/src/scripts/mb-example1-ids-json.ts | 42 +++++++++ 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 984e4655b..6a4d311cf 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -126,6 +126,24 @@ export class MangoAccount { return ta ? ta.uiBorrows(bank) : 0; } + getHealth(healthType: HealthType): I80F48 { + return healthType == HealthType.init + ? (this.accountData as MangoAccountData).initHealth + : (this.accountData as MangoAccountData).maintHealth; + } + + /** + * TODO: this is incorrect, getAssetsVal and getLiabsVal are in equity, and not in init health. + * Wait for dev to be deployed to mainnet, and then we can adapt this. + */ + getHealthRatio(healthType: HealthType): I80F48 { + const assets = this.getAssetsVal(); + const liabs = this.getLiabsVal(); + return liabs.gt(ZERO_I80F48) + ? assets.div(liabs).sub(ONE_I80F48).mul(I80F48.fromNumber(100)) + : I80F48.fromNumber(100); + } + /** * Sum of all the assets i.e. token deposits, borrows, total assets in spot open orders, (perps positions is todo) in terms of quote value. */ @@ -142,7 +160,7 @@ export class MangoAccount { * The amount of native quote you could withdraw against your existing assets. */ getCollateralValue(): I80F48 { - return (this.accountData as MangoAccountData).initHealth; + return this.getHealth(HealthType.init); } /** @@ -173,10 +191,7 @@ export class MangoAccount { * The amount of given native token you can borrow, considering all existing assets as collateral except the deposits for this token. * The existing native deposits need to be added to get the full amount that could be withdrawn. */ - async getMaxWithdrawWithBorrowForToken( - group: Group, - tokenName: string, - ): Promise { + getMaxWithdrawWithBorrowForToken(group: Group, tokenName: string): I80F48 { const bank = group.banksMap.get(tokenName); const initHealth = (this.accountData as MangoAccountData).initHealth; const inUsdcUnits = this.getInNativeUsdcUnits(bank) @@ -186,6 +201,75 @@ export class MangoAccount { return newInitHealth.div(bank.price.mul(bank.initLiabWeight)); } + /** + * The amount of given source native token you can swap to a target token considering all existing assets as collateral. + * note: slippageAndFeesFactor is a normalized number, <1, e.g. a slippage of 5% and some fees which are 1%, then slippageAndFeesFactor = 0.94 + * the factor is used to compute how much target can be obtained by swapping source + */ + getMaxSourceForTokenSwap( + group: Group, + sourceTokenName: string, + targetTokenName: string, + slippageAndFeesFactor: number, + ): I80F48 { + const initHealth = (this.accountData as MangoAccountData).initHealth; + + const sourceBank = group.banksMap.get(sourceTokenName); + const targetBank = group.banksMap.get(targetTokenName); + + // This is a conservative approximation of the easy case, where + // mango account has no token positions for source and target tokens, or + // borrows for source and deposits for target tokens before the swap. + // Tighter estimates can be obtained by adding cases where deposits can exist for source, + // and borrows for target. TODO: solve this by searching over a blackbox like health formula. + // Lets solve below for s, + // h - s * slw + t * taw = 0 + // where h is init_health, s is source amount in usdc native units, and t is target amount in usdc native units + // where t = s * slip ( s < 1), where slip is factor for slippage and fees which is normalised e.g. for 5% slippage, slip = 0.95 + // h - s * (slw - slip * taw) = 0 + // s = h / ( slw - slip * taw ) + return initHealth + .div( + sourceBank.initLiabWeight.sub( + I80F48.fromNumber(slippageAndFeesFactor).mul( + targetBank.initAssetWeight, + ), + ), + ) + .div(sourceBank.price); + } + + /** + * Simulates new health after applying tokenChanges to the token positions. Useful to simulate health after a potential swap. + */ + simHealthWithTokenPositionChanges( + group: Group, + tokenChanges: { tokenName: string; tokenAmount: number }[], + ): I80F48 { + // This is a approximation of the easy case, where + // mango account has no token positions for tokens in changes list, or + // the change is in direction e.g. deposits for deposits, borrows for borrows, of existing token position. + // TODO: recompute entire health using components. + const initHealth = (this.accountData as MangoAccountData).initHealth; + for (const change of tokenChanges) { + const bank = group.banksMap.get(change.tokenName); + if (change.tokenAmount >= 0) { + initHealth.add( + bank.initAssetWeight + .mul(I80F48.fromNumber(change.tokenAmount)) + .mul(bank.price), + ); + } else { + initHealth.sub( + bank.initLiabWeight + .mul(I80F48.fromNumber(change.tokenAmount)) + .mul(bank.price), + ); + } + } + return initHealth; + } + /** * The remaining native quote margin available for given market. * diff --git a/ts/client/src/scripts/mb-example1-ids-json.ts b/ts/client/src/scripts/mb-example1-ids-json.ts index 829d78c82..abf41b234 100644 --- a/ts/client/src/scripts/mb-example1-ids-json.ts +++ b/ts/client/src/scripts/mb-example1-ids-json.ts @@ -1,6 +1,7 @@ import { AnchorProvider, Wallet } from '@project-serum/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; +import { HealthType } from '../accounts/mangoAccount'; import { MangoClient } from '../client'; import { toUiDecimals } from '../utils'; @@ -52,6 +53,14 @@ async function main() { 'mangoAccount.getEquity() ' + toUiDecimals(mangoAccount.getEquity().toNumber()), ); + console.log( + 'mangoAccount.getHealth(HealthType.init) ' + + toUiDecimals(mangoAccount.getHealth(HealthType.init).toNumber()), + ); + console.log( + 'mangoAccount.getHealthRatio(HealthType.init) ' + + mangoAccount.getHealthRatio(HealthType.init).toNumber(), + ); console.log( 'mangoAccount.getCollateralValue() ' + toUiDecimals(mangoAccount.getCollateralValue().toNumber()), @@ -72,6 +81,39 @@ async function main() { ).toNumber(), ), ); + console.log( + "mangoAccount.getMaxSourceForTokenSwap(group, 'USDC', 'BTC') " + + toUiDecimals( + ( + await mangoAccount.getMaxSourceForTokenSwap( + group, + 'USDC', + 'BTC', + 0.94, + ) + ).toNumber(), + ), + ); + console.log( + 'mangoAccount.simHealthWithTokenPositionChanges ' + + toUiDecimals( + ( + await mangoAccount.simHealthWithTokenPositionChanges(group, [ + { + tokenName: 'USDC', + tokenAmount: + -20_000 * + Math.pow(10, group.banksMap.get('BTC')?.mintDecimals!), + }, + { + tokenName: 'BTC', + tokenAmount: + 1 * Math.pow(10, group.banksMap.get('BTC')?.mintDecimals!), + }, + ]) + ).toNumber(), + ), + ); } process.exit();