2022-10-11 00:39:57 -07:00
import { AnchorProvider , BN } from '@project-serum/anchor' ;
2022-04-12 07:19:58 -07:00
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes' ;
2022-09-23 20:14:12 -07:00
import { OpenOrders , Order , Orderbook } from '@project-serum/serum/lib/market' ;
2022-11-04 08:07:26 -07:00
import { AccountInfo , PublicKey , TransactionSignature } from '@solana/web3.js' ;
2022-04-12 21:37:36 -07:00
import { MangoClient } from '../client' ;
2022-12-08 01:16:06 -08:00
import { OPENBOOK_PROGRAM_ID } from '../constants' ;
2022-09-30 06:07:43 -07:00
import { I80F48 , I80F48Dto , ONE_I80F48 , ZERO_I80F48 } from '../numbers/I80F48' ;
import { toNativeI80F48 , toUiDecimals , toUiDecimalsForQuote } from '../utils' ;
2022-12-19 09:30:26 -08:00
import { Bank , TokenIndex } from './bank' ;
2022-06-03 06:34:05 -07:00
import { Group } from './group' ;
2022-09-29 06:51:09 -07:00
import { HealthCache } from './healthCache' ;
import { PerpMarket , PerpMarketIndex , PerpOrder , PerpOrderSide } from './perp' ;
import { MarketIndex , Serum3Side } from './serum3' ;
2022-04-02 23:57:45 -07:00
export class MangoAccount {
2022-10-11 00:34:02 -07:00
public name : string ;
2022-06-22 02:21:02 -07:00
public tokens : TokenPosition [ ] ;
public serum3 : Serum3Orders [ ] ;
2022-08-18 04:45:31 -07:00
public perps : PerpPosition [ ] ;
2022-09-20 03:57:01 -07:00
public perpOpenOrders : PerpOo [ ] ;
2022-04-02 23:57:45 -07:00
static from (
publicKey : PublicKey ,
obj : {
group : PublicKey ;
owner : PublicKey ;
2022-08-04 01:41:54 -07:00
name : number [ ] ;
2022-04-02 23:57:45 -07:00
delegate : PublicKey ;
accountNum : number ;
2022-10-11 00:34:02 -07:00
beingLiquidated : number ;
inHealthRegion : number ;
2022-08-15 02:10:33 -07:00
netDeposits : BN ;
2022-11-02 05:13:29 -07:00
perpSpotTransfers : BN ;
2022-10-11 00:34:02 -07:00
healthRegionBeginInitHealth : BN ;
2022-08-04 00:07:32 -07:00
headerVersion : number ;
2022-08-04 01:41:54 -07:00
tokens : unknown ;
2022-08-04 10:42:41 -07:00
serum3 : unknown ;
2022-08-04 01:41:54 -07:00
perps : unknown ;
perpOpenOrders : unknown ;
2022-04-02 23:57:45 -07:00
} ,
2022-10-11 00:34:02 -07:00
) : MangoAccount {
2022-04-02 23:57:45 -07:00
return new MangoAccount (
publicKey ,
obj . group ,
obj . owner ,
2022-08-04 01:41:54 -07:00
obj . name ,
2022-04-02 23:57:45 -07:00
obj . delegate ,
obj . accountNum ,
2022-10-11 00:34:02 -07:00
obj . beingLiquidated == 1 ,
obj . inHealthRegion == 1 ,
2022-08-04 01:41:54 -07:00
obj . netDeposits ,
2022-11-02 05:13:29 -07:00
obj . perpSpotTransfers ,
2022-10-11 00:34:02 -07:00
obj . healthRegionBeginInitHealth ,
2022-08-04 01:41:54 -07:00
obj . headerVersion ,
obj . tokens as TokenPositionDto [ ] ,
obj . serum3 as Serum3PositionDto [ ] ,
obj . perps as PerpPositionDto [ ] ,
2022-09-20 03:57:01 -07:00
obj . perpOpenOrders as PerpOoDto [ ] ,
2022-09-29 06:51:09 -07:00
new Map ( ) , // serum3OosMapByMarketIndex
2022-04-02 23:57:45 -07:00
) ;
}
constructor (
public publicKey : PublicKey ,
2022-04-07 09:58:42 -07:00
public group : PublicKey ,
public owner : PublicKey ,
2022-08-04 01:41:54 -07:00
name : number [ ] ,
2022-04-07 09:58:42 -07:00
public delegate : PublicKey ,
2022-08-04 01:41:54 -07:00
public accountNum : number ,
2022-10-11 00:34:02 -07:00
public beingLiquidated : boolean ,
public inHealthRegion : boolean ,
public netDeposits : BN ,
2022-11-02 05:13:29 -07:00
public perpSpotTransfers : BN ,
2022-10-11 00:34:02 -07:00
public healthRegionBeginInitHealth : BN ,
public headerVersion : number ,
2022-07-25 07:07:53 -07:00
tokens : TokenPositionDto [ ] ,
serum3 : Serum3PositionDto [ ] ,
perps : PerpPositionDto [ ] ,
2022-09-20 03:57:01 -07:00
perpOpenOrders : PerpOoDto [ ] ,
2022-09-29 06:51:09 -07:00
public serum3OosMapByMarketIndex : Map < number , OpenOrders > ,
2022-04-02 23:57:45 -07:00
) {
2022-04-12 07:19:58 -07:00
this . name = utf8 . decode ( new Uint8Array ( name ) ) . split ( '\x00' ) [ 0 ] ;
2022-07-25 07:07:53 -07:00
this . tokens = tokens . map ( ( dto ) = > TokenPosition . from ( dto ) ) ;
this . serum3 = serum3 . map ( ( dto ) = > Serum3Orders . from ( dto ) ) ;
2022-08-18 04:45:31 -07:00
this . perps = perps . map ( ( dto ) = > PerpPosition . from ( dto ) ) ;
2022-09-20 03:57:01 -07:00
this . perpOpenOrders = perpOpenOrders . map ( ( dto ) = > PerpOo . from ( dto ) ) ;
2022-04-02 23:57:45 -07:00
}
2022-10-11 00:39:57 -07:00
public async reload ( client : MangoClient ) : Promise < MangoAccount > {
2022-09-02 15:47:09 -07:00
const mangoAccount = await client . getMangoAccount ( this ) ;
2022-09-29 06:51:09 -07:00
await mangoAccount . reloadAccountData ( client ) ;
2022-09-02 15:47:09 -07:00
Object . assign ( this , mangoAccount ) ;
return mangoAccount ;
2022-07-04 03:29:35 -07:00
}
2022-10-11 00:39:57 -07:00
public async reloadWithSlot (
2022-09-05 09:31:57 -07:00
client : MangoClient ,
) : Promise < { value : MangoAccount ; slot : number } > {
const resp = await client . getMangoAccountWithSlot ( this . publicKey ) ;
2022-09-29 06:51:09 -07:00
await resp ? . value . reloadAccountData ( client ) ;
2022-09-05 09:31:57 -07:00
Object . assign ( this , resp ? . value ) ;
return { value : resp ! . value , slot : resp ! . slot } ;
}
2022-09-29 06:51:09 -07:00
async reloadAccountData ( client : MangoClient ) : Promise < MangoAccount > {
const serum3Active = this . serum3Active ( ) ;
const ais =
await client . program . provider . connection . getMultipleAccountsInfo (
serum3Active . map ( ( serum3 ) = > serum3 . openOrders ) ,
) ;
this . serum3OosMapByMarketIndex = new Map (
Array . from (
ais . map ( ( ai , i ) = > {
if ( ! ai ) {
throw new Error (
` Undefined AI for open orders ${ serum3Active [ i ] . openOrders } and market ${ serum3Active [ i ] . marketIndex } ! ` ,
) ;
}
const oo = OpenOrders . fromAccountInfo (
serum3Active [ i ] . openOrders ,
ai ,
2022-12-08 01:16:06 -08:00
OPENBOOK_PROGRAM_ID [ client . cluster ] ,
2022-09-29 06:51:09 -07:00
) ;
return [ serum3Active [ i ] . marketIndex , oo ] ;
} ) ,
) ,
) ;
2022-08-27 00:55:55 -07:00
return this ;
2022-04-12 21:37:36 -07:00
}
2022-10-11 00:39:57 -07:00
public isDelegate ( client : MangoClient ) : boolean {
return this . delegate . equals (
( client . program . provider as AnchorProvider ) . wallet . publicKey ,
) ;
}
public tokensActive ( ) : TokenPosition [ ] {
2022-08-31 02:36:44 -07:00
return this . tokens . filter ( ( token ) = > token . isActive ( ) ) ;
}
2022-10-11 00:39:57 -07:00
public serum3Active ( ) : Serum3Orders [ ] {
2022-08-31 02:36:44 -07:00
return this . serum3 . filter ( ( serum3 ) = > serum3 . isActive ( ) ) ;
}
2022-11-21 11:36:13 -08:00
public perpPositionExistsForMarket ( perpMarket : PerpMarket ) : boolean {
return this . perps . some (
( pp ) = > pp . isActive ( ) && pp . marketIndex == perpMarket . perpMarketIndex ,
) ;
}
2022-10-11 00:39:57 -07:00
public perpActive ( ) : PerpPosition [ ] {
2022-08-31 02:36:44 -07:00
return this . perps . filter ( ( perp ) = > perp . isActive ( ) ) ;
}
2022-10-11 00:39:57 -07:00
public perpOrdersActive ( ) : PerpOo [ ] {
2022-09-20 03:57:01 -07:00
return this . perpOpenOrders . filter (
( oo ) = > oo . orderMarket !== PerpOo . OrderMarketUnset ,
) ;
}
2022-10-11 00:39:57 -07:00
public getToken ( tokenIndex : TokenIndex ) : TokenPosition | undefined {
2022-04-02 23:57:45 -07:00
return this . tokens . find ( ( ta ) = > ta . tokenIndex == tokenIndex ) ;
}
2022-10-11 00:39:57 -07:00
public getSerum3Account ( marketIndex : MarketIndex ) : Serum3Orders | undefined {
2022-04-08 03:30:21 -07:00
return this . serum3 . find ( ( sa ) = > sa . marketIndex == marketIndex ) ;
}
2022-10-11 00:39:57 -07:00
public getPerpPosition (
perpMarketIndex : PerpMarketIndex ,
useEventQueue? : boolean ,
) : PerpPosition | undefined {
2022-10-07 04:52:04 -07:00
return this . perps . find ( ( pp ) = > pp . marketIndex == perpMarketIndex ) ;
}
2022-10-11 00:39:57 -07:00
public getPerpPositionUi (
group : Group ,
perpMarketIndex : PerpMarketIndex ,
useEventQueue? : boolean ,
) : number {
2022-10-07 04:52:04 -07:00
const pp = this . perps . find ( ( pp ) = > pp . marketIndex == perpMarketIndex ) ;
if ( ! pp ) {
throw new Error ( ` No position found for PerpMarket ${ perpMarketIndex } ! ` ) ;
}
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
2022-10-11 00:39:57 -07:00
return pp . getBasePositionUi ( perpMarket , useEventQueue ) ;
2022-10-07 04:52:04 -07:00
}
2022-10-11 00:39:57 -07:00
public getSerum3OoAccount ( marketIndex : MarketIndex ) : OpenOrders {
2022-09-29 06:51:09 -07:00
const oo : OpenOrders | undefined =
this . serum3OosMapByMarketIndex . get ( marketIndex ) ;
if ( ! oo ) {
throw new Error (
` Open orders account not loaded for market with marketIndex ${ marketIndex } ! ` ,
) ;
}
return oo ;
}
2022-08-18 07:19:37 -07:00
// How to navigate
// * if a function is returning a I80F48, then usually the return value is in native quote or native token, unless specified
2022-08-18 07:39:22 -07:00
// * if a function is returning a number, then usually the return value is in ui tokens, unless specified
2022-08-18 07:19:37 -07:00
// * functions try to be explicit by having native or ui in the name to better reflect the value
// * some values might appear unexpected large or small, usually the doc contains a "note"
2022-04-02 23:57:45 -07:00
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native balance for a token , is signed
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenBalance ( bank : Bank ) : I80F48 {
2022-09-29 06:51:09 -07:00
const tp = this . getToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . balance ( bank ) : ZERO_I80F48 ( ) ;
2022-08-18 07:19:37 -07:00
}
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native deposits for a token , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenDeposits ( bank : Bank ) : I80F48 {
2022-09-29 06:51:09 -07:00
const tp = this . getToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . deposits ( bank ) : ZERO_I80F48 ( ) ;
2022-07-04 03:29:35 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native borrows for a token , 0 if position has deposits
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenBorrows ( bank : Bank ) : I80F48 {
2022-09-29 06:51:09 -07:00
const tp = this . getToken ( bank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
return tp ? tp . borrows ( bank ) : ZERO_I80F48 ( ) ;
2022-07-04 03:29:35 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI balance for a token , is signed
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenBalanceUi ( bank : Bank ) : number {
2022-09-29 06:51:09 -07:00
const tp = this . getToken ( bank . tokenIndex ) ;
2022-08-23 04:47:08 -07:00
return tp ? tp . balanceUi ( bank ) : 0 ;
2022-04-02 23:57:45 -07:00
}
2022-05-11 04:33:01 -07:00
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI deposits for a token , 0 or more
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenDepositsUi ( bank : Bank ) : number {
2022-09-29 06:51:09 -07:00
const ta = this . getToken ( bank . tokenIndex ) ;
2022-08-18 07:19:37 -07:00
return ta ? ta . depositsUi ( bank ) : 0 ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
*
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI borrows for a token , 0 or less
2022-08-18 07:19:37 -07:00
* /
2022-10-11 00:39:57 -07:00
public getTokenBorrowsUi ( bank : Bank ) : number {
2022-09-29 06:51:09 -07:00
const ta = this . getToken ( bank . tokenIndex ) ;
2022-08-18 07:19:37 -07:00
return ta ? ta . borrowsUi ( bank ) : 0 ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* Health , see health . rs or https : //docs.mango.markets/mango-markets/health-overview
* @param healthType
* @returns raw health number , in native quote
* /
2022-10-11 00:39:57 -07:00
public getHealth ( group : Group , healthType : HealthType ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . health ( healthType ) ;
2022-07-12 03:05:19 -07:00
}
2022-11-21 11:36:13 -08:00
public getPerpSettleHealth ( group : Group ) : I80F48 {
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . perpSettleHealth ( ) ;
}
2022-08-18 07:19:37 -07:00
/ * *
* Health ratio , which is computed so ` 100 * (assets-liabs)/liabs `
* Note : health ratio is technically ∞ if liabs are 0
* @param healthType
* @returns health ratio , in percentage form
* /
2022-10-11 00:39:57 -07:00
public getHealthRatio ( group : Group , healthType : HealthType ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . healthRatio ( healthType ) ;
2022-07-12 03:05:19 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* Health ratio
* @param healthType
* @returns health ratio , in percentage form , capped to 100
* /
2022-10-11 00:39:57 -07:00
public getHealthRatioUi ( group : Group , healthType : HealthType ) : number {
2022-09-29 06:51:09 -07:00
const ratio = this . getHealthRatio ( group , healthType ) . toNumber ( ) ;
2022-09-30 06:07:43 -07:00
return ratio > 100 ? 100 : Math.trunc ( ratio ) ;
2022-08-11 12:06:01 -07:00
}
2022-07-04 03:29:35 -07:00
/ * *
2022-09-30 04:33:21 -07:00
* Sum of all the assets i . e . token deposits , borrows , total assets in spot open orders , and perps positions .
2022-08-18 07:19:37 -07:00
* @returns equity , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-10-11 00:39:57 -07:00
public getEquity ( group : Group ) : I80F48 {
2022-09-29 06:51:09 -07:00
const tokensMap = new Map < number , I80F48 > ( ) ;
for ( const tp of this . tokensActive ( ) ) {
const bank = group . getFirstBankByTokenIndex ( tp . tokenIndex ) ;
tokensMap . set ( tp . tokenIndex , tp . balance ( bank ) . mul ( bank . price ) ) ;
2022-08-31 02:41:12 -07:00
}
2022-09-29 06:51:09 -07:00
for ( const sp of this . serum3Active ( ) ) {
const oo = this . getSerum3OoAccount ( sp . marketIndex ) ;
const baseBank = group . getFirstBankByTokenIndex ( sp . baseTokenIndex ) ;
tokensMap
. get ( baseBank . tokenIndex ) !
2022-09-30 06:07:43 -07:00
. iadd ( I80F48 . fromI64 ( oo . baseTokenTotal ) . mul ( baseBank . price ) ) ;
2022-09-29 06:51:09 -07:00
const quoteBank = group . getFirstBankByTokenIndex ( sp . quoteTokenIndex ) ;
// NOTE: referrerRebatesAccrued is not declared on oo class, but the layout
// is aware of it
tokensMap
. get ( baseBank . tokenIndex ) !
. iadd (
2022-09-30 06:07:43 -07:00
I80F48 . fromI64 (
oo . quoteTokenTotal . add ( ( oo as any ) . referrerRebatesAccrued ) ,
2022-09-29 06:51:09 -07:00
) . mul ( quoteBank . price ) ,
) ;
2022-08-31 02:41:12 -07:00
}
2022-09-29 06:51:09 -07:00
const tokenEquity = Array . from ( tokensMap . values ( ) ) . reduce (
( a , b ) = > a . add ( b ) ,
ZERO_I80F48 ( ) ,
) ;
const perpEquity = this . perpActive ( ) . reduce (
( a , b ) = >
a . add ( b . getEquity ( group . getPerpMarketByMarketIndex ( b . marketIndex ) ) ) ,
ZERO_I80F48 ( ) ,
) ;
return tokenEquity . add ( perpEquity ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
* The amount of native quote you could withdraw against your existing assets .
2022-08-18 07:19:37 -07:00
* @returns collateral value , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-10-11 00:39:57 -07:00
public getCollateralValue ( group : Group ) : I80F48 {
2022-09-29 06:51:09 -07:00
return this . getHealth ( group , HealthType . init ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
2022-08-11 08:44:12 -07:00
* Sum of all positive assets .
2022-08-18 07:19:37 -07:00
* @returns assets , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-12-30 12:26:53 -08:00
public getAssetsValue ( group : Group , healthType? : HealthType ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . assets ( healthType ) ;
2022-07-04 03:29:35 -07:00
}
/ * *
2022-08-11 08:44:12 -07:00
* Sum of all negative assets .
2022-08-18 07:19:37 -07:00
* @returns liabs , in native quote
2022-07-04 03:29:35 -07:00
* /
2022-12-30 12:26:53 -08:00
public getLiabsValue ( group : Group , healthType? : HealthType ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . liabs ( healthType ) ;
2022-07-04 03:29:35 -07:00
}
2022-09-23 11:39:51 -07:00
/ * *
* @returns Overall PNL , in native quote
* PNL is defined here as spot value + serum3 open orders value + perp value - net deposits value ( evaluated at native quote price at the time of the deposit / withdraw )
* spot value + serum3 open orders value + perp value is returned by getEquity ( open orders values are added to spot token values implicitly )
* /
2022-10-11 00:39:57 -07:00
public getPnl ( group : Group ) : I80F48 {
2022-09-29 06:51:09 -07:00
return this . getEquity ( group ) ? . add (
2022-09-27 08:33:51 -07:00
I80F48 . fromI64 ( this . netDeposits ) . mul ( I80F48 . fromNumber ( - 1 ) ) ,
) ;
2022-07-04 03:29:35 -07:00
}
2022-11-21 10:59:26 -08:00
/ * *
* @returns token cumulative interest , in native token units . Sum of deposit and borrow interest .
* Caveat : This will only return cumulative interest since the tokenPosition was last opened .
* If the tokenPosition was closed and reopened multiple times it is necessary to add this result to
* cumulative interest at each of the prior tokenPosition closings ( from mango API ) to get the all time
* cumulative interest .
* /
getCumulativeInterest ( bank : Bank ) : number {
const token = this . getToken ( bank . tokenIndex ) ;
if ( token === undefined ) {
// tokenPosition does not exist on mangoAccount so no cumulative interest
return 0 ;
} else {
if ( token . indexedPosition . isPos ( ) ) {
const interest = bank . depositIndex
. sub ( token . previousIndex )
. mul ( token . indexedPosition )
. toNumber ( ) ;
return (
interest +
token . cumulativeDepositInterest +
token . cumulativeBorrowInterest
) ;
} else {
const interest = bank . borrowIndex
. sub ( token . previousIndex )
. mul ( token . indexedPosition )
. toNumber ( ) ;
return (
interest +
token . cumulativeDepositInterest +
token . cumulativeBorrowInterest
) ;
}
}
}
2022-07-04 03:29:35 -07:00
/ * *
2022-08-23 04:47:08 -07:00
* The amount of given native token you can withdraw including borrows , considering all existing assets as collateral .
* @returns amount of given native token you can borrow , considering all existing assets as collateral , in native token
2022-12-08 01:16:06 -08:00
*
* TODO : take into account net_borrow_limit and min_vault_to_deposits_ratio
2022-07-04 03:29:35 -07:00
* /
2022-10-11 00:39:57 -07:00
public getMaxWithdrawWithBorrowForToken (
group : Group ,
mintPk : PublicKey ,
) : I80F48 {
2022-08-23 04:47:08 -07:00
const tokenBank : Bank = group . getFirstBankByMint ( mintPk ) ;
2022-09-29 06:51:09 -07:00
const initHealth = this . getHealth ( group , HealthType . init ) ;
2022-08-31 02:41:12 -07:00
2022-08-23 04:47:08 -07:00
// Case 1:
// Cannot withdraw if init health is below 0
2022-09-01 00:48:23 -07:00
if ( initHealth . lte ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-23 04:47:08 -07:00
}
// Deposits need special treatment since they would neither count towards liabilities
// nor would be charged loanOriginationFeeRate when withdrawn
2022-09-29 06:51:09 -07:00
const tp = this . getToken ( tokenBank . tokenIndex ) ;
2022-09-01 00:48:23 -07:00
const existingTokenDeposits = tp ? tp . deposits ( tokenBank ) : ZERO_I80F48 ( ) ;
2022-09-13 23:14:46 -07:00
let existingPositionHealthContrib = ZERO_I80F48 ( ) ;
2022-09-01 00:48:23 -07:00
if ( existingTokenDeposits . gt ( ZERO_I80F48 ( ) ) ) {
2022-09-13 23:14:46 -07:00
existingPositionHealthContrib = existingTokenDeposits
. mul ( tokenBank . price )
2022-09-01 00:48:23 -07:00
. imul ( tokenBank . initAssetWeight ) ;
2022-08-23 04:47:08 -07:00
}
// Case 2: token deposits have higher contribution than initHealth,
// can withdraw without borrowing until initHealth reaches 0
if ( existingPositionHealthContrib . gt ( initHealth ) ) {
const withdrawAbleExistingPositionHealthContrib = initHealth ;
return withdrawAbleExistingPositionHealthContrib
. div ( tokenBank . initAssetWeight )
. div ( tokenBank . price ) ;
}
// Case 3: withdraw = withdraw existing deposits + borrows until initHealth reaches 0
const initHealthWithoutExistingPosition = initHealth . sub (
existingPositionHealthContrib ,
) ;
2022-12-13 10:41:19 -08:00
let maxBorrowNative = initHealthWithoutExistingPosition
2022-08-23 04:47:08 -07:00
. div ( tokenBank . initLiabWeight )
. div ( tokenBank . price ) ;
2022-12-13 10:41:19 -08:00
// Cap maxBorrow to maintain minVaultToDepositsRatio on the bank
const vaultAmount = group . vaultAmountsMap . get ( tokenBank . vault . toBase58 ( ) ) ;
if ( ! vaultAmount ) {
throw new Error (
` No vault amount found for ${ tokenBank . name } vault ${ tokenBank . vault } ! ` ,
) ;
}
const vaultAmountAfterWithdrawingDeposits = I80F48 . fromU64 ( vaultAmount ) . sub (
existingTokenDeposits ,
) ;
const expectedVaultMinAmount = tokenBank
. nativeDeposits ( )
. mul ( I80F48 . fromNumber ( tokenBank . minVaultToDepositsRatio ) ) ;
if ( vaultAmountAfterWithdrawingDeposits . gt ( expectedVaultMinAmount ) ) {
maxBorrowNative = maxBorrowNative . min (
vaultAmountAfterWithdrawingDeposits . sub ( expectedVaultMinAmount ) ,
) ;
}
2022-08-23 04:47:08 -07:00
const maxBorrowNativeWithoutFees = maxBorrowNative . div (
2022-09-01 00:48:23 -07:00
ONE_I80F48 ( ) . add ( tokenBank . loanOriginationFeeRate ) ,
2022-08-23 04:47:08 -07:00
) ;
2022-12-13 10:41:19 -08:00
2022-08-23 04:47:08 -07:00
return maxBorrowNativeWithoutFees . add ( existingTokenDeposits ) ;
}
2022-10-11 00:39:57 -07:00
public getMaxWithdrawWithBorrowForTokenUi (
group : Group ,
mintPk : PublicKey ,
) : number {
2022-08-31 02:41:12 -07:00
const maxWithdrawWithBorrow = this . getMaxWithdrawWithBorrowForToken (
group ,
mintPk ,
2022-07-14 05:29:44 -07:00
) ;
2022-10-07 10:48:05 -07:00
return toUiDecimals ( maxWithdrawWithBorrow , group . getMintDecimals ( mintPk ) ) ;
2022-07-04 03:29:35 -07:00
}
2022-08-23 07:21:05 -07:00
/ * *
* The max amount of given source ui token you can swap to a target token .
* @returns max amount of given source ui token you can swap to a target token , in ui token
* /
getMaxSourceUiForTokenSwap (
group : Group ,
sourceMintPk : PublicKey ,
targetMintPk : PublicKey ,
2022-12-16 07:33:37 -08:00
slippageAndFeesFactor = 1 ,
2022-10-07 10:48:05 -07:00
) : number {
2022-09-23 02:43:26 -07:00
if ( sourceMintPk . equals ( targetMintPk ) ) {
return 0 ;
}
2022-12-02 06:48:43 -08:00
const s = group . getFirstBankByMint ( sourceMintPk ) ;
const t = group . getFirstBankByMint ( targetMintPk ) ;
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2022-12-16 07:33:37 -08:00
const maxSource = hc . getMaxSwapSource (
2022-12-02 06:48:43 -08:00
s ,
t ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber (
slippageAndFeesFactor *
( ( s . uiPrice / t . uiPrice ) *
Math . pow ( 10 , t . mintDecimals - s . mintDecimals ) ) ,
) ,
2022-09-23 02:43:26 -07:00
) ;
maxSource . idiv (
ONE_I80F48 ( ) . add (
group . getFirstBankByMint ( sourceMintPk ) . loanOriginationFeeRate ,
) ,
2022-08-23 07:21:05 -07:00
) ;
2022-10-07 10:48:05 -07:00
return toUiDecimals ( maxSource , group . getMintDecimals ( sourceMintPk ) ) ;
2022-08-23 07:21:05 -07:00
}
2022-08-23 04:47:08 -07:00
/ * *
* Simulates new health ratio after applying tokenChanges to the token positions .
* Note : token changes are expected in ui amounts
*
* e . g . useful to simulate health after a potential swap .
* Note : health ratio is technically ∞ if liabs are 0
* @returns health ratio , in percentage form
* /
2022-10-11 00:39:57 -07:00
public simHealthRatioWithTokenPositionUiChanges (
2022-08-23 04:47:08 -07:00
group : Group ,
uiTokenChanges : {
uiTokenAmount : number ;
mintPk : PublicKey ;
} [ ] ,
healthType : HealthType = HealthType . init ,
2022-10-07 10:48:05 -07:00
) : number {
2022-08-23 04:47:08 -07:00
const nativeTokenChanges = uiTokenChanges . map ( ( tokenChange ) = > {
return {
2022-09-30 06:07:43 -07:00
nativeTokenAmount : toNativeI80F48 (
2022-08-23 04:47:08 -07:00
tokenChange . uiTokenAmount ,
group . getMintDecimals ( tokenChange . mintPk ) ,
) ,
mintPk : tokenChange.mintPk ,
} ;
} ) ;
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc
2022-08-23 07:21:05 -07:00
. simHealthRatioWithTokenPositionChanges (
group ,
nativeTokenChanges ,
healthType ,
)
. toNumber ( ) ;
2022-07-12 03:05:19 -07:00
}
2022-09-23 20:14:12 -07:00
public async loadSerum3OpenOrdersAccounts (
client : MangoClient ,
2022-09-25 17:27:44 -07:00
) : Promise < OpenOrders [ ] > {
2022-09-25 17:26:02 -07:00
const response =
2022-09-23 20:14:12 -07:00
await client . program . provider . connection . getMultipleAccountsInfo (
2022-10-31 12:08:26 -07:00
this . serum3Active ( ) . map ( ( s ) = > s . openOrders ) ,
2022-09-23 20:14:12 -07:00
) ;
2022-09-25 17:26:02 -07:00
const accounts = response . filter ( ( a ) : a is AccountInfo < Buffer > = >
Boolean ( a ) ,
) ;
2022-09-23 20:14:12 -07:00
return accounts . map ( ( acc , index ) = > {
2022-09-25 17:26:02 -07:00
return OpenOrders . fromAccountInfo (
this . serum3 [ index ] . openOrders ,
acc ,
2022-12-08 01:16:06 -08:00
OPENBOOK_PROGRAM_ID [ client . cluster ] ,
2022-09-25 17:26:02 -07:00
) ;
2022-09-23 20:14:12 -07:00
} ) ;
}
2022-08-31 02:36:44 -07:00
public async loadSerum3OpenOrdersForMarket (
client : MangoClient ,
group : Group ,
externalMarketPk : PublicKey ,
) : Promise < Order [ ] > {
2022-09-29 06:51:09 -07:00
const serum3Market =
group . getSerum3MarketByExternalMarket ( externalMarketPk ) ;
2022-08-31 02:36:44 -07:00
const serum3OO = this . serum3Active ( ) . find (
( s ) = > s . marketIndex === serum3Market . marketIndex ,
) ;
if ( ! serum3OO ) {
throw new Error ( ` No open orders account found for ${ externalMarketPk } ` ) ;
}
2022-09-29 06:51:09 -07:00
const serum3MarketExternal = group . serum3ExternalMarketsMap . get (
2022-08-31 02:36:44 -07:00
externalMarketPk . toBase58 ( ) ,
) ! ;
const [ bidsInfo , asksInfo ] =
await client . program . provider . connection . getMultipleAccountsInfo ( [
serum3MarketExternal . bidsAddress ,
serum3MarketExternal . asksAddress ,
] ) ;
2022-09-29 06:51:09 -07:00
if ( ! bidsInfo ) {
2022-08-31 02:55:54 -07:00
throw new Error (
2022-09-29 06:51:09 -07:00
` Undefined bidsInfo for serum3Market with externalMarket ${ externalMarketPk . toString ( ) ! } ` ,
) ;
}
if ( ! asksInfo ) {
2022-08-31 02:55:54 -07:00
throw new Error (
2022-09-29 06:51:09 -07:00
` Undefined asksInfo for serum3Market with externalMarket ${ externalMarketPk . toString ( ) ! } ` ,
2022-08-31 02:55:54 -07:00
) ;
}
2022-08-31 02:36:44 -07:00
const bids = Orderbook . decode ( serum3MarketExternal , bidsInfo . data ) ;
const asks = Orderbook . decode ( serum3MarketExternal , asksInfo . data ) ;
return [ . . . bids , . . . asks ] . filter ( ( o ) = >
o . openOrdersAddress . equals ( serum3OO . openOrders ) ,
) ;
}
2022-07-04 03:29:35 -07:00
/ * *
2022-12-02 06:48:43 -08:00
* TODO REWORK , know to break in binary search , also make work for limit orders
*
2022-08-31 02:36:44 -07:00
* @param group
2022-09-23 02:43:26 -07:00
* @param externalMarketPk
2022-10-07 04:52:04 -07:00
* @returns maximum ui quote which can be traded at oracle price for base token given current health
2022-07-04 03:29:35 -07:00
* /
2022-08-31 02:36:44 -07:00
public getMaxQuoteForSerum3BidUi (
group : Group ,
externalMarketPk : PublicKey ,
) : number {
2022-09-29 06:51:09 -07:00
const serum3Market =
group . getSerum3MarketByExternalMarket ( externalMarketPk ) ;
const baseBank = group . getFirstBankByTokenIndex (
serum3Market . baseTokenIndex ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-29 06:51:09 -07:00
const quoteBank = group . getFirstBankByTokenIndex (
serum3Market . quoteTokenIndex ,
) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
let nativeAmount = hc . getMaxSerum3OrderForHealthRatio (
baseBank ,
quoteBank ,
serum3Market ,
Serum3Side . bid ,
I80F48 . fromNumber ( 2 ) ,
) ;
// If its a bid then the reserved fund and potential loan is in base
// also keep some buffer for fees, use taker fees for worst case simulation.
nativeAmount = nativeAmount
. div ( quoteBank . price )
. div ( ONE_I80F48 ( ) . add ( baseBank . loanOriginationFeeRate ) )
2022-12-21 01:21:24 -08:00
. div ( ONE_I80F48 ( ) . add ( I80F48 . fromNumber ( serum3Market . getFeeRates ( true ) ) ) ) ;
2022-08-31 02:36:44 -07:00
return toUiDecimals (
nativeAmount ,
group . getFirstBankByTokenIndex ( serum3Market . quoteTokenIndex ) . mintDecimals ,
) ;
}
/ * *
2022-12-02 06:48:43 -08:00
* TODO REWORK , know to break in binary search , also make work for limit orders
2022-08-31 02:36:44 -07:00
* @param group
2022-09-23 02:43:26 -07:00
* @param externalMarketPk
2022-10-07 04:52:04 -07:00
* @returns maximum ui base which can be traded at oracle price for quote token given current health
2022-08-31 02:36:44 -07:00
* /
public getMaxBaseForSerum3AskUi (
group : Group ,
externalMarketPk : PublicKey ,
) : number {
2022-09-29 06:51:09 -07:00
const serum3Market =
group . getSerum3MarketByExternalMarket ( externalMarketPk ) ;
const baseBank = group . getFirstBankByTokenIndex (
serum3Market . baseTokenIndex ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-29 06:51:09 -07:00
const quoteBank = group . getFirstBankByTokenIndex (
serum3Market . quoteTokenIndex ,
) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
let nativeAmount = hc . getMaxSerum3OrderForHealthRatio (
baseBank ,
quoteBank ,
serum3Market ,
Serum3Side . ask ,
I80F48 . fromNumber ( 2 ) ,
) ;
// If its a ask then the reserved fund and potential loan is in base
// also keep some buffer for fees, use taker fees for worst case simulation.
nativeAmount = nativeAmount
2022-12-19 09:30:26 -08:00
. div ( baseBank . price )
2022-09-29 06:51:09 -07:00
. div ( ONE_I80F48 ( ) . add ( baseBank . loanOriginationFeeRate ) )
2022-12-21 01:21:24 -08:00
. div ( ONE_I80F48 ( ) . add ( I80F48 . fromNumber ( serum3Market . getFeeRates ( true ) ) ) ) ;
2022-08-31 02:36:44 -07:00
return toUiDecimals (
nativeAmount ,
group . getFirstBankByTokenIndex ( serum3Market . baseTokenIndex ) . mintDecimals ,
) ;
}
/ * *
*
* @param group
2022-09-23 02:43:26 -07:00
* @param uiQuoteAmount
* @param externalMarketPk
2022-08-31 02:36:44 -07:00
* @param healthType
2022-09-23 02:43:26 -07:00
* @returns health ratio after a bid with uiQuoteAmount is placed
2022-08-31 02:36:44 -07:00
* /
2022-09-23 02:43:26 -07:00
public simHealthRatioWithSerum3BidUiChanges (
2022-08-31 02:36:44 -07:00
group : Group ,
2022-09-23 02:43:26 -07:00
uiQuoteAmount : number ,
externalMarketPk : PublicKey ,
2022-08-31 02:36:44 -07:00
healthType : HealthType = HealthType . init ,
2022-09-23 02:43:26 -07:00
) : number {
2022-09-29 06:51:09 -07:00
const serum3Market =
group . getSerum3MarketByExternalMarket ( externalMarketPk ) ;
const baseBank = group . getFirstBankByTokenIndex (
serum3Market . baseTokenIndex ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-29 06:51:09 -07:00
const quoteBank = group . getFirstBankByTokenIndex (
serum3Market . quoteTokenIndex ,
) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc
2022-09-23 02:43:26 -07:00
. simHealthRatioWithSerum3BidChanges (
2022-09-29 06:51:09 -07:00
baseBank ,
quoteBank ,
2022-09-30 06:07:43 -07:00
toNativeI80F48 (
2022-09-23 02:43:26 -07:00
uiQuoteAmount ,
group . getFirstBankByTokenIndex ( serum3Market . quoteTokenIndex )
. mintDecimals ,
) ,
serum3Market ,
healthType ,
)
. toNumber ( ) ;
2022-08-31 02:36:44 -07:00
}
2022-09-23 02:43:26 -07:00
/ * *
*
* @param group
* @param uiBaseAmount
* @param externalMarketPk
* @param healthType
* @returns health ratio after an ask with uiBaseAmount is placed
* /
public simHealthRatioWithSerum3AskUiChanges (
2022-08-31 02:36:44 -07:00
group : Group ,
2022-09-23 02:43:26 -07:00
uiBaseAmount : number ,
2022-08-31 02:36:44 -07:00
externalMarketPk : PublicKey ,
healthType : HealthType = HealthType . init ,
) : number {
2022-09-29 06:51:09 -07:00
const serum3Market =
group . getSerum3MarketByExternalMarket ( externalMarketPk ) ;
const baseBank = group . getFirstBankByTokenIndex (
serum3Market . baseTokenIndex ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-29 06:51:09 -07:00
const quoteBank = group . getFirstBankByTokenIndex (
serum3Market . quoteTokenIndex ,
) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc
2022-09-23 02:43:26 -07:00
. simHealthRatioWithSerum3AskChanges (
2022-09-29 06:51:09 -07:00
baseBank ,
quoteBank ,
2022-09-30 06:07:43 -07:00
toNativeI80F48 (
2022-09-23 02:43:26 -07:00
uiBaseAmount ,
group . getFirstBankByTokenIndex ( serum3Market . baseTokenIndex )
. mintDecimals ,
) ,
serum3Market ,
healthType ,
)
. toNumber ( ) ;
2022-08-31 02:36:44 -07:00
}
2022-11-04 08:07:26 -07:00
// TODO: don't send a settle instruction if there's nothing to settle
public async serum3SettleFundsForAllMarkets (
client : MangoClient ,
group : Group ,
) : Promise < TransactionSignature [ ] > {
// Future: collect ixs, batch them, and send them in fewer txs
return await Promise . all (
this . serum3Active ( ) . map ( ( s ) = > {
const serum3Market = group . getSerum3MarketByMarketIndex ( s . marketIndex ) ;
return client . serum3SettleFunds (
group ,
this ,
serum3Market . serumMarketExternal ,
) ;
} ) ,
) ;
}
// TODO: cancel until all are cancelled
public async serum3CancelAllOrdersForAllMarkets (
client : MangoClient ,
group : Group ,
) : Promise < TransactionSignature [ ] > {
// Future: collect ixs, batch them, and send them in in fewer txs
return await Promise . all (
this . serum3Active ( ) . map ( ( s ) = > {
const serum3Market = group . getSerum3MarketByMarketIndex ( s . marketIndex ) ;
return client . serum3CancelAllOrders (
group ,
this ,
serum3Market . serumMarketExternal ,
) ;
} ) ,
) ;
}
2022-08-31 02:36:44 -07:00
/ * *
2022-12-02 06:48:43 -08:00
* TODO : also think about limit orders
2022-08-31 02:36:44 -07:00
*
2022-12-02 06:48:43 -08:00
* The max ui quote you can place a market / ioc bid on the market ,
* price is the ui price at which you think the order would materialiase .
2022-08-31 02:36:44 -07:00
* @param group
2022-09-23 02:43:26 -07:00
* @param perpMarketName
2022-10-07 04:52:04 -07:00
* @returns maximum ui quote which can be traded at oracle price for quote token given current health
2022-08-31 02:36:44 -07:00
* /
2022-09-23 02:43:26 -07:00
public getMaxQuoteForPerpBidUi (
2022-08-31 02:36:44 -07:00
group : Group ,
2022-09-29 06:51:09 -07:00
perpMarketIndex : PerpMarketIndex ,
2022-09-23 02:43:26 -07:00
) : number {
2022-09-29 06:51:09 -07:00
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
const baseLots = hc . getMaxPerpForHealthRatio (
2022-09-23 02:43:26 -07:00
perpMarket ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber ( perpMarket . uiPrice ) ,
2022-09-23 02:43:26 -07:00
PerpOrderSide . bid ,
2022-09-29 06:51:09 -07:00
I80F48 . fromNumber ( 2 ) ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-30 06:07:43 -07:00
const nativeBase = baseLots . mul ( I80F48 . fromI64 ( perpMarket . baseLotSize ) ) ;
2022-09-29 06:51:09 -07:00
const nativeQuote = nativeBase . mul ( perpMarket . price ) ;
2022-09-30 06:07:43 -07:00
return toUiDecimalsForQuote ( nativeQuote ) ;
2022-08-31 02:36:44 -07:00
}
2022-09-23 02:43:26 -07:00
/ * *
2022-12-02 06:48:43 -08:00
* TODO : also think about limit orders
2022-09-23 02:43:26 -07:00
*
2022-12-02 06:48:43 -08:00
* The max ui base you can place a market / ioc ask on the market ,
* price is the ui price at which you think the order would materialiase .
2022-09-23 02:43:26 -07:00
* @param group
* @param perpMarketName
* @param uiPrice ui price at which ask would be placed at
* @returns max ui base ask
* /
public getMaxBaseForPerpAskUi (
2022-08-31 02:36:44 -07:00
group : Group ,
2022-09-29 06:51:09 -07:00
perpMarketIndex : PerpMarketIndex ,
2022-08-31 02:36:44 -07:00
) : number {
2022-09-29 06:51:09 -07:00
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
const baseLots = hc . getMaxPerpForHealthRatio (
2022-09-23 02:43:26 -07:00
perpMarket ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber ( perpMarket . uiPrice ) ,
2022-09-23 02:43:26 -07:00
PerpOrderSide . ask ,
2022-09-29 06:51:09 -07:00
I80F48 . fromNumber ( 2 ) ,
2022-08-31 02:36:44 -07:00
) ;
2022-09-23 02:43:26 -07:00
return perpMarket . baseLotsToUi ( new BN ( baseLots . toString ( ) ) ) ;
2022-07-04 03:29:35 -07:00
}
2022-10-07 04:52:04 -07:00
public simHealthRatioWithPerpBidUiChanges (
group : Group ,
perpMarketIndex : PerpMarketIndex ,
size : number ,
) : number {
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
const pp = this . getPerpPosition ( perpMarket . perpMarketIndex ) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc
. simHealthRatioWithPerpOrderChanges (
perpMarket ,
pp
? pp
: PerpPosition . emptyFromPerpMarketIndex ( perpMarket . perpMarketIndex ) ,
PerpOrderSide . bid ,
2022-12-02 06:48:43 -08:00
perpMarket . uiBaseToLots ( size ) ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber ( perpMarket . uiPrice ) ,
2022-10-07 04:52:04 -07:00
HealthType . init ,
)
. toNumber ( ) ;
}
public simHealthRatioWithPerpAskUiChanges (
group : Group ,
perpMarketIndex : PerpMarketIndex ,
size : number ,
) : number {
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
const pp = this . getPerpPosition ( perpMarket . perpMarketIndex ) ;
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc
. simHealthRatioWithPerpOrderChanges (
perpMarket ,
pp
? pp
: PerpPosition . emptyFromPerpMarketIndex ( perpMarket . perpMarketIndex ) ,
PerpOrderSide . ask ,
2022-12-02 06:48:43 -08:00
perpMarket . uiBaseToLots ( size ) ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber ( perpMarket . uiPrice ) ,
2022-10-07 04:52:04 -07:00
HealthType . init ,
)
. toNumber ( ) ;
}
2022-09-20 03:57:01 -07:00
public async loadPerpOpenOrdersForMarket (
client : MangoClient ,
2022-08-31 02:41:12 -07:00
group : Group ,
2022-09-29 06:51:09 -07:00
perpMarketIndex : PerpMarketIndex ,
2022-09-20 03:57:01 -07:00
) : Promise < PerpOrder [ ] > {
2022-09-29 06:51:09 -07:00
const perpMarket = group . getPerpMarketByMarketIndex ( perpMarketIndex ) ;
2022-09-20 03:57:01 -07:00
const [ bids , asks ] = await Promise . all ( [
perpMarket . loadBids ( client ) ,
perpMarket . loadAsks ( client ) ,
] ) ;
2022-12-15 01:40:45 -08:00
2022-09-20 03:57:01 -07:00
return [ . . . Array . from ( bids . items ( ) ) , . . . Array . from ( asks . items ( ) ) ] . filter (
( order ) = > order . owner . equals ( this . publicKey ) ,
) ;
2022-07-04 03:09:33 -07:00
}
2022-12-15 00:41:03 -08:00
toString ( group? : Group , onlyTokens = false ) : string {
2022-07-04 03:29:35 -07:00
let res = 'MangoAccount' ;
res = res + '\n pk: ' + this . publicKey . toString ( ) ;
res = res + '\n name: ' + this . name ;
2022-12-14 06:50:10 -08:00
res = res + '\n accountNum: ' + this . accountNum ;
2022-08-26 01:08:45 -07:00
res = res + '\n owner: ' + this . owner ;
2022-07-05 10:31:47 -07:00
res = res + '\n delegate: ' + this . delegate ;
2022-07-04 03:09:33 -07:00
2022-09-20 03:57:01 -07:00
res =
res +
` \ n max token slots ${ this . tokens . length } , max serum3 slots ${ this . serum3 . length } , max perp slots ${ this . perps . length } , max perp oo slots ${ this . perpOpenOrders . length } ` ;
2022-07-04 03:09:33 -07:00
res =
this . tokensActive ( ) . length > 0
? res +
'\n tokens:' +
JSON . stringify (
2022-12-15 00:41:03 -08:00
this . tokens
. filter ( ( token , i ) = > token . isActive ( ) )
. map ( ( token , i ) = > token . toString ( group , i ) ) ,
2022-07-04 03:09:33 -07:00
null ,
4 ,
)
: res + '' ;
2022-12-15 00:41:03 -08:00
if ( onlyTokens ) {
return res ;
}
2022-07-04 03:09:33 -07:00
res =
this . serum3Active ( ) . length > 0
? res + '\n serum:' + JSON . stringify ( this . serum3Active ( ) , null , 4 )
: res + '' ;
res =
this . perpActive ( ) . length > 0
? res + '\n perps:' + JSON . stringify ( this . perpActive ( ) , null , 4 )
: res + '' ;
2022-09-20 03:57:01 -07:00
res =
this . perpOrdersActive ( ) . length > 0
? res +
'\n perps oo:' +
JSON . stringify ( this . perpOrdersActive ( ) , null , 4 )
: res + '' ;
2022-07-04 03:09:33 -07:00
return res ;
2022-05-11 04:33:01 -07:00
}
2022-04-02 23:57:45 -07:00
}
2022-06-22 02:21:02 -07:00
export class TokenPosition {
2022-08-04 10:42:41 -07:00
static TokenIndexUnset = 65535 ;
2022-09-29 06:51:09 -07:00
static from ( dto : TokenPositionDto ) : TokenPosition {
2022-06-22 02:21:02 -07:00
return new TokenPosition (
2022-06-23 06:36:08 -07:00
I80F48 . from ( dto . indexedPosition ) ,
2022-09-29 06:51:09 -07:00
dto . tokenIndex as TokenIndex ,
2022-04-02 23:57:45 -07:00
dto . inUseCount ,
2022-11-21 10:59:26 -08:00
I80F48 . from ( dto . previousIndex ) ,
dto . cumulativeDepositInterest ,
dto . cumulativeBorrowInterest ,
2022-04-02 23:57:45 -07:00
) ;
}
constructor (
2022-06-22 02:21:02 -07:00
public indexedPosition : I80F48 ,
2022-09-29 06:51:09 -07:00
public tokenIndex : TokenIndex ,
2022-04-02 23:57:45 -07:00
public inUseCount : number ,
2022-11-21 10:59:26 -08:00
public previousIndex : I80F48 ,
public cumulativeDepositInterest : number ,
public cumulativeBorrowInterest : number ,
2022-04-02 23:57:45 -07:00
) { }
2022-05-02 09:26:25 -07:00
2022-06-03 06:34:05 -07:00
public isActive ( ) : boolean {
2022-08-10 01:15:28 -07:00
return this . tokenIndex !== TokenPosition . TokenIndexUnset ;
2022-05-02 09:26:25 -07:00
}
2022-06-03 06:34:05 -07:00
2022-08-23 04:47:08 -07:00
/ * *
*
* @param bank
* @returns native balance
* /
public balance ( bank : Bank ) : I80F48 {
2022-06-22 02:21:02 -07:00
if ( this . indexedPosition . isPos ( ) ) {
return bank . depositIndex . mul ( this . indexedPosition ) ;
2022-06-03 06:34:05 -07:00
} else {
2022-06-22 02:21:02 -07:00
return bank . borrowIndex . mul ( this . indexedPosition ) ;
2022-06-03 06:34:05 -07:00
}
}
2022-08-18 07:19:37 -07:00
/ * *
2022-08-23 04:47:08 -07:00
*
2022-08-18 07:19:37 -07:00
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native deposits , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public deposits ( bank : Bank ) : I80F48 {
2022-09-01 00:48:23 -07:00
if ( this . indexedPosition && this . indexedPosition . lt ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-23 04:47:08 -07:00
}
return this . balance ( bank ) ;
2022-06-03 06:34:05 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
2022-08-23 04:47:08 -07:00
*
2022-08-18 07:19:37 -07:00
* @param bank
2022-08-23 04:47:08 -07:00
* @returns native borrows , 0 if position has deposits
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public borrows ( bank : Bank ) : I80F48 {
2022-09-01 00:48:23 -07:00
if ( this . indexedPosition && this . indexedPosition . gt ( ZERO_I80F48 ( ) ) ) {
return ZERO_I80F48 ( ) ;
2022-08-18 07:19:37 -07:00
}
2022-08-23 04:47:08 -07:00
return this . balance ( bank ) . abs ( ) ;
}
2022-08-18 07:19:37 -07:00
2022-08-23 04:47:08 -07:00
/ * *
* @param bank
* @returns UI balance , is signed
* /
public balanceUi ( bank : Bank ) : number {
2022-09-30 06:07:43 -07:00
return toUiDecimals ( this . balance ( bank ) , bank . mintDecimals ) ;
2022-06-29 12:55:39 -07:00
}
2022-08-18 07:19:37 -07:00
/ * *
* @param bank
2022-08-23 04:47:08 -07:00
* @returns UI deposits , 0 if position has borrows
2022-08-18 07:19:37 -07:00
* /
2022-08-23 04:47:08 -07:00
public depositsUi ( bank : Bank ) : number {
2022-09-30 06:07:43 -07:00
return toUiDecimals ( this . deposits ( bank ) , bank . mintDecimals ) ;
2022-08-23 04:47:08 -07:00
}
2022-08-18 07:19:37 -07:00
2022-08-23 04:47:08 -07:00
/ * *
* @param bank
* @returns UI borrows , 0 if position has deposits
* /
public borrowsUi ( bank : Bank ) : number {
2022-09-30 06:07:43 -07:00
return toUiDecimals ( this . borrows ( bank ) , bank . mintDecimals ) ;
2022-06-29 12:55:39 -07:00
}
2022-08-10 08:17:16 -07:00
public toString ( group? : Group , index? : number ) : string {
2022-08-04 10:42:41 -07:00
let extra = '' ;
2022-06-03 06:34:05 -07:00
if ( group ) {
2022-08-17 23:48:45 -07:00
const bank : Bank = group . getFirstBankByTokenIndex ( this . tokenIndex ) ;
2022-06-03 06:34:05 -07:00
if ( bank ) {
2022-08-23 04:47:08 -07:00
const native = this . balance ( bank ) ;
2022-06-03 06:34:05 -07:00
extra += ', native: ' + native . toNumber ( ) ;
2022-08-18 07:19:37 -07:00
extra += ', ui: ' + this . balanceUi ( bank ) ;
2022-06-03 06:34:05 -07:00
extra += ', tokenName: ' + bank . name ;
}
}
return (
2022-08-10 08:17:16 -07:00
( index !== undefined ? 'index: ' + index : '' ) +
', tokenIndex: ' +
2022-06-03 06:34:05 -07:00
this . tokenIndex +
', inUseCount: ' +
this . inUseCount +
', indexedValue: ' +
2022-06-22 02:21:02 -07:00
this . indexedPosition . toNumber ( ) +
2022-06-03 06:34:05 -07:00
extra
) ;
}
2022-04-02 23:57:45 -07:00
}
2022-06-23 07:02:35 -07:00
export class TokenPositionDto {
2022-04-02 23:57:45 -07:00
constructor (
2022-06-23 06:36:08 -07:00
public indexedPosition : I80F48Dto ,
2022-04-02 23:57:45 -07:00
public tokenIndex : number ,
public inUseCount : number ,
public reserved : number [ ] ,
2022-11-21 10:59:26 -08:00
public previousIndex : I80F48Dto ,
public cumulativeDepositInterest : number ,
public cumulativeBorrowInterest : number ,
2022-04-02 23:57:45 -07:00
) { }
}
2022-06-22 02:21:02 -07:00
export class Serum3Orders {
2022-04-03 11:08:56 -07:00
static Serum3MarketIndexUnset = 65535 ;
2022-08-10 01:15:28 -07:00
static from ( dto : Serum3PositionDto ) : Serum3Orders {
2022-06-22 02:21:02 -07:00
return new Serum3Orders (
2022-04-03 07:02:14 -07:00
dto . openOrders ,
2022-09-29 06:51:09 -07:00
dto . marketIndex as MarketIndex ,
dto . baseTokenIndex as TokenIndex ,
dto . quoteTokenIndex as TokenIndex ,
2022-04-03 07:02:14 -07:00
) ;
}
constructor (
public openOrders : PublicKey ,
2022-09-29 06:51:09 -07:00
public marketIndex : MarketIndex ,
public baseTokenIndex : TokenIndex ,
public quoteTokenIndex : TokenIndex ,
2022-04-03 07:02:14 -07:00
) { }
2022-07-04 03:09:33 -07:00
public isActive ( ) : boolean {
return this . marketIndex !== Serum3Orders . Serum3MarketIndexUnset ;
}
2022-04-03 07:02:14 -07:00
}
2022-06-23 07:02:35 -07:00
export class Serum3PositionDto {
2022-04-03 07:02:14 -07:00
constructor (
public openOrders : PublicKey ,
public marketIndex : number ,
2022-12-06 05:05:12 -08:00
public baseBorrowsWithoutFee : BN ,
public quoteBorrowsWithoutFee : BN ,
2022-04-03 07:02:14 -07:00
public baseTokenIndex : number ,
public quoteTokenIndex : number ,
public reserved : number [ ] ,
) { }
}
2022-05-11 04:33:01 -07:00
2022-08-18 04:45:31 -07:00
export class PerpPosition {
2022-05-11 04:33:01 -07:00
static PerpMarketIndexUnset = 65535 ;
2022-09-29 06:51:09 -07:00
static from ( dto : PerpPositionDto ) : PerpPosition {
2022-08-18 04:45:31 -07:00
return new PerpPosition (
2022-09-29 06:51:09 -07:00
dto . marketIndex as PerpMarketIndex ,
2022-12-06 05:05:12 -08:00
dto . settlePnlLimitWindow ,
dto . settlePnlLimitSettledInCurrentWindowNative ,
2022-09-30 06:07:43 -07:00
dto . basePositionLots ,
2022-09-29 06:51:09 -07:00
I80F48 . from ( dto . quotePositionNative ) ,
2022-11-17 23:58:56 -08:00
dto . quoteRunningNative ,
2022-09-29 06:51:09 -07:00
I80F48 . from ( dto . longSettledFunding ) ,
I80F48 . from ( dto . shortSettledFunding ) ,
2022-11-17 23:58:56 -08:00
dto . bidsBaseLots ,
dto . asksBaseLots ,
2022-11-02 05:13:29 -07:00
dto . takerBaseLots ,
dto . takerQuoteLots ,
dto . cumulativeLongFunding ,
dto . cumulativeShortFunding ,
dto . makerVolume ,
dto . takerVolume ,
dto . perpSpotTransfers ,
2022-12-06 05:05:12 -08:00
dto . avgEntryPricePerBaseLot ,
I80F48 . from ( dto . realizedPnlNative ) ,
2022-05-11 04:33:01 -07:00
) ;
}
2022-10-07 04:52:04 -07:00
static emptyFromPerpMarketIndex (
perpMarketIndex : PerpMarketIndex ,
) : PerpPosition {
return new PerpPosition (
perpMarketIndex ,
2022-12-06 05:05:12 -08:00
0 ,
2022-10-07 04:52:04 -07:00
new BN ( 0 ) ,
new BN ( 0 ) ,
2022-12-06 05:05:12 -08:00
ZERO_I80F48 ( ) ,
2022-10-07 04:52:04 -07:00
new BN ( 0 ) ,
ZERO_I80F48 ( ) ,
ZERO_I80F48 ( ) ,
2022-11-02 05:13:29 -07:00
new BN ( 0 ) ,
new BN ( 0 ) ,
new BN ( 0 ) ,
new BN ( 0 ) ,
0 ,
0 ,
new BN ( 0 ) ,
new BN ( 0 ) ,
new BN ( 0 ) ,
2022-12-06 05:05:12 -08:00
0 ,
ZERO_I80F48 ( ) ,
2022-10-07 04:52:04 -07:00
) ;
}
2022-05-11 04:33:01 -07:00
constructor (
2022-09-29 06:51:09 -07:00
public marketIndex : PerpMarketIndex ,
2022-12-06 05:05:12 -08:00
public settlePnlLimitWindow : number ,
public settlePnlLimitSettledInCurrentWindowNative : BN ,
2022-09-30 06:07:43 -07:00
public basePositionLots : BN ,
2022-09-29 06:51:09 -07:00
public quotePositionNative : I80F48 ,
2022-11-02 05:13:29 -07:00
public quoteRunningNative : BN ,
public longSettledFunding : I80F48 ,
public shortSettledFunding : I80F48 ,
2022-09-30 06:07:43 -07:00
public bidsBaseLots : BN ,
public asksBaseLots : BN ,
public takerBaseLots : BN ,
public takerQuoteLots : BN ,
2022-11-02 05:13:29 -07:00
public cumulativeLongFunding : number ,
public cumulativeShortFunding : number ,
public makerVolume : BN ,
public takerVolume : BN ,
public perpSpotTransfers : BN ,
2022-12-06 05:05:12 -08:00
public avgEntryPricePerBaseLot : number ,
public realizedPnlNative : I80F48 ,
2022-05-11 04:33:01 -07:00
) { }
2022-07-04 03:09:33 -07:00
isActive ( ) : boolean {
2022-12-08 12:53:07 -08:00
return this . marketIndex !== PerpPosition . PerpMarketIndexUnset ;
2022-07-04 03:09:33 -07:00
}
2022-09-29 06:51:09 -07:00
2022-10-11 00:39:57 -07:00
public getBasePositionUi (
perpMarket : PerpMarket ,
useEventQueue? : boolean ,
) : number {
return perpMarket . baseLotsToUi (
useEventQueue
? this . basePositionLots . add ( this . takerBaseLots )
: this . basePositionLots ,
) ;
2022-10-07 04:52:04 -07:00
}
public getUnsettledFunding ( perpMarket : PerpMarket ) : I80F48 {
2022-09-30 06:07:43 -07:00
if ( this . basePositionLots . gt ( new BN ( 0 ) ) ) {
2022-09-29 06:51:09 -07:00
return perpMarket . longFunding
. sub ( this . longSettledFunding )
2022-09-30 06:07:43 -07:00
. mul ( I80F48 . fromI64 ( this . basePositionLots ) ) ;
} else if ( this . basePositionLots . lt ( new BN ( 0 ) ) ) {
2022-09-29 06:51:09 -07:00
return perpMarket . shortFunding
. sub ( this . shortSettledFunding )
2022-09-30 06:07:43 -07:00
. mul ( I80F48 . fromI64 ( this . basePositionLots ) ) ;
2022-09-29 06:51:09 -07:00
}
return ZERO_I80F48 ( ) ;
}
2022-12-08 11:12:57 -08:00
public getEquityUi ( group : Group , perpMarket : PerpMarket ) : number {
return toUiDecimals (
this . getEquity ( perpMarket ) ,
group . getMintDecimalsByTokenIndex ( perpMarket . settleTokenIndex ) ,
) ;
}
2022-09-29 06:51:09 -07:00
public getEquity ( perpMarket : PerpMarket ) : I80F48 {
2022-09-30 06:07:43 -07:00
const lotsToQuote = I80F48 . fromI64 ( perpMarket . baseLotSize ) . mul (
perpMarket . price ,
) ;
2022-09-29 06:51:09 -07:00
2022-09-30 06:07:43 -07:00
const baseLots = I80F48 . fromI64 (
this . basePositionLots . add ( this . takerBaseLots ) ,
2022-09-29 06:51:09 -07:00
) ;
2022-10-07 04:52:04 -07:00
const unsettledFunding = this . getUnsettledFunding ( perpMarket ) ;
2022-09-30 06:07:43 -07:00
const takerQuote = I80F48 . fromI64 (
new BN ( this . takerQuoteLots ) . mul ( perpMarket . quoteLotSize ) ,
2022-09-29 06:51:09 -07:00
) ;
2022-09-30 06:07:43 -07:00
const quoteCurrent = this . quotePositionNative
2022-09-29 06:51:09 -07:00
. sub ( unsettledFunding )
. add ( takerQuote ) ;
return baseLots . mul ( lotsToQuote ) . add ( quoteCurrent ) ;
}
public hasOpenOrders ( ) : boolean {
2022-09-30 06:07:43 -07:00
const zero = new BN ( 0 ) ;
2022-09-29 06:51:09 -07:00
return (
2022-09-30 06:07:43 -07:00
! this . asksBaseLots . eq ( zero ) ||
! this . bidsBaseLots . eq ( zero ) ||
! this . takerBaseLots . eq ( zero ) ||
! this . takerQuoteLots . eq ( zero )
2022-09-29 06:51:09 -07:00
) ;
}
2022-11-17 23:58:56 -08:00
2022-12-08 01:16:06 -08:00
public getAverageEntryPriceUi ( perpMarket : PerpMarket ) : number {
2022-12-19 04:20:23 -08:00
return perpMarket . priceNativeToUi (
this . avgEntryPricePerBaseLot / perpMarket . baseLotSize . toNumber ( ) ,
2022-12-08 01:16:06 -08:00
) ;
2022-11-17 23:58:56 -08:00
}
2022-12-08 01:16:06 -08:00
public getBreakEvenPriceUi ( perpMarket : PerpMarket ) : number {
2022-11-17 23:58:56 -08:00
if ( this . basePositionLots . eq ( new BN ( 0 ) ) ) {
2022-12-08 01:16:06 -08:00
return 0 ;
2022-11-17 23:58:56 -08:00
}
2022-12-19 04:20:23 -08:00
return perpMarket . priceNativeToUi (
- this . quoteRunningNative . toNumber ( ) /
this . basePositionLots . mul ( perpMarket . baseLotSize ) . toNumber ( ) ,
2022-12-08 01:16:06 -08:00
) ;
2022-11-17 23:58:56 -08:00
}
2022-11-21 11:36:13 -08:00
public getPnl ( perpMarket : PerpMarket ) : I80F48 {
return this . quotePositionNative . add (
I80F48 . fromI64 ( this . basePositionLots . mul ( perpMarket . baseLotSize ) ) . mul (
perpMarket . price ,
) ,
) ;
}
2022-05-11 04:33:01 -07:00
}
2022-06-23 07:02:35 -07:00
export class PerpPositionDto {
2022-05-11 04:33:01 -07:00
constructor (
public marketIndex : number ,
2022-12-06 05:05:12 -08:00
public settlePnlLimitWindow : number ,
public settlePnlLimitSettledInCurrentWindowNative : BN ,
2022-05-11 04:33:01 -07:00
public basePositionLots : BN ,
public quotePositionNative : { val : BN } ,
2022-11-02 05:13:29 -07:00
public quoteRunningNative : BN ,
public longSettledFunding : I80F48Dto ,
public shortSettledFunding : I80F48Dto ,
2022-05-11 04:33:01 -07:00
public bidsBaseLots : BN ,
public asksBaseLots : BN ,
public takerBaseLots : BN ,
public takerQuoteLots : BN ,
2022-11-02 05:13:29 -07:00
public cumulativeLongFunding : number ,
public cumulativeShortFunding : number ,
public makerVolume : BN ,
public takerVolume : BN ,
public perpSpotTransfers : BN ,
2022-12-06 05:05:12 -08:00
public avgEntryPricePerBaseLot : number ,
public realizedPnlNative : I80F48Dto ,
2022-05-11 04:33:01 -07:00
) { }
}
2022-07-04 03:29:35 -07:00
2022-09-20 03:57:01 -07:00
export class PerpOo {
static OrderMarketUnset = 65535 ;
2022-09-29 06:51:09 -07:00
static from ( dto : PerpOoDto ) : PerpOo {
2022-12-08 12:53:07 -08:00
return new PerpOo ( dto . sideAndTree , dto . market , dto . clientId , dto . id ) ;
2022-09-20 03:57:01 -07:00
}
constructor (
2022-12-08 12:53:07 -08:00
public sideAndTree : any ,
2022-09-20 03:57:01 -07:00
public orderMarket : 0 ,
2022-12-08 12:53:07 -08:00
public clientId : BN ,
public id : BN ,
2022-09-20 03:57:01 -07:00
) { }
}
export class PerpOoDto {
constructor (
2022-12-08 12:53:07 -08:00
public sideAndTree : any ,
public market : 0 ,
public clientId : BN ,
public id : BN ,
2022-09-20 03:57:01 -07:00
) { }
}
2022-07-04 03:29:35 -07:00
export class HealthType {
static maint = { maint : { } } ;
static init = { init : { } } ;
}