2023-02-21 23:36:59 -08:00
import { AnchorProvider , BN } from '@coral-xyz/anchor' ;
import { utf8 } from '@coral-xyz/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' ;
2023-01-11 05:32:15 -08:00
import { OPENBOOK_PROGRAM_ID , RUST_I64_MAX , RUST_I64_MIN } 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' ;
2023-03-07 03:50:41 -08:00
import { PerpMarket , PerpMarketIndex , PerpOrder , PerpOrderSide } from './perp' ;
2022-09-29 06:51:09 -07:00
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 [ ] ;
2023-07-03 05:09:11 -07:00
public tokenConditionalSwaps : TokenConditionalSwap [ ] ;
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 ;
2023-01-12 04:08:10 -08:00
frozenUntil : BN ;
2023-02-27 07:36:27 -08:00
buybackFeesAccruedCurrent : BN ;
buybackFeesAccruedPrevious : BN ;
buybackFeesExpiryTimestamp : 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
} ,
2023-07-03 05:09:11 -07:00
tokenConditionalSwaps : TokenConditionalSwapDto [ ] ,
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 ,
2023-01-12 04:08:10 -08:00
obj . frozenUntil ,
2023-02-27 07:36:27 -08:00
obj . buybackFeesAccruedCurrent ,
obj . buybackFeesAccruedPrevious ,
obj . buybackFeesExpiryTimestamp ,
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 [ ] ,
2023-07-03 05:09:11 -07:00
tokenConditionalSwaps ,
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 ,
2023-01-12 04:08:10 -08:00
public frozenUntil : BN ,
2023-02-27 07:36:27 -08:00
public buybackFeesAccruedCurrent : BN ,
public buybackFeesAccruedPrevious : BN ,
public buybackFeesExpiryTimestamp : BN ,
2022-10-11 00:34:02 -07:00
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 [ ] ,
2023-07-03 05:09:11 -07:00
tokenConditionalSwaps : TokenConditionalSwapDto [ ] ,
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 ) ) ;
2023-07-03 05:09:11 -07:00
this . tokenConditionalSwaps = tokenConditionalSwaps . map ( ( dto ) = >
TokenConditionalSwap . 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 > {
2023-07-03 05:09:11 -07:00
const mangoAccount = await client . getMangoAccount ( this . publicKey ) ;
2023-01-19 02:31:54 -08:00
await mangoAccount . reloadSerum3OpenOrders ( 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 ) ;
2023-01-19 02:31:54 -08:00
await resp ? . value . reloadSerum3OpenOrders ( client ) ;
2022-09-05 09:31:57 -07:00
Object . assign ( this , resp ? . value ) ;
return { value : resp ! . value , slot : resp ! . slot } ;
}
2023-01-19 02:31:54 -08:00
async reloadSerum3OpenOrders ( client : MangoClient ) : Promise < MangoAccount > {
2022-09-29 06:51:09 -07:00
const serum3Active = this . serum3Active ( ) ;
2023-04-03 12:07:00 -07:00
if ( ! serum3Active . length ) return this ;
2022-09-29 06:51:09 -07:00
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
}
2023-07-04 00:26:59 -07:00
loadSerum3OpenOrders ( serum3OosMapByOo : Map < string , OpenOrders > ) : void {
const serum3Active = this . serum3Active ( ) ;
if ( ! serum3Active . length ) return ;
this . serum3OosMapByMarketIndex = new Map (
Array . from (
serum3Active . map ( ( mangoOo ) = > {
const oo = serum3OosMapByOo . get ( mangoOo . openOrders . toBase58 ( ) ) ;
if ( ! oo ) {
throw new Error ( ` Undefined open orders for ${ mangoOo . openOrders } ` ) ;
}
return [ mangoOo . marketIndex , oo ] ;
} ) ,
) ,
) ;
}
2022-10-11 00:39:57 -07:00
public isDelegate ( client : MangoClient ) : boolean {
return this . delegate . equals (
( client . program . provider as AnchorProvider ) . wallet . publicKey ,
) ;
}
2023-01-12 04:08:10 -08:00
public isOperational ( ) : boolean {
return this . frozenUntil . lt ( new BN ( Date . now ( ) / 1000 ) ) ;
}
2022-10-11 00:39:57 -07:00
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 ,
) ;
}
2023-01-24 08:44:22 -08:00
public perpOrderExistsForMarket ( perpMarket : PerpMarket ) : boolean {
return this . perpOpenOrders . some (
( poo ) = > poo . isActive ( ) && poo . orderMarket == 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 ( ) ) ;
}
2023-07-03 05:09:11 -07:00
public tokenConditionalSwapsActive ( ) : TokenConditionalSwap [ ] {
return this . tokenConditionalSwaps . filter ( ( tcs ) = > tcs . hasData ) ;
}
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 ,
) : 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
}
2023-05-13 02:55:08 -07:00
// TODO: once perp quote is merged, also add in the settle token balance if relevant
public getEffectiveTokenBalance ( group : Group , bank : Bank ) : I80F48 {
const tp = this . getToken ( bank . tokenIndex ) ;
if ( tp ) {
const bal = tp . balance ( bank ) ;
for ( const serum3Market of Array . from (
group . serum3MarketsMapByMarketIndex . values ( ) ,
) ) {
const oo = this . serum3OosMapByMarketIndex . get ( serum3Market . marketIndex ) ;
if ( serum3Market . baseTokenIndex == bank . tokenIndex && oo ) {
bal . add ( I80F48 . fromI64 ( oo . baseTokenFree ) ) ;
}
if ( serum3Market . quoteTokenIndex == bank . tokenIndex && oo ) {
bal . add ( I80F48 . fromI64 ( oo . quoteTokenFree ) ) ;
}
}
return bal ;
}
return 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
}
2023-05-17 06:50:05 -07:00
public perpMaxSettle (
group : Group ,
perpMarketSettleTokenIndex : TokenIndex ,
) : I80F48 {
2022-11-21 11:36:13 -08:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2023-05-17 06:50:05 -07:00
return hc . perpMaxSettle ( perpMarketSettleTokenIndex ) ;
2022-11-21 11:36:13 -08:00
}
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 ) ;
tokensMap
. get ( baseBank . tokenIndex ) !
2023-05-27 22:44:45 -07:00
. iadd ( I80F48 . fromI64 ( oo . quoteTokenTotal ) . 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
* /
2023-05-17 06:50:05 -07:00
public getAssetsValue ( group : Group ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2023-05-17 06:50:05 -07:00
return hc . healthAssetsAndLiabs ( undefined , false ) . assets ;
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
* /
2023-05-17 06:50:05 -07:00
public getLiabsValue ( group : Group ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2023-05-17 06:50:05 -07:00
return hc . healthAssetsAndLiabs ( undefined , false ) . liabs ;
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
2023-05-17 06:50:05 -07:00
* TODO : see max_borrow_for_health_fn
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 ;
}
2023-01-05 23:42:16 -08:00
const sourceBank = group . getFirstBankByMint ( sourceMintPk ) ;
const targetBank = group . getFirstBankByMint ( targetMintPk ) ;
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2023-01-05 23:42:16 -08:00
let maxSource = hc . getMaxSwapSource (
sourceBank ,
targetBank ,
2022-12-16 07:33:37 -08:00
I80F48 . fromNumber (
slippageAndFeesFactor *
2023-01-05 23:42:16 -08:00
( ( sourceBank . uiPrice / targetBank . uiPrice ) *
Math . pow ( 10 , targetBank . mintDecimals - sourceBank . mintDecimals ) ) ,
2022-09-23 02:43:26 -07:00
) ,
2022-08-23 07:21:05 -07:00
) ;
2023-05-13 02:55:08 -07:00
const sourceBalance = this . getEffectiveTokenBalance ( group , sourceBank ) ;
2023-01-05 23:42:16 -08:00
if ( maxSource . gt ( sourceBalance ) ) {
const sourceBorrow = maxSource . sub ( sourceBalance ) ;
maxSource = sourceBalance . add (
sourceBorrow . div ( ONE_I80F48 ( ) . add ( sourceBank . loanOriginationFeeRate ) ) ,
) ;
}
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 [ ] > {
2023-02-21 23:07:06 -08:00
const openOrderPks = this . serum3Active ( ) . map ( ( s ) = > s . openOrders ) ;
if ( ! openOrderPks . length ) return [ ] ;
2022-09-25 17:26:02 -07:00
const response =
2022-09-23 20:14:12 -07:00
await client . program . provider . connection . getMultipleAccountsInfo (
2023-02-21 23:07:06 -08:00
openOrderPks ,
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 ) ;
2023-01-05 23:42:16 -08:00
const nativeAmount = hc . getMaxSerum3OrderForHealthRatio (
2022-09-29 06:51:09 -07:00
baseBank ,
quoteBank ,
serum3Market ,
Serum3Side . bid ,
I80F48 . fromNumber ( 2 ) ,
) ;
2023-01-05 23:42:16 -08:00
let quoteAmount = nativeAmount . div ( quoteBank . price ) ;
2022-09-29 06:51:09 -07:00
// 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.
2023-05-13 02:55:08 -07:00
const quoteBalance = this . getEffectiveTokenBalance ( group , quoteBank ) ;
2023-01-05 23:42:16 -08:00
if ( quoteAmount . gt ( quoteBalance ) ) {
const quoteBorrow = quoteAmount . sub ( quoteBalance ) ;
quoteAmount = quoteBalance . add (
quoteBorrow . div ( ONE_I80F48 ( ) . add ( quoteBank . loanOriginationFeeRate ) ) ,
) ;
}
quoteAmount = quoteAmount . div (
ONE_I80F48 ( ) . add ( I80F48 . fromNumber ( serum3Market . getFeeRates ( true ) ) ) ,
2022-08-31 02:36:44 -07:00
) ;
2023-05-16 10:20:43 -07:00
return toUiDecimals ( quoteAmount , quoteBank . mintDecimals ) ;
2022-08-31 02:36:44 -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 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 ) ;
2023-01-05 23:42:16 -08:00
const nativeAmount = hc . getMaxSerum3OrderForHealthRatio (
2022-09-29 06:51:09 -07:00
baseBank ,
quoteBank ,
serum3Market ,
Serum3Side . ask ,
I80F48 . fromNumber ( 2 ) ,
) ;
2023-01-05 23:42:16 -08:00
let baseAmount = nativeAmount . div ( baseBank . price ) ;
2022-09-29 06:51:09 -07:00
// 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.
2023-05-13 02:55:08 -07:00
const baseBalance = this . getEffectiveTokenBalance ( group , baseBank ) ;
2023-01-05 23:42:16 -08:00
if ( baseAmount . gt ( baseBalance ) ) {
const baseBorrow = baseAmount . sub ( baseBalance ) ;
baseAmount = baseBalance . add (
baseBorrow . div ( ONE_I80F48 ( ) . add ( baseBank . loanOriginationFeeRate ) ) ,
) ;
}
baseAmount = baseAmount . div (
ONE_I80F48 ( ) . add ( I80F48 . fromNumber ( serum3Market . getFeeRates ( true ) ) ) ,
2022-08-31 02:36:44 -07:00
) ;
2023-01-05 23:42:16 -08:00
return toUiDecimals ( baseAmount , baseBank . mintDecimals ) ;
2022-08-31 02:36:44 -07:00
}
/ * *
*
* @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 ,
2023-03-07 03:50:41 -08:00
perpMarket . price ,
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 ,
2023-03-07 03:50:41 -08:00
perpMarket . price ,
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 ) ,
2023-03-07 04:08:54 -08:00
perpMarket . price ,
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 ) ,
2023-03-07 04:08:54 -08:00
perpMarket . price ,
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 ,
2023-02-21 23:07:06 -08:00
forceReload? : boolean ,
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 ( [
2023-02-21 23:07:06 -08:00
perpMarket . loadBids ( client , forceReload ) ,
perpMarket . loadAsks ( client , forceReload ) ,
2022-09-20 03:57:01 -07:00
] ) ;
2022-12-15 01:40:45 -08:00
2023-02-21 23:07:06 -08:00
return [ . . . bids . items ( ) , . . . asks . items ( ) ] . filter ( ( order ) = >
order . owner . equals ( this . publicKey ) ,
2022-09-20 03:57:01 -07:00
) ;
2022-07-04 03:09:33 -07:00
}
2023-02-28 03:05:02 -08:00
public getBuybackFeesAccrued ( ) : BN {
return this . buybackFeesAccruedCurrent . add ( this . buybackFeesAccruedPrevious ) ;
}
public getBuybackFeesAccruedUi ( ) : number {
return toUiDecimalsForQuote ( this . getBuybackFeesAccrued ( ) ) ;
}
public getMaxFeesBuyback ( group : Group ) : BN {
const mngoBalanceValueWithBonus = new BN (
this . getTokenBalance ( group . getFirstBankForMngo ( ) )
. mul ( group . getFirstBankForMngo ( ) . price )
. mul ( I80F48 . fromNumber ( group . buybackFeesMngoBonusFactor ) )
. floor ( )
. toNumber ( ) ,
) ;
return BN . max (
BN . min ( this . getBuybackFeesAccrued ( ) , mngoBalanceValueWithBonus ) ,
new BN ( 0 ) ,
) ;
}
public getMaxFeesBuybackUi ( group : Group ) : number {
return toUiDecimalsForQuote ( this . getMaxFeesBuyback ( group ) ) ;
}
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
2023-01-18 11:15:04 -08:00
? res +
'\n perps:' +
JSON . stringify (
this . perpActive ( ) . map ( ( p ) = >
2023-01-21 02:35:43 -08:00
p . toString ( group ? . getPerpMarketByMarketIndex ( p . marketIndex ) ) ,
2023-01-18 11:15:04 -08:00
) ,
null ,
4 ,
)
2022-07-04 03:09:33 -07:00
: 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 ,
2023-01-11 05:32:15 -08:00
I80F48 . from ( dto . realizedTradePnlNative ) ,
I80F48 . from ( dto . realizedOtherPnlNative ) ,
dto . settlePnlLimitRealizedTrade ,
2023-01-17 05:07:58 -08:00
I80F48 . from ( dto . realizedPnlForPositionNative ) ,
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 ( ) ,
2023-01-11 05:32:15 -08:00
ZERO_I80F48 ( ) ,
new BN ( 0 ) ,
2023-01-17 05:07:58 -08:00
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 ,
2023-01-11 05:32:15 -08:00
public realizedTradePnlNative : I80F48 ,
public realizedOtherPnlNative : I80F48 ,
public settlePnlLimitRealizedTrade : BN ,
2023-01-17 05:07:58 -08:00
public realizedPnlForPositionNative : 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
2023-06-21 23:40:06 -07:00
public getBasePosition ( perpMarket : PerpMarket ) : I80F48 {
2023-01-17 05:07:58 -08:00
return I80F48 . fromI64 ( this . basePositionLots . mul ( perpMarket . baseLotSize ) ) ;
}
2022-10-11 00:39:57 -07:00
public getBasePositionUi (
perpMarket : PerpMarket ,
useEventQueue? : boolean ,
) : number {
2023-01-11 05:32:15 -08:00
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
2022-10-11 00:39:57 -07:00
return perpMarket . baseLotsToUi (
useEventQueue
? this . basePositionLots . add ( this . takerBaseLots )
: this . basePositionLots ,
) ;
2022-10-07 04:52:04 -07:00
}
2023-02-24 01:43:37 -08:00
public getQuotePositionUi (
perpMarket : PerpMarket ,
useEventQueue? : boolean ,
) : number {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
const quotePositionUi = toUiDecimalsForQuote ( this . quotePositionNative ) ;
return useEventQueue
? quotePositionUi + perpMarket . quoteLotsToUi ( this . takerQuoteLots )
: quotePositionUi ;
}
public getNotionalValueUi (
perpMarket : PerpMarket ,
useEventQueue? : boolean ,
) : number {
return (
this . getBasePositionUi ( perpMarket , useEventQueue ) * perpMarket . uiPrice
) ;
}
2022-10-07 04:52:04 -07:00
public getUnsettledFunding ( perpMarket : PerpMarket ) : I80F48 {
2023-01-11 05:32:15 -08:00
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
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 ( ) ;
}
2023-03-30 08:17:38 -07:00
public getUnsettledFundingUi ( perpMarket : PerpMarket ) : number {
return toUiDecimalsForQuote ( this . getUnsettledFunding ( perpMarket ) ) ;
}
2022-09-29 06:51:09 -07:00
public getEquity ( perpMarket : PerpMarket ) : I80F48 {
2023-01-11 05:32:15 -08:00
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
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 ) ;
}
2023-06-21 23:40:06 -07:00
public getEquityUi ( perpMarket : PerpMarket ) : number {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
return toUiDecimalsForQuote ( this . getEquity ( perpMarket ) ) ;
}
2022-09-29 06:51:09 -07:00
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
2023-01-17 05:07:58 -08:00
public getAverageEntryPrice ( perpMarket : PerpMarket ) : I80F48 {
2023-01-19 01:52:49 -08:00
return I80F48 . fromNumber ( this . avgEntryPricePerBaseLot ) . div (
2023-01-17 05:07:58 -08:00
I80F48 . fromI64 ( perpMarket . baseLotSize ) ,
) ;
}
2023-01-11 05:32:15 -08:00
2023-01-17 05:07:58 -08:00
public getAverageEntryPriceUi ( perpMarket : PerpMarket ) : number {
2022-12-19 04:20:23 -08:00
return perpMarket . priceNativeToUi (
2023-01-17 05:07:58 -08:00
this . getAverageEntryPrice ( perpMarket ) . toNumber ( ) ,
2022-12-08 01:16:06 -08:00
) ;
2022-11-17 23:58:56 -08:00
}
2023-06-26 07:45:52 -07:00
public getLiquidationPrice (
group : Group ,
mangoAccount : MangoAccount ,
) : I80F48 | null {
2022-11-17 23:58:56 -08:00
if ( this . basePositionLots . eq ( new BN ( 0 ) ) ) {
2023-06-26 07:45:52 -07:00
return null ;
2022-11-17 23:58:56 -08:00
}
2023-06-26 07:45:52 -07:00
return HealthCache . fromMangoAccount (
group ,
mangoAccount ,
) . getPerpPositionLiquidationPrice ( group , mangoAccount , this ) ;
2022-11-17 23:58:56 -08:00
}
2022-11-21 11:36:13 -08:00
2023-06-26 07:45:52 -07:00
public getLiquidationPriceUi (
group : Group ,
mangoAccount : MangoAccount ,
) : number | null {
const pm = group . getPerpMarketByMarketIndex ( this . marketIndex ) ;
const lp = this . getLiquidationPrice ( group , mangoAccount ) ;
return lp == null ? null : pm . priceNativeToUi ( lp . toNumber ( ) ) ;
}
2023-06-23 05:32:04 -07:00
public getBreakEvenPrice ( perpMarket : PerpMarket ) : I80F48 {
2023-01-11 05:32:15 -08:00
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
2022-11-17 23:58:56 -08:00
if ( this . basePositionLots . eq ( new BN ( 0 ) ) ) {
2023-06-23 05:32:04 -07:00
return ZERO_I80F48 ( ) ;
2022-11-17 23:58:56 -08:00
}
2023-01-17 05:07:58 -08:00
2023-06-23 05:32:04 -07:00
return I80F48 . fromI64 ( this . quoteRunningNative )
. sub ( this . getUnsettledFunding ( perpMarket ) )
. neg ( )
. div ( I80F48 . fromI64 ( this . basePositionLots . mul ( perpMarket . baseLotSize ) ) ) ;
2023-01-17 05:07:58 -08:00
}
2023-06-23 05:32:04 -07:00
public getBreakEvenPriceUi ( perpMarket : PerpMarket ) : number {
2022-12-19 04:20:23 -08:00
return perpMarket . priceNativeToUi (
2023-06-23 05:32:04 -07:00
this . getBreakEvenPrice ( perpMarket ) . toNumber ( ) ,
2023-01-17 05:07:58 -08:00
) ;
}
2023-06-21 23:40:06 -07:00
public canSettlePnl (
group : Group ,
perpMarket : PerpMarket ,
account : MangoAccount ,
) : boolean {
return ! this . getSettleablePnl ( group , perpMarket , account ) . eq ( ZERO_I80F48 ( ) ) ;
2022-11-21 11:36:13 -08:00
}
2023-01-11 05:32:15 -08:00
public updateSettleLimit ( perpMarket : PerpMarket ) : void {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
const windowSize = perpMarket . settlePnlLimitWindowSizeTs ;
const windowStart = new BN ( this . settlePnlLimitWindow ) . mul ( windowSize ) ;
const windowEnd = windowStart . add ( windowSize ) ;
const nowTs = new BN ( Date . now ( ) / 1000 ) ;
const newWindow = nowTs . gte ( windowEnd ) || nowTs . lt ( windowStart ) ;
if ( newWindow ) {
this . settlePnlLimitWindow = nowTs . div ( windowSize ) . toNumber ( ) ;
this . settlePnlLimitSettledInCurrentWindowNative = new BN ( 0 ) ;
}
}
public availableSettleLimit ( perpMarket : PerpMarket ) : [ BN , BN ] {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
if ( perpMarket . settlePnlLimitFactor < 0 ) {
return [ RUST_I64_MIN ( ) , RUST_I64_MAX ( ) ] ;
}
const baseNative = I80F48 . fromI64 (
this . basePositionLots . mul ( perpMarket . baseLotSize ) ,
2023-03-30 00:39:49 -07:00
) . abs ( ) ;
2023-01-11 05:32:15 -08:00
const positionValue = I80F48 . fromNumber (
perpMarket . stablePriceModel . stablePrice ,
)
. mul ( baseNative )
. toNumber ( ) ;
const unrealized = new BN ( perpMarket . settlePnlLimitFactor * positionValue ) ;
const used = new BN (
this . settlePnlLimitSettledInCurrentWindowNative . toNumber ( ) ,
) ;
let minPnl = unrealized . neg ( ) . sub ( used ) ;
let maxPnl = unrealized . sub ( used ) ;
const realizedTrade = this . settlePnlLimitRealizedTrade ;
if ( realizedTrade . gte ( new BN ( 0 ) ) ) {
maxPnl = maxPnl . add ( realizedTrade ) ;
} else {
minPnl = minPnl . add ( realizedTrade ) ;
}
const realizedOther = new BN ( this . realizedOtherPnlNative . toNumber ( ) ) ;
if ( realizedOther . gte ( new BN ( 0 ) ) ) {
maxPnl = maxPnl . add ( realizedOther ) ;
} else {
minPnl = minPnl . add ( realizedOther ) ;
}
return [ BN . min ( minPnl , new BN ( 0 ) ) , BN . max ( maxPnl , new BN ( 0 ) ) ] ;
}
public applyPnlSettleLimit ( pnl : I80F48 , perpMarket : PerpMarket ) : I80F48 {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
if ( perpMarket . settlePnlLimitFactor < 0 ) {
return pnl ;
}
const [ minPnl , maxPnl ] = this . availableSettleLimit ( perpMarket ) ;
if ( pnl . lt ( ZERO_I80F48 ( ) ) ) {
return pnl . max ( I80F48 . fromI64 ( minPnl ) ) ;
} else {
return pnl . min ( I80F48 . fromI64 ( maxPnl ) ) ;
}
}
2023-06-21 23:40:06 -07:00
public getUnsettledPnl ( perpMarket : PerpMarket ) : I80F48 {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
return this . quotePositionNative . add (
this . getBasePosition ( perpMarket ) . mul ( perpMarket . price ) ,
) ;
}
public getUnsettledPnlUi ( perpMarket : PerpMarket ) : number {
return toUiDecimalsForQuote ( this . getUnsettledPnl ( perpMarket ) ) ;
}
2023-02-09 04:31:56 -08:00
public getSettleablePnl (
group : Group ,
perpMarket : PerpMarket ,
account : MangoAccount ,
) : I80F48 {
2023-01-11 05:32:15 -08:00
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
2023-02-09 04:31:56 -08:00
this . updateSettleLimit ( perpMarket ) ;
2023-05-17 06:50:05 -07:00
const perpMaxSettle = account . perpMaxSettle (
group ,
perpMarket . settleTokenIndex ,
) ;
2023-02-09 04:31:56 -08:00
const limitedUnsettled = this . applyPnlSettleLimit (
2023-01-17 05:07:58 -08:00
this . getUnsettledPnl ( perpMarket ) ,
perpMarket ,
) ;
2023-02-09 04:31:56 -08:00
if ( limitedUnsettled . lt ( ZERO_I80F48 ( ) ) ) {
2023-05-17 06:50:05 -07:00
return limitedUnsettled . max ( perpMaxSettle . max ( ZERO_I80F48 ( ) ) . neg ( ) ) ;
2023-02-09 04:31:56 -08:00
}
return limitedUnsettled ;
}
2023-06-21 23:40:06 -07:00
public getSettleablePnlUi (
2023-02-09 04:31:56 -08:00
group : Group ,
perpMarket : PerpMarket ,
account : MangoAccount ,
) : number {
return toUiDecimalsForQuote (
this . getSettleablePnl ( group , perpMarket , account ) ,
) ;
}
2023-06-21 23:40:06 -07:00
public cumulativePnlOverPositionLifetimeUi ( perpMarket : PerpMarket ) : number {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
const priceChange = perpMarket . price . sub (
this . getAverageEntryPrice ( perpMarket ) ,
) ;
return toUiDecimalsForQuote (
this . realizedPnlForPositionNative . add (
this . getBasePosition ( perpMarket ) . mul ( priceChange ) ,
) ,
) ;
}
public getUnRealizedPnlUi ( perpMarket : PerpMarket ) : number {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
const priceChange = perpMarket . price . sub (
this . getAverageEntryPrice ( perpMarket ) ,
) ;
return toUiDecimalsForQuote (
this . getBasePosition ( perpMarket ) . mul ( priceChange ) ,
) ;
}
public getRealizedPnlUi ( ) : number {
return toUiDecimalsForQuote ( this . realizedPnlForPositionNative ) ;
2023-01-11 05:32:15 -08:00
}
2023-01-18 11:15:04 -08:00
2023-01-21 02:35:43 -08:00
toString ( perpMarket? : PerpMarket ) : string {
return perpMarket
2023-01-18 11:15:04 -08:00
? 'market - ' +
perpMarket . name +
', basePositionLots - ' +
perpMarket . baseLotsToUi ( this . basePositionLots ) +
2023-01-21 02:35:43 -08:00
', quotePositive - ' +
toUiDecimalsForQuote ( this . quotePositionNative . toNumber ( ) ) +
2023-01-18 11:15:04 -08:00
', bidsBaseLots - ' +
perpMarket . baseLotsToUi ( this . bidsBaseLots ) +
', asksBaseLots - ' +
perpMarket . baseLotsToUi ( this . asksBaseLots ) +
', takerBaseLots - ' +
perpMarket . baseLotsToUi ( this . takerBaseLots ) +
', takerQuoteLots - ' +
perpMarket . quoteLotsToUi ( this . takerQuoteLots ) +
', unsettled pnl - ' +
2023-03-06 23:32:44 -08:00
this . getUnsettledPnlUi ( perpMarket ! ) . toString ( ) +
', average entry price ui - ' +
this . getAverageEntryPriceUi ( perpMarket ! ) . toString ( ) +
', notional value ui - ' +
this . getNotionalValueUi ( perpMarket ! ) . toString ( ) +
', cumulative pnl over position lifetime ui - ' +
this . cumulativePnlOverPositionLifetimeUi ( perpMarket ! ) . toString ( ) +
', realized other pnl native ui - ' +
toUiDecimalsForQuote ( this . realizedOtherPnlNative ) +
', cumulative long funding ui - ' +
toUiDecimalsForQuote ( this . cumulativeLongFunding ) +
', cumulative short funding ui - ' +
toUiDecimalsForQuote ( this . cumulativeShortFunding )
2023-01-18 11:15:04 -08:00
: '' ;
}
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 ,
2023-01-11 05:32:15 -08:00
public realizedTradePnlNative : I80F48Dto ,
public realizedOtherPnlNative : I80F48Dto ,
public settlePnlLimitRealizedTrade : BN ,
2023-01-17 05:07:58 -08:00
public realizedPnlForPositionNative : 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 ,
2023-01-24 08:44:22 -08:00
public orderMarket : number ,
2022-12-08 12:53:07 -08:00
public clientId : BN ,
public id : BN ,
2022-09-20 03:57:01 -07:00
) { }
2023-01-24 08:44:22 -08:00
isActive ( ) : boolean {
return this . orderMarket !== PerpOo . OrderMarketUnset ;
}
2022-09-20 03:57:01 -07:00
}
export class PerpOoDto {
constructor (
2022-12-08 12:53:07 -08:00
public sideAndTree : any ,
2023-01-24 08:44:22 -08:00
public market : number ,
2022-12-08 12:53:07 -08:00
public clientId : BN ,
public id : BN ,
2022-09-20 03:57:01 -07:00
) { }
}
2023-08-03 03:37:01 -07:00
export class TokenConditionalSwapDisplayPriceStyle {
static sellTokenPerBuyToken = { sellTokenPerBuyToken : { } } ;
static buyTokenPerSellToken = { buyTokenPerSellToken : { } } ;
}
2023-08-08 09:16:59 -07:00
export class TokenConditionalSwapIntention {
static unknown = { unknown : { } } ;
static stopLoss = { stopLoss : { } } ;
static takeProfit = { takeProfit : { } } ;
}
function tokenConditionalSwapIntentionFromDto (
intention : number ,
) : TokenConditionalSwapIntention {
switch ( intention ) {
case 0 :
return TokenConditionalSwapIntention . unknown ;
case 1 :
return TokenConditionalSwapIntention . stopLoss ;
case 2 :
return TokenConditionalSwapIntention . takeProfit ;
default :
throw new Error (
` unexpected token conditional swap intention: ${ intention } ` ,
) ;
}
}
2023-07-03 05:09:11 -07:00
export class TokenConditionalSwap {
static from ( dto : TokenConditionalSwapDto ) : TokenConditionalSwap {
return new TokenConditionalSwap (
dto . id ,
dto . maxBuy ,
dto . maxSell ,
dto . bought ,
dto . sold ,
dto . expiryTimestamp ,
dto . priceLowerLimit ,
dto . priceUpperLimit ,
2023-08-08 01:21:13 -07:00
dto . pricePremiumRate ,
dto . takerFeeRate ,
dto . makerFeeRate ,
2023-07-03 05:09:11 -07:00
dto . buyTokenIndex as TokenIndex ,
dto . sellTokenIndex as TokenIndex ,
dto . hasData == 1 ,
dto . allowCreatingDeposits == 1 ,
dto . allowCreatingBorrows == 1 ,
2023-08-03 03:37:01 -07:00
dto . priceDisplayStyle == 0
? TokenConditionalSwapDisplayPriceStyle . sellTokenPerBuyToken
: TokenConditionalSwapDisplayPriceStyle . buyTokenPerSellToken ,
2023-08-08 09:16:59 -07:00
tokenConditionalSwapIntentionFromDto ( dto . intention ) ,
2023-07-03 05:09:11 -07:00
) ;
}
constructor (
public id : BN ,
public maxBuy : BN ,
public maxSell : BN ,
public bought : BN ,
public sold : BN ,
public expiryTimestamp : BN ,
public priceLowerLimit : number ,
public priceUpperLimit : number ,
2023-08-08 01:21:13 -07:00
public pricePremiumRate : number ,
public takerFeeRate : number ,
public makerFeeRate : number ,
2023-07-03 05:09:11 -07:00
public buyTokenIndex : TokenIndex ,
public sellTokenIndex : TokenIndex ,
public hasData : boolean ,
public allowCreatingDeposits : boolean ,
public allowCreatingBorrows : boolean ,
2023-08-03 03:37:01 -07:00
public priceDisplayStyle : TokenConditionalSwapDisplayPriceStyle ,
2023-08-08 09:16:59 -07:00
public intention : TokenConditionalSwapIntention ,
2023-07-03 05:09:11 -07:00
) { }
}
export class TokenConditionalSwapDto {
constructor (
public id : BN ,
public maxBuy : BN ,
public maxSell : BN ,
public bought : BN ,
public sold : BN ,
public expiryTimestamp : BN ,
public priceLowerLimit : number ,
public priceUpperLimit : number ,
2023-08-08 01:21:13 -07:00
public pricePremiumRate : number ,
public takerFeeRate : number ,
public makerFeeRate : number ,
2023-07-03 05:09:11 -07:00
public buyTokenIndex : number ,
public sellTokenIndex : number ,
public hasData : number ,
public allowCreatingDeposits : number ,
public allowCreatingBorrows : number ,
2023-08-03 03:37:01 -07:00
public priceDisplayStyle : number ,
2023-08-08 09:16:59 -07:00
public intention : number ,
2023-07-03 05:09:11 -07:00
) { }
}
2022-07-04 03:29:35 -07:00
export class HealthType {
static maint = { maint : { } } ;
static init = { init : { } } ;
2023-02-10 00:00:36 -08:00
static liquidationEnd = { liquidationEnd : { } } ;
2022-07-04 03:29:35 -07:00
}