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' ;
2023-08-11 10:12:13 -07:00
import { AccountInfo , PublicKey } 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' ;
2023-07-27 23:26:34 -07:00
import {
U64_MAX_BN ,
roundTo5 ,
toNativeI80F48 ,
toUiDecimals ,
toUiDecimalsForQuote ,
2023-08-07 04:09:19 -07:00
toUiSellPerBuyTokenPrice ,
2023-07-27 23:26:34 -07:00
} from '../utils' ;
2023-08-11 10:12:13 -07:00
import { MangoSignatureStatus } from '../utils/rpc' ;
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 ;
2023-11-27 00:30:37 -08:00
tokenConditionalSwaps : 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 ,
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-11-27 00:30:37 -08:00
obj . tokenConditionalSwaps as TokenConditionalSwapDto [ ] ,
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 ) ) ;
}
2023-09-20 01:48:11 -07:00
public async tokenPositionsForNotConfidentOrStaleOracles (
client : MangoClient ,
group : Group ,
) : Promise < Bank [ ] > {
const nowSlot = await client . connection . getSlot ( ) ;
return this . tokensActive ( )
. map ( ( tp ) = > group . getFirstBankByTokenIndex ( tp . tokenIndex ) )
. filter ( ( bank ) = > bank . isOracleStaleOrUnconfident ( nowSlot ) ) ;
}
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 ( ) ) ;
}
2023-07-28 06:57:25 -07:00
public tokenConditionalSwapsActive ( ) : TokenConditionalSwap [ ] {
2023-11-24 02:06:44 -08:00
return this . tokenConditionalSwaps . filter ( ( tcs ) = > tcs . isConfigured ) ;
2023-07-28 06:57:25 -07:00
}
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 ( ) ) ;
}
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-07-06 09:17:24 -07:00
public getHealthContributionPerAssetUi (
group : Group ,
healthType : HealthType ,
2023-07-14 05:02:06 -07:00
) : {
asset : string ;
contribution : number ;
contributionDetails :
| {
spotUi : number ;
perpMarketContributions : { market : string ; contributionUi : number } [ ] ;
}
| undefined ;
} [ ] {
2023-07-06 09:17:24 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
return hc . healthContributionPerAssetUi ( group , healthType ) ;
}
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-07-04 22:32:26 -07:00
public getAssetsValue ( group : Group , healthType? : HealthType ) : I80F48 {
2022-09-29 06:51:09 -07:00
const hc = HealthCache . fromMangoAccount ( group , this ) ;
2023-07-04 22:32:26 -07:00
return hc . healthAssetsAndLiabs ( healthType , 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
2023-08-07 04:12:37 -07:00
. mul ( tokenBank . getAssetPrice ( ) )
. imul ( tokenBank . scaledInitAssetWeight ( tokenBank . getAssetPrice ( ) ) ) ;
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
2023-08-07 04:12:37 -07:00
. div ( tokenBank . scaledInitAssetWeight ( tokenBank . getAssetPrice ( ) ) )
. div ( tokenBank . getAssetPrice ( ) ) ;
2022-08-23 04:47:08 -07:00
}
// 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-07-13 07:29:13 -07:00
const maxWithdrawNative = sourceBank . getMaxWithdraw (
group . getTokenVaultBalanceByMint ( sourceBank . mint ) ,
sourceBalance ,
) ;
maxSource = maxSource . min ( maxWithdrawNative ) ;
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-07-13 07:29:13 -07:00
const maxWithdrawNative = quoteBank . getMaxWithdraw (
group . getTokenVaultBalanceByMint ( quoteBank . mint ) ,
quoteBalance ,
) ;
quoteAmount = quoteAmount . min ( maxWithdrawNative ) ;
2023-01-05 23:42:16 -08:00
quoteAmount = quoteAmount . div (
ONE_I80F48 ( ) . add ( I80F48 . fromNumber ( serum3Market . getFeeRates ( true ) ) ) ,
2022-08-31 02:36:44 -07:00
) ;
2023-04-25 04:50:30 -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-07-13 07:29:13 -07:00
const maxWithdrawNative = baseBank . getMaxWithdraw (
group . getTokenVaultBalanceByMint ( baseBank . mint ) ,
baseBalance ,
) ;
baseAmount = baseAmount . min ( maxWithdrawNative ) ;
2023-01-05 23:42:16 -08:00
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 ,
2023-08-11 10:12:13 -07:00
) : Promise < MangoSignatureStatus [ ] > {
2022-11-04 08:07:26 -07:00
// 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 ,
2023-08-11 10:12:13 -07:00
) : Promise < MangoSignatureStatus [ ] > {
2022-11-04 08:07:26 -07:00
// 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 ,
2023-07-17 06:23:47 -07:00
healthType : HealthType = HealthType . init ,
2022-10-07 04:52:04 -07:00
) : 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 ,
2023-07-17 06:23:47 -07:00
healthType ,
2022-10-07 04:52:04 -07:00
)
. toNumber ( ) ;
}
public simHealthRatioWithPerpAskUiChanges (
group : Group ,
perpMarketIndex : PerpMarketIndex ,
size : number ,
2023-07-17 06:23:47 -07:00
healthType : HealthType = HealthType . init ,
2022-10-07 04:52:04 -07:00
) : 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 ,
2023-07-17 06:23:47 -07:00
healthType ,
2022-10-07 04:52:04 -07:00
)
. 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 ,
2023-09-16 02:04:38 -07:00
dto . highestPlacedBidInv ,
dto . lowestPlacedAsk ,
2023-11-14 01:29:58 -08:00
// dto.baseDepositsReserved.toNumber(),
// dto.quoteDepositsReserved.toNumber(),
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 ,
2023-09-16 02:04:38 -07:00
public highestPlacedBidInv : number ,
2023-11-14 08:05:55 -08:00
public lowestPlacedAsk : number , // public baseDepositsReserved: number, // public quoteDepositsReserved: number,
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 ,
2023-09-16 02:04:38 -07:00
public highestPlacedBidInv : number ,
public lowestPlacedAsk : number ,
2023-11-14 01:29:58 -08:00
// public baseDepositsReserved: BN,
// public quoteDepositsReserved: BN,
2022-04-03 07:02:14 -07:00
public reserved : number [ ] ,
) { }
}
2022-05-11 04:33:01 -07:00
2023-10-09 01:27:44 -07:00
export interface CumulativeFunding {
cumulativeLongFunding : number ;
cumulativeShortFunding : number ;
}
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-10-09 01:27:44 -07:00
Merge dev changes (#532)
* Fix script
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fix docs build job (#501)
* workaround where rpc rejects base58 encoded pubkeys (#502)
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Rename settle-bot to settler, fix build (#492)
* Fix settler build, rename due to heroku not liking '-'
* Temporarily remove ref tag
* Remove temporary branch trigger
* Add oracleProvider to Bank and PerpMarket (#491)
* Add oracleProvider to Bank and PerpMarket
* v0.9.6
* Fix null checks on getters for PerpMarket and Bank (#505)
* Export OracleProvider
* Fix null checks on getters
* token_liq_bankruptcy: Use oracle for valuing insurance fund tokens (#503)
Previously a token from the insurance fund was valued at 1 USD. Now it
uses the oracle associated with it (USDC oracle).
* v0.9.7
* ts: Fix ix gate enum, add code for creating a disable-tx gov ix
* Fee buyback: Respect USDC oracle (#504)
* refactor script (#509)
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* IxGateSet: Log AccountBuybackFeesWithMngo state
* TokenRegister: Sanity checks on token_index
* Allow token/market names to fill storage bytes completely
Previously the last byte was always zero.
* HealthRegion: Whitelist allowed instruction types (#508)
This fixes a security issue where bankruptcy related instructions could
be called inside a health region. Now health regions are limited to
compute optimization like when placing multiple orders in one
transaction.
This limitation also makes it impossible to abuse health regions for
flash loans. Use the FlashLoan instructions for that purpose.
* Add fly deploy scripts (#490)
* Bump program version to v0.10.0
* liquidator docs (#512)
* liquidator docs
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* update
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* update
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
---------
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Quality of life fixes (#511)
* breaking: make pegLimit an optional arg
* pass externally cached blockhashes to sendTransaction
* convenience accessors for connection & walletPk on client
* add script to sim accounts with leverage change (#514)
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fix bug: only account for borrows we are offsetting (#513)
* Fix bug: only account for borrows we are offsetting
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* fix
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Bank: Unittest for net borrow limits
---------
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
Co-authored-by: Christian Kamm <mail@ckamm.de>
* extend script, fix util function
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fix interest rate computation in client (#520)
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* minor ts fixes
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Temporarily disable health region use on mm script (#507)
* Use new shared mango-feeds-connector crate for chain_data (#515)
* Add prometheus metrics to crank (#517)
* Add prometheus metrics to keeper
* update script
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* fix bug where unrealised profit was not abs'ed
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fix script
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* update script
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* in perp settle fees, dont error, rather return early, this enables blindly concatenating perp settle fees to perp settle pnl (#526)
* in perp settle fees, dont error, rather return early
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fixes from review
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
---------
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* update script
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Net borrow limits: Use correct price for check (#527)
* Changelog for program-v0.10.0 and idl update
* script for grabbing logs
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Perp funding: Fix logging in update funding + deactivate pos (#528)
* update
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fix test
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* sync rate params to latest proposal (#523)
* sync rate params to latest proposal
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fixes from review
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Fixes from review
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
---------
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Limit funding and interest accrual during downtimes (#529)
Previously, if the funding or interest updating instruction wasn't
called for a long time (like for a solana downtime or the security
council halting the program), the next update would apply funding or
interest for the whole time interval since the last update.
This could lead to a bad downtime situation becoming worse. Instead,
limit the maximum funding and interest time interval to one hour.
* Changelog for program-v0.11.0, bump version, update idl
* Don't reload openorders if there's no active markets
* update
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* reorg
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
* Emit perp fees settled on update_funding. Required to have a full picture of total perp market fees. (#530)
---------
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
Co-authored-by: microwavedcola1 <microwavedcola@gmail.com>
Co-authored-by: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com>
Co-authored-by: Christian Kamm <mail@ckamm.de>
Co-authored-by: Maximilian Schneider <mail@maximilianschneider.net>
Co-authored-by: tlrsssss <tjshipe@gmail.com>
Co-authored-by: Nicholas Clarke <nicholasgclarke@gmail.com>
2023-04-07 05:57:53 -07:00
public getUnsettledFundingUi ( perpMarket : PerpMarket ) : number {
return toUiDecimalsForQuote ( this . getUnsettledFunding ( perpMarket ) ) ;
}
2022-09-29 06:51:09 -07:00
2023-10-09 01:27:44 -07:00
/ * *
* @returns perp position cumulative funding , in quote token units .
* If the user paid $1 in funding for a short position , this would be - 1 e6 .
* Caveat : This will only return cumulative interest since the perp position was last opened .
* If the perp position was closed and reopened multiple times it is necessary to add this result to
* cumulative funding at each of the prior perp position closings ( from mango API ) to get the all time
* cumulative funding .
* /
public getCumulativeFunding ( perpMarket : PerpMarket ) : CumulativeFunding {
const funding = this . getUnsettledFunding ( perpMarket ) . toNumber ( ) ;
let cumulativeLongFunding = this . cumulativeLongFunding ;
let cumulativeShortFunding = this . cumulativeShortFunding ;
if ( this . basePositionLots . toNumber ( ) > 0 ) {
cumulativeLongFunding += funding ;
} else {
cumulativeShortFunding -= funding ;
}
return {
cumulativeLongFunding : cumulativeLongFunding ,
cumulativeShortFunding : cumulativeShortFunding ,
} ;
}
2023-11-05 06:13:23 -08:00
/ * *
* @returns perp position cumulative funding .
* Caveat : This will only return cumulative interest since the perp position was last opened .
* /
public getCumulativeFundingUi ( perpMarket : PerpMarket ) : number {
if ( perpMarket . perpMarketIndex !== this . marketIndex ) {
throw new Error ( "PerpPosition doesn't belong to the given market!" ) ;
}
const cumulativeFunding = this . getCumulativeFunding ( perpMarket ) ;
// can't be long and short at the same time
if ( cumulativeFunding . cumulativeLongFunding !== 0 ) {
return - 1 * toUiDecimalsForQuote ( cumulativeFunding . cumulativeLongFunding ) ;
} else {
return toUiDecimalsForQuote ( cumulativeFunding . cumulativeShortFunding ) ;
}
}
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 {
if ( this . basePositionLots . eq ( new BN ( 0 ) ) ) {
return null ;
}
return HealthCache . fromMangoAccount (
group ,
mangoAccount ,
) . getPerpPositionLiquidationPrice ( group , mangoAccount , this ) ;
}
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-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 ) ) ) ;
}
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 ( ) ,
2022-12-08 01:16:06 -08:00
) ;
2022-11-17 23:58:56 -08:00
}
2022-11-21 11:36:13 -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-10-06 01:05:56 -07:00
export type TokenConditionalSwapDisplayPriceStyle =
| { sellTokenPerBuyToken : Record < string , never > }
| { buyTokenPerSellToken : Record < string , never > } ;
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace TokenConditionalSwapDisplayPriceStyle {
export const sellTokenPerBuyToken = { sellTokenPerBuyToken : { } } ;
export const buyTokenPerSellToken = { buyTokenPerSellToken : { } } ;
2023-08-03 03:37:01 -07:00
}
2023-10-06 01:05:56 -07:00
export type TokenConditionalSwapIntention =
| { unknown : Record < string , never > }
| { stopLoss : Record < string , never > }
| { takeProfit : Record < string , never > } ;
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace TokenConditionalSwapIntention {
export const unknown = { unknown : { } } ;
export const stopLoss = { stopLoss : { } } ;
export const takeProfit = { takeProfit : { } } ;
2023-08-08 09:16:59 -07:00
}
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 ,
2023-11-24 02:06:44 -08:00
dto . isConfigured == 1 ,
2023-07-03 05:09:11 -07:00
dto . allowCreatingDeposits == 1 ,
dto . allowCreatingBorrows == 1 ,
2023-09-08 03:15:50 -07:00
dto . displayPriceStyle == 0
2023-08-03 03:37:01 -07:00
? 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 ,
2023-11-24 02:06:44 -08:00
public isConfigured : boolean ,
2023-07-03 05:09:11 -07:00
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
) { }
2023-07-27 23:26:34 -07:00
getMaxBuyUi ( group : Group ) : number {
const buyBank = this . getBuyToken ( group ) ;
return toUiDecimals ( this . maxBuy , buyBank . mintDecimals ) ;
}
getMaxSellUi ( group : Group ) : number {
const sellBank = this . getSellToken ( group ) ;
return toUiDecimals ( this . maxSell , sellBank . mintDecimals ) ;
}
getBoughtUi ( group : Group ) : number {
const buyBank = this . getBuyToken ( group ) ;
return toUiDecimals ( this . bought , buyBank . mintDecimals ) ;
}
getSoldUi ( group : Group ) : number {
const sellBank = this . getSellToken ( group ) ;
return toUiDecimals ( this . sold , sellBank . mintDecimals ) ;
}
2023-07-28 06:32:50 -07:00
getExpiryTimestampInEpochSeconds ( ) : number {
2023-07-27 23:26:34 -07:00
return this . expiryTimestamp . toNumber ( ) ;
}
private priceLimitToUi (
group : Group ,
sellTokenPerBuyTokenNative : number ,
) : number {
const buyBank = this . getBuyToken ( group ) ;
const sellBank = this . getSellToken ( group ) ;
2023-08-07 04:09:19 -07:00
const sellTokenPerBuyTokenUi = toUiSellPerBuyTokenPrice (
2023-07-27 23:26:34 -07:00
sellTokenPerBuyTokenNative ,
2023-08-07 04:09:19 -07:00
sellBank ,
buyBank ,
2023-07-27 23:26:34 -07:00
) ;
// Below are workarounds to know when to show an inverted price in ui
// We want to identify if the pair user is wanting to trade is
// buytoken/selltoken or selltoken/buytoken
// Buy limit / close short
2023-09-07 23:49:38 -07:00
if (
this . priceDisplayStyle ==
TokenConditionalSwapDisplayPriceStyle . sellTokenPerBuyToken
) {
2023-07-27 23:26:34 -07:00
return roundTo5 ( sellTokenPerBuyTokenUi ) ;
}
// Stop loss / take profit
const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi ;
return roundTo5 ( buyTokenPerSellTokenUi ) ;
}
getPriceLowerLimitUi ( group : Group ) : number {
return this . priceLimitToUi ( group , this . priceLowerLimit ) ;
}
getPriceUpperLimitUi ( group : Group ) : number {
return this . priceLimitToUi ( group , this . priceUpperLimit ) ;
}
2023-08-08 05:59:43 -07:00
getThresholdPriceUi ( group : Group ) : number {
const buyBank = this . getBuyToken ( group ) ;
const sellBank = this . getSellToken ( group ) ;
2023-08-10 07:16:05 -07:00
const a = toUiSellPerBuyTokenPrice ( this . priceLowerLimit , sellBank , buyBank ) ;
const b = toUiSellPerBuyTokenPrice ( this . priceUpperLimit , sellBank , buyBank ) ;
const o = buyBank . uiPrice / sellBank . uiPrice ;
2023-08-08 05:59:43 -07:00
// Choose the price closest to oracle
2023-08-10 07:16:05 -07:00
if ( Math . abs ( o - a ) < Math . abs ( o - b ) ) {
2023-08-08 05:59:43 -07:00
return this . getPriceLowerLimitUi ( group ) ;
}
return this . getPriceUpperLimitUi ( group ) ;
}
2023-08-13 03:01:20 -07:00
getCurrentPairPriceUi ( group : Group ) : number {
const buyBank = this . getBuyToken ( group ) ;
const sellBank = this . getSellToken ( group ) ;
const sellTokenPerBuyTokenUi = toUiSellPerBuyTokenPrice (
buyBank . price . div ( sellBank . price ) . toNumber ( ) ,
sellBank ,
buyBank ,
) ;
// Below are workarounds to know when to show an inverted price in ui
// We want to identify if the pair user is wanting to trade is
// buytoken/selltoken or selltoken/buytoken
// Buy limit / close short
2023-09-07 23:49:38 -07:00
if (
this . priceDisplayStyle ==
TokenConditionalSwapDisplayPriceStyle . sellTokenPerBuyToken
) {
2023-08-13 03:01:20 -07:00
return roundTo5 ( sellTokenPerBuyTokenUi ) ;
}
// Stop loss / take profit
const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi ;
return roundTo5 ( buyTokenPerSellTokenUi ) ;
}
2023-08-10 04:32:06 -07:00
// in percent
2023-07-27 23:26:34 -07:00
getPricePremium ( ) : number {
2023-08-10 04:32:06 -07:00
return this . pricePremiumRate * 100 ;
2023-07-27 23:26:34 -07:00
}
2023-09-07 23:49:38 -07:00
getCurrentlySuggestedPremium ( group : Group ) : number {
const buyBank = this . getBuyToken ( group ) ;
const sellBank = this . getSellToken ( group ) ;
return TokenConditionalSwap . computePremium (
group ,
buyBank ,
sellBank ,
this . maxBuy ,
this . maxSell ,
this . getMaxBuyUi ( group ) ,
this . getMaxSellUi ( group ) ,
) ;
}
static computePremium (
group : Group ,
buyBank : Bank ,
sellBank : Bank ,
maxBuy : BN ,
maxSell : BN ,
maxBuyUi : number ,
maxSellUi : number ,
) : number {
const buyAmountInUsd =
maxBuy != U64_MAX_BN
? maxBuyUi * buyBank . uiPrice
: Number . MAX_SAFE_INTEGER ;
const sellAmountInUsd =
maxSell != U64_MAX_BN
? maxSellUi * sellBank . uiPrice
: Number . MAX_SAFE_INTEGER ;
// Used for computing optimal premium
let liqorTcsChunkSizeInUsd = Math . min ( buyAmountInUsd , sellAmountInUsd ) ;
if ( liqorTcsChunkSizeInUsd > 5000 ) {
liqorTcsChunkSizeInUsd = 5000 ;
}
// For small TCS swaps, reduce chunk size to 1000 USD
else {
liqorTcsChunkSizeInUsd = 1000 ;
}
const buyTokenPriceImpact = group . getPriceImpactByTokenIndex (
buyBank . tokenIndex ,
liqorTcsChunkSizeInUsd ,
) ;
const sellTokenPriceImpact = group . getPriceImpactByTokenIndex (
sellBank . tokenIndex ,
liqorTcsChunkSizeInUsd ,
) ;
return (
( ( 1 + buyTokenPriceImpact / 100 ) * ( 1 + sellTokenPriceImpact / 100 ) - 1 ) *
100
) ;
}
2023-07-27 23:26:34 -07:00
getBuyToken ( group : Group ) : Bank {
return group . getFirstBankByTokenIndex ( this . buyTokenIndex ) ;
}
getSellToken ( group : Group ) : Bank {
return group . getFirstBankByTokenIndex ( this . sellTokenIndex ) ;
}
getAllowCreatingDeposits ( ) : boolean {
return this . allowCreatingDeposits ;
}
getAllowCreatingBorrows ( ) : boolean {
return this . allowCreatingBorrows ;
}
toString ( group : Group ) : string {
2023-08-20 12:30:12 -07:00
return ` ${
group . getFirstBankByTokenIndex ( this . buyTokenIndex ) . name +
'/' +
group . getFirstBankByTokenIndex ( this . sellTokenIndex ) . name
} , getMaxBuy $ { this . getMaxBuyUi ( group ) } , getMaxSell $ { this . getMaxSellUi (
2023-07-27 23:26:34 -07:00
group ,
2023-08-20 12:30:12 -07:00
) } , bought $ { this . getBoughtUi ( group ) } , sold $ { this . getSoldUi (
2023-07-27 23:26:34 -07:00
group ,
) } , getPriceLowerLimitUi $ { this . getPriceLowerLimitUi (
group ,
) } , getPriceUpperLimitUi $ { this . getPriceUpperLimitUi (
group ,
2023-08-20 12:30:12 -07:00
) } , getCurrentPairPriceUi $ { this . getCurrentPairPriceUi (
group ,
2023-08-08 05:59:43 -07:00
) } , getThresholdPriceUi $ { this . getThresholdPriceUi (
group ,
2023-07-28 06:13:01 -07:00
) } , getPricePremium $ { this . getPricePremium ( ) } , expiry $ { this . expiryTimestamp . toString ( ) } ` ;
2023-07-27 23:26:34 -07:00
}
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 ,
2023-11-24 02:06:44 -08:00
public isConfigured : number ,
2023-07-03 05:09:11 -07:00
public allowCreatingDeposits : number ,
public allowCreatingBorrows : number ,
2023-09-08 03:15:50 -07:00
public displayPriceStyle : 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
}