Serum: more health computation
Now the open orders are actually read
This commit is contained in:
parent
6aa4724b45
commit
0593aa81f7
|
@ -1,9 +1,10 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::dex::serum_dex;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use std::cell::Ref;
|
use std::cell::Ref;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::{oracle_price, Bank, MangoAccount, TokenIndex};
|
use crate::state::{oracle_price, Bank, MangoAccount};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
use crate::util::LoadZeroCopy;
|
use crate::util::LoadZeroCopy;
|
||||||
|
@ -21,17 +22,26 @@ pub fn compute_health(account: &MangoAccount, ais: &[AccountInfo]) -> Result<I80
|
||||||
compute_health_detail(account, banks, oracles, serum_oos)
|
compute_health_detail(account, banks, oracles, serum_oos)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BankAndPrice<'a> {
|
struct TokenInfo<'a> {
|
||||||
bank: Ref<'a, Bank>,
|
bank: Ref<'a, Bank>,
|
||||||
price: I80F48,
|
oracle_price: I80F48, // native/native
|
||||||
|
// in native tokens, summing token deposits/borrows and serum open orders
|
||||||
|
balance: I80F48,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_price(token_index: TokenIndex, banks_and_prices: &[BankAndPrice]) -> Result<I80F48> {
|
fn strip_dex_padding<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, [u8]>> {
|
||||||
Ok(banks_and_prices
|
require!(acc.data_len() >= 12, MangoError::SomeError);
|
||||||
.iter()
|
let unpadded_data: Ref<[u8]> = Ref::map(acc.try_borrow_data()?, |data| {
|
||||||
.find(|b| b.bank.token_index == token_index)
|
let data_len = data.len() - 12;
|
||||||
.ok_or(error!(MangoError::SomeError))?
|
let (_, rest) = data.split_at(5);
|
||||||
.price)
|
let (mid, _) = rest.split_at(data_len);
|
||||||
|
mid
|
||||||
|
});
|
||||||
|
Ok(unpadded_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_open_orders<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
|
||||||
|
Ok(Ref::map(strip_dex_padding(acc)?, bytemuck::from_bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_health_detail(
|
fn compute_health_detail(
|
||||||
|
@ -40,24 +50,26 @@ fn compute_health_detail(
|
||||||
oracles: &[AccountInfo],
|
oracles: &[AccountInfo],
|
||||||
serum_oos: &[AccountInfo],
|
serum_oos: &[AccountInfo],
|
||||||
) -> Result<I80F48> {
|
) -> Result<I80F48> {
|
||||||
let mut assets = I80F48::ZERO;
|
|
||||||
let mut liabilities = I80F48::ZERO; // absolute value
|
|
||||||
|
|
||||||
// collect the bank and oracle data once
|
// collect the bank and oracle data once
|
||||||
let banks_and_prices = util::zip!(banks.iter(), oracles.iter())
|
let mut token_infos = util::zip!(banks.iter(), oracles.iter())
|
||||||
.map(|(bank_ai, oracle_ai)| {
|
.map(|(bank_ai, oracle_ai)| {
|
||||||
let bank = bank_ai.load::<Bank>()?;
|
let bank = bank_ai.load::<Bank>()?;
|
||||||
require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle);
|
require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle);
|
||||||
let price = oracle_price(oracle_ai)?;
|
let oracle_price = oracle_price(oracle_ai)?;
|
||||||
Ok(BankAndPrice { bank, price })
|
Ok(TokenInfo {
|
||||||
|
bank,
|
||||||
|
oracle_price,
|
||||||
|
balance: I80F48::ZERO,
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<BankAndPrice>>>()?;
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
// health contribution from token accounts
|
// token contribution from token accounts
|
||||||
for (position, BankAndPrice { bank, price }) in util::zip!(
|
for (position, token_info) in util::zip!(
|
||||||
account.token_account_map.iter_active(),
|
account.token_account_map.iter_active(),
|
||||||
banks_and_prices.iter()
|
token_infos.iter_mut()
|
||||||
) {
|
) {
|
||||||
|
let bank = &token_info.bank;
|
||||||
// This assumes banks are passed in order
|
// This assumes banks are passed in order
|
||||||
require!(
|
require!(
|
||||||
bank.token_index == position.token_index,
|
bank.token_index == position.token_index,
|
||||||
|
@ -66,17 +78,11 @@ fn compute_health_detail(
|
||||||
|
|
||||||
// converts the token value to the basis token value for health computations
|
// converts the token value to the basis token value for health computations
|
||||||
// TODO: health basis token == USDC?
|
// TODO: health basis token == USDC?
|
||||||
let price = *price;
|
let native = position.native(&bank);
|
||||||
let native_position = position.native(&bank);
|
token_info.balance = cm!(token_info.balance + native);
|
||||||
let native_basis = cm!(native_position * price);
|
|
||||||
if native_basis.is_positive() {
|
|
||||||
assets = cm!(assets + bank.init_asset_weight * native_basis);
|
|
||||||
} else {
|
|
||||||
liabilities = cm!(liabilities - bank.init_liab_weight * native_basis);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// health contribution from serum accounts
|
// token contribution from serum accounts
|
||||||
for (serum_account, oo_ai) in
|
for (serum_account, oo_ai) in
|
||||||
util::zip!(account.serum_account_map.iter_active(), serum_oos.iter())
|
util::zip!(account.serum_account_map.iter_active(), serum_oos.iter())
|
||||||
{
|
{
|
||||||
|
@ -87,10 +93,48 @@ fn compute_health_detail(
|
||||||
);
|
);
|
||||||
|
|
||||||
// find the prices for the market
|
// find the prices for the market
|
||||||
// TODO: each of these is a linear scan through banks_and_prices - is that too expensive?
|
// TODO: each of these is a linear scan - is that too expensive?
|
||||||
let _base_price = find_price(serum_account.base_token_index, &banks_and_prices)?;
|
let base_index = token_infos
|
||||||
let _quote_price = find_price(serum_account.quote_token_index, &banks_and_prices)?;
|
.iter()
|
||||||
|
.position(|ti| ti.bank.token_index == serum_account.base_token_index)
|
||||||
|
.ok_or(error!(MangoError::SomeError))?;
|
||||||
|
let quote_index = token_infos
|
||||||
|
.iter()
|
||||||
|
.position(|ti| ti.bank.token_index == serum_account.quote_token_index)
|
||||||
|
.ok_or(error!(MangoError::SomeError))?;
|
||||||
|
|
||||||
|
let oo = load_open_orders(oo_ai)?;
|
||||||
|
|
||||||
|
// add the amounts that are freely settleable
|
||||||
|
token_infos[base_index].balance += I80F48::from_num(oo.native_coin_free);
|
||||||
|
token_infos[quote_index].balance +=
|
||||||
|
I80F48::from_num(oo.native_pc_free + oo.referrer_rebates_accrued);
|
||||||
|
|
||||||
|
// for the amounts that are reserved for orders, compute the worst case for health
|
||||||
|
// by checking if everything-is-base or everything-is-quote produces worse
|
||||||
|
// outcomes
|
||||||
|
// TODO: that kind of approach may no longer be possible with each
|
||||||
|
// market potentially having two different tokens involved?
|
||||||
|
let reserved_base = oo.native_coin_total - oo.native_coin_free;
|
||||||
|
let reserved_quote = oo.native_pc_total - oo.native_pc_free;
|
||||||
|
// TODO: do it, this is just a stub
|
||||||
|
token_infos[base_index].balance += I80F48::from_num(reserved_base);
|
||||||
|
token_infos[quote_index].balance += I80F48::from_num(reserved_quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cm!(assets - liabilities))
|
// convert the token balance to health
|
||||||
|
let mut asset_health = I80F48::ZERO;
|
||||||
|
let mut liability_health = I80F48::ZERO; // positive
|
||||||
|
for token_info in token_infos.iter() {
|
||||||
|
let bank = &token_info.bank;
|
||||||
|
if token_info.balance.is_negative() {
|
||||||
|
liability_health = cm!(liability_health
|
||||||
|
- bank.init_liab_weight * token_info.balance * token_info.oracle_price);
|
||||||
|
} else {
|
||||||
|
asset_health = cm!(asset_health
|
||||||
|
+ bank.init_asset_weight * token_info.balance * token_info.oracle_price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cm!(asset_health - liability_health))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue