client: fetch top settle pnl counterparties
This commit is contained in:
parent
9cbc352197
commit
11daf4d0eb
|
@ -7,22 +7,27 @@ use anchor_client::ClientError;
|
|||
use anchor_lang::AccountDeserialize;
|
||||
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::account::{AccountSharedData, ReadableAccount};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
use mango_v4::state::MangoAccountValue;
|
||||
|
||||
pub trait AccountFetcher: Sync + Send {
|
||||
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account>;
|
||||
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData>;
|
||||
fn fetch_program_accounts(
|
||||
&self,
|
||||
program: &Pubkey,
|
||||
discriminator: [u8; 8],
|
||||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>>;
|
||||
}
|
||||
|
||||
// Can't be in the trait, since then it would no longer be object-safe...
|
||||
pub fn account_fetcher_fetch_anchor_account<T: AccountDeserialize>(
|
||||
fetcher: &dyn AccountFetcher,
|
||||
address: Pubkey,
|
||||
address: &Pubkey,
|
||||
) -> anyhow::Result<T> {
|
||||
let account = fetcher.fetch_raw_account(address)?;
|
||||
let mut data: &[u8] = &account.data;
|
||||
let mut data: &[u8] = &account.data();
|
||||
T::try_deserialize(&mut data)
|
||||
.with_context(|| format!("deserializing anchor account {}", address))
|
||||
}
|
||||
|
@ -30,10 +35,10 @@ pub fn account_fetcher_fetch_anchor_account<T: AccountDeserialize>(
|
|||
// Can't be in the trait, since then it would no longer be object-safe...
|
||||
pub fn account_fetcher_fetch_mango_account(
|
||||
fetcher: &dyn AccountFetcher,
|
||||
address: Pubkey,
|
||||
address: &Pubkey,
|
||||
) -> anyhow::Result<MangoAccountValue> {
|
||||
let account = fetcher.fetch_raw_account(address)?;
|
||||
let data: &[u8] = &account.data;
|
||||
let data: &[u8] = &account.data();
|
||||
MangoAccountValue::from_bytes(&data[8..])
|
||||
.with_context(|| format!("deserializing mango account {}", address))
|
||||
}
|
||||
|
@ -43,26 +48,73 @@ pub struct RpcAccountFetcher {
|
|||
}
|
||||
|
||||
impl AccountFetcher for RpcAccountFetcher {
|
||||
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account> {
|
||||
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
||||
self.rpc
|
||||
.get_account_with_commitment(&address, self.rpc.commitment())
|
||||
.with_context(|| format!("fetch account {}", address))?
|
||||
.get_account_with_commitment(address, self.rpc.commitment())
|
||||
.with_context(|| format!("fetch account {}", *address))?
|
||||
.value
|
||||
.ok_or(ClientError::AccountNotFound)
|
||||
.with_context(|| format!("fetch account {}", address))
|
||||
.with_context(|| format!("fetch account {}", *address))
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn fetch_program_accounts(
|
||||
&self,
|
||||
program: &Pubkey,
|
||||
discriminator: [u8; 8],
|
||||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
||||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
|
||||
use solana_client::rpc_filter::{
|
||||
Memcmp, MemcmpEncodedBytes, MemcmpEncoding, RpcFilterType,
|
||||
};
|
||||
let config = RpcProgramAccountsConfig {
|
||||
filters: Some(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(discriminator.to_vec()),
|
||||
encoding: Some(MemcmpEncoding::Binary),
|
||||
})]),
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(self.rpc.commitment()),
|
||||
..RpcAccountInfoConfig::default()
|
||||
},
|
||||
with_context: Some(true),
|
||||
};
|
||||
let accs = self.rpc.get_program_accounts_with_config(program, config)?;
|
||||
// convert Account -> AccountSharedData
|
||||
Ok(accs
|
||||
.into_iter()
|
||||
.map(|(pk, acc)| (pk, acc.into()))
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
struct AccountCache {
|
||||
accounts: HashMap<Pubkey, AccountSharedData>,
|
||||
keys_for_program_and_discriminator: HashMap<(Pubkey, [u8; 8]), Vec<Pubkey>>,
|
||||
}
|
||||
|
||||
impl AccountCache {
|
||||
fn clear(&mut self) {
|
||||
self.accounts.clear();
|
||||
self.keys_for_program_and_discriminator.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CachedAccountFetcher<T: AccountFetcher> {
|
||||
fetcher: T,
|
||||
cache: Mutex<HashMap<Pubkey, Account>>,
|
||||
cache: Mutex<AccountCache>,
|
||||
}
|
||||
|
||||
impl<T: AccountFetcher> CachedAccountFetcher<T> {
|
||||
pub fn new(fetcher: T) -> Self {
|
||||
Self {
|
||||
fetcher,
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
cache: Mutex::new(AccountCache {
|
||||
accounts: HashMap::new(),
|
||||
keys_for_program_and_discriminator: HashMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,13 +125,38 @@ impl<T: AccountFetcher> CachedAccountFetcher<T> {
|
|||
}
|
||||
|
||||
impl<T: AccountFetcher> AccountFetcher for CachedAccountFetcher<T> {
|
||||
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account> {
|
||||
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
||||
let mut cache = self.cache.lock().unwrap();
|
||||
if let Some(account) = cache.get(&address) {
|
||||
if let Some(account) = cache.accounts.get(address) {
|
||||
return Ok(account.clone());
|
||||
}
|
||||
let account = self.fetcher.fetch_raw_account(address)?;
|
||||
cache.insert(address, account.clone());
|
||||
cache.accounts.insert(*address, account.clone());
|
||||
Ok(account)
|
||||
}
|
||||
|
||||
fn fetch_program_accounts(
|
||||
&self,
|
||||
program: &Pubkey,
|
||||
discriminator: [u8; 8],
|
||||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
||||
let cache_key = (*program, discriminator);
|
||||
let mut cache = self.cache.lock().unwrap();
|
||||
if let Some(accounts) = cache.keys_for_program_and_discriminator.get(&cache_key) {
|
||||
return Ok(accounts
|
||||
.iter()
|
||||
.map(|pk| (*pk, cache.accounts.get(&pk).unwrap().clone()))
|
||||
.collect::<Vec<_>>());
|
||||
}
|
||||
let accounts = self
|
||||
.fetcher
|
||||
.fetch_program_accounts(program, discriminator)?;
|
||||
cache
|
||||
.keys_for_program_and_discriminator
|
||||
.insert(cache_key, accounts.iter().map(|(pk, _)| *pk).collect());
|
||||
for (pk, acc) in accounts.iter() {
|
||||
cache.accounts.insert(*pk, acc.clone());
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,6 +224,16 @@ impl ChainData {
|
|||
.ok_or_else(|| anyhow::anyhow!("account {} has no live data", pubkey))
|
||||
}
|
||||
|
||||
pub fn iter_accounts<'a>(&'a self) -> impl Iterator<Item = (&'a Pubkey, &'a AccountAndSlot)> {
|
||||
self.accounts.iter().filter_map(|(pk, writes)| {
|
||||
writes
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|w| self.is_account_write_live(w))
|
||||
.map(|latest_write| (pk, latest_write))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn slots_count(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
|
|
|
@ -138,7 +138,31 @@ impl AccountFetcher {
|
|||
}
|
||||
|
||||
impl crate::AccountFetcher for AccountFetcher {
|
||||
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<solana_sdk::account::Account> {
|
||||
self.fetch_raw(&address).map(|a| a.into())
|
||||
fn fetch_raw_account(
|
||||
&self,
|
||||
address: &Pubkey,
|
||||
) -> anyhow::Result<solana_sdk::account::AccountSharedData> {
|
||||
self.fetch_raw(&address)
|
||||
}
|
||||
|
||||
fn fetch_program_accounts(
|
||||
&self,
|
||||
program: &Pubkey,
|
||||
discriminator: [u8; 8],
|
||||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
||||
let chain_data = self.chain_data.read().unwrap();
|
||||
Ok(chain_data
|
||||
.iter_accounts()
|
||||
.filter_map(|(pk, data)| {
|
||||
if data.account.owner() != program {
|
||||
return None;
|
||||
}
|
||||
let acc_data = data.account.data();
|
||||
if acc_data.len() < 8 || acc_data[..8] != discriminator {
|
||||
return None;
|
||||
}
|
||||
Some((*pk, data.account.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,13 @@ use solana_client::rpc_client::RpcClient;
|
|||
use solana_sdk::signer::keypair;
|
||||
|
||||
use crate::account_fetcher::*;
|
||||
use crate::context::{MangoGroupContext, PerpMarketContext, Serum3MarketContext, TokenContext};
|
||||
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
|
||||
use crate::gpa::fetch_mango_accounts;
|
||||
use crate::jupiter;
|
||||
use crate::util::MyClone;
|
||||
|
||||
use anyhow::Context;
|
||||
use solana_sdk::account::ReadableAccount;
|
||||
use solana_sdk::instruction::{AccountMeta, Instruction};
|
||||
use solana_sdk::signature::{Keypair, Signature};
|
||||
use solana_sdk::sysvar;
|
||||
|
@ -230,7 +231,7 @@ impl MangoClient {
|
|||
) -> anyhow::Result<Self> {
|
||||
let rpc = client.rpc();
|
||||
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
|
||||
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, account)?;
|
||||
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, &account)?;
|
||||
let group = mango_account.fixed.group;
|
||||
if mango_account.fixed.owner != owner.pubkey() {
|
||||
anyhow::bail!(
|
||||
|
@ -290,12 +291,12 @@ impl MangoClient {
|
|||
}
|
||||
|
||||
pub fn mango_account(&self) -> anyhow::Result<MangoAccountValue> {
|
||||
account_fetcher_fetch_mango_account(&*self.account_fetcher, self.mango_account_address)
|
||||
account_fetcher_fetch_mango_account(&*self.account_fetcher, &self.mango_account_address)
|
||||
}
|
||||
|
||||
pub fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
|
||||
let bank_address = self.context.mint_info(token_index).first_bank();
|
||||
account_fetcher_fetch_anchor_account(&*self.account_fetcher, bank_address)
|
||||
account_fetcher_fetch_anchor_account(&*self.account_fetcher, &bank_address)
|
||||
}
|
||||
|
||||
pub fn derive_health_check_remaining_account_metas(
|
||||
|
@ -461,8 +462,8 @@ impl MangoClient {
|
|||
) -> Result<pyth_sdk_solana::Price, anyhow::Error> {
|
||||
let token_index = *self.context.token_indexes_by_name.get(token_name).unwrap();
|
||||
let mint_info = self.context.mint_info(token_index);
|
||||
let oracle_account = self.account_fetcher.fetch_raw_account(mint_info.oracle)?;
|
||||
Ok(pyth_sdk_solana::load_price(&oracle_account.data).unwrap())
|
||||
let oracle_account = self.account_fetcher.fetch_raw_account(&mint_info.oracle)?;
|
||||
Ok(pyth_sdk_solana::load_price(&oracle_account.data()).unwrap())
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -719,8 +720,8 @@ impl MangoClient {
|
|||
.unwrap();
|
||||
let account = self.mango_account()?;
|
||||
let open_orders = account.serum3_orders(market_index).unwrap().open_orders;
|
||||
|
||||
let open_orders_bytes = self.account_fetcher.fetch_raw_account(open_orders)?.data;
|
||||
let open_orders_acc = self.account_fetcher.fetch_raw_account(&open_orders)?;
|
||||
let open_orders_bytes = open_orders_acc.data();
|
||||
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
|
||||
&open_orders_bytes[5..5 + std::mem::size_of::<serum_dex::state::OpenOrders>()],
|
||||
);
|
||||
|
@ -838,11 +839,45 @@ impl MangoClient {
|
|||
//
|
||||
// Perps
|
||||
//
|
||||
fn perp_data_by_market_index(
|
||||
pub fn perp_settle_pnl(
|
||||
&self,
|
||||
market_index: PerpMarketIndex,
|
||||
) -> Result<&PerpMarketContext, ClientError> {
|
||||
Ok(self.context.perp_markets.get(&market_index).unwrap())
|
||||
account_a: &Pubkey,
|
||||
account_b: (&Pubkey, &MangoAccountValue),
|
||||
) -> anyhow::Result<Signature> {
|
||||
let perp = self.context.perp(market_index);
|
||||
let settlement_token = self.context.token(0);
|
||||
|
||||
let health_remaining_ams = self
|
||||
.context
|
||||
.derive_health_check_remaining_account_metas(account_b.1, vec![], false)
|
||||
.unwrap();
|
||||
|
||||
self.program()
|
||||
.request()
|
||||
.instruction(Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::PerpSettlePnl {
|
||||
group: self.group(),
|
||||
perp_market: perp.address,
|
||||
account_a: *account_a,
|
||||
account_b: *account_b.0,
|
||||
oracle: perp.market.oracle,
|
||||
quote_bank: settlement_token.mint_info.first_bank(),
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(health_remaining_ams.into_iter());
|
||||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpSettlePnl {
|
||||
max_settle_amount: u64::MAX,
|
||||
}),
|
||||
})
|
||||
.send()
|
||||
.map_err(prettify_client_error)
|
||||
}
|
||||
|
||||
pub fn perp_liq_force_cancel_orders(
|
||||
|
@ -850,7 +885,7 @@ impl MangoClient {
|
|||
liqee: (&Pubkey, &MangoAccountValue),
|
||||
market_index: PerpMarketIndex,
|
||||
) -> anyhow::Result<Signature> {
|
||||
let perp = self.perp_data_by_market_index(market_index)?;
|
||||
let perp = self.context.perp(market_index);
|
||||
|
||||
let health_remaining_ams = self
|
||||
.context
|
||||
|
@ -890,7 +925,7 @@ impl MangoClient {
|
|||
market_index: PerpMarketIndex,
|
||||
max_base_transfer: i64,
|
||||
) -> anyhow::Result<Signature> {
|
||||
let perp = self.perp_data_by_market_index(market_index)?;
|
||||
let perp = self.context.perp(market_index);
|
||||
|
||||
let health_remaining_ams = self
|
||||
.context
|
||||
|
@ -1002,7 +1037,7 @@ impl MangoClient {
|
|||
|
||||
let group = account_fetcher_fetch_anchor_account::<Group>(
|
||||
&*self.account_fetcher,
|
||||
self.context.group,
|
||||
&self.context.group,
|
||||
)?;
|
||||
|
||||
self.program()
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
use crate::{AccountFetcher, MangoGroupContext};
|
||||
use anyhow::Context;
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::{FixedOrderAccountRetriever, HealthCache, MangoAccountValue};
|
||||
|
||||
pub fn new(
|
||||
context: &MangoGroupContext,
|
||||
account_fetcher: &impl AccountFetcher,
|
||||
account: &MangoAccountValue,
|
||||
) -> anyhow::Result<HealthCache> {
|
||||
let active_token_len = account.active_token_positions().count();
|
||||
let active_perp_len = account.active_perp_positions().count();
|
||||
|
||||
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
|
||||
let accounts = metas
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
Ok(KeyedAccountSharedData::new(
|
||||
meta.pubkey,
|
||||
account_fetcher.fetch_raw_account(&meta.pubkey)?,
|
||||
))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
|
||||
let retriever = FixedOrderAccountRetriever {
|
||||
ais: accounts,
|
||||
n_banks: active_token_len,
|
||||
n_perps: active_perp_len,
|
||||
begin_perp: active_token_len * 2,
|
||||
begin_serum3: active_token_len * 2 + active_perp_len,
|
||||
};
|
||||
mango_v4::state::new_health_cache(&account.borrow(), &retriever).context("make health cache")
|
||||
}
|
|
@ -9,5 +9,7 @@ mod chain_data_fetcher;
|
|||
mod client;
|
||||
mod context;
|
||||
mod gpa;
|
||||
pub mod health_cache;
|
||||
mod jupiter;
|
||||
pub mod perp_pnl;
|
||||
mod util;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::Discriminator;
|
||||
use fixed::types::I80F48;
|
||||
use solana_sdk::account::ReadableAccount;
|
||||
|
||||
use crate::*;
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Direction {
|
||||
MaxPositive,
|
||||
MaxNegative,
|
||||
}
|
||||
|
||||
/// Returns up to `count` accounts with highest abs pnl (by `direction`) in descending order.
|
||||
pub fn fetch_top(
|
||||
context: &crate::context::MangoGroupContext,
|
||||
account_fetcher: &impl AccountFetcher,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
perp_market_address: &Pubkey,
|
||||
direction: Direction,
|
||||
count: usize,
|
||||
) -> anyhow::Result<Vec<(Pubkey, MangoAccountValue, I80F48)>> {
|
||||
let perp_market =
|
||||
account_fetcher_fetch_anchor_account::<PerpMarket>(account_fetcher, perp_market_address)?;
|
||||
let oracle_acc = account_fetcher.fetch_raw_account(&perp_market.oracle)?;
|
||||
let oracle_price =
|
||||
perp_market.oracle_price(&KeyedAccountSharedData::new(perp_market.oracle, oracle_acc))?;
|
||||
|
||||
let accounts =
|
||||
account_fetcher.fetch_program_accounts(&mango_v4::id(), MangoAccount::discriminator())?;
|
||||
|
||||
let mut accounts_pnl = accounts
|
||||
.iter()
|
||||
.filter_map(|(pk, acc)| {
|
||||
let data = acc.data();
|
||||
let mango_acc = MangoAccountValue::from_bytes(&data[8..]);
|
||||
if mango_acc.is_err() {
|
||||
return None;
|
||||
}
|
||||
let mango_acc = mango_acc.unwrap();
|
||||
let perp_pos = mango_acc.perp_position(perp_market_index);
|
||||
if perp_pos.is_err() {
|
||||
return None;
|
||||
}
|
||||
let perp_pos = perp_pos.unwrap();
|
||||
let pnl = perp_pos.base_position_native(&perp_market) * oracle_price
|
||||
+ perp_pos.quote_position_native();
|
||||
if pnl >= 0 && direction == Direction::MaxNegative
|
||||
|| pnl <= 0 && direction == Direction::MaxPositive
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some((*pk, mango_acc, pnl))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort the top accounts to the front
|
||||
match direction {
|
||||
Direction::MaxPositive => {
|
||||
accounts_pnl.sort_by(|a, b| b.2.cmp(&a.2));
|
||||
}
|
||||
Direction::MaxNegative => {
|
||||
accounts_pnl.sort_by(|a, b| a.2.cmp(&b.2));
|
||||
}
|
||||
}
|
||||
|
||||
// Negative pnl needs to be limited by spot_health.
|
||||
// We're doing it in a second step, because it's pretty expensive and we don't
|
||||
// want to run this for all accounts.
|
||||
if direction == Direction::MaxNegative {
|
||||
let mut stable = 0;
|
||||
for i in 0..accounts_pnl.len() {
|
||||
let (_, acc, pnl) = &accounts_pnl[i];
|
||||
let next_pnl = if i + 1 < accounts_pnl.len() {
|
||||
accounts_pnl[i + 1].2
|
||||
} else {
|
||||
I80F48::ZERO
|
||||
};
|
||||
let spot_health = crate::health_cache::new(context, account_fetcher, &acc)?
|
||||
.spot_health(HealthType::Maint);
|
||||
let settleable_pnl = if spot_health > 0 && !acc.being_liquidated() {
|
||||
(*pnl).max(-spot_health)
|
||||
} else {
|
||||
I80F48::ZERO
|
||||
};
|
||||
accounts_pnl[i].2 = settleable_pnl;
|
||||
|
||||
// if the ordering was unchanged `count` times we know we have the top `count` accounts
|
||||
if settleable_pnl <= next_pnl {
|
||||
stable += 1;
|
||||
if stable >= count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
accounts_pnl.sort_by(|a, b| a.2.cmp(&b.2));
|
||||
}
|
||||
|
||||
// return highest abs pnl accounts
|
||||
Ok(accounts_pnl[0..count].to_vec())
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
use mango_v4::accounts_zerocopy::{AccountReader, KeyedAccountReader};
|
||||
use solana_sdk::{account::AccountSharedData, pubkey::Pubkey};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyedAccountSharedData {
|
||||
pub key: Pubkey,
|
||||
pub data: AccountSharedData,
|
||||
}
|
||||
|
||||
impl KeyedAccountSharedData {
|
||||
pub fn new(key: Pubkey, data: AccountSharedData) -> Self {
|
||||
Self { key, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountReader for KeyedAccountSharedData {
|
||||
fn owner(&self) -> &Pubkey {
|
||||
AccountReader::owner(&self.data)
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
AccountReader::data(&self.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyedAccountReader for KeyedAccountSharedData {
|
||||
fn key(&self) -> &Pubkey {
|
||||
&self.key
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::account_shared_data::KeyedAccountSharedData;
|
||||
|
||||
use client::{chain_data, AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
|
||||
use client::{chain_data, health_cache, AccountFetcher, MangoClient, MangoClientError};
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::{
|
||||
new_health_cache, Bank, FixedOrderAccountRetriever, HealthCache, HealthType, MangoAccountValue,
|
||||
PerpMarketIndex, Serum3Orders, Side, TokenIndex, QUOTE_TOKEN_INDEX,
|
||||
Bank, HealthType, PerpMarketIndex, Serum3Orders, Side, TokenIndex, QUOTE_TOKEN_INDEX,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
@ -17,35 +15,6 @@ pub struct Config {
|
|||
pub refresh_timeout: Duration,
|
||||
}
|
||||
|
||||
pub fn new_health_cache_(
|
||||
context: &MangoGroupContext,
|
||||
account_fetcher: &chain_data::AccountFetcher,
|
||||
account: &MangoAccountValue,
|
||||
) -> anyhow::Result<HealthCache> {
|
||||
let active_token_len = account.active_token_positions().count();
|
||||
let active_perp_len = account.active_perp_positions().count();
|
||||
|
||||
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
|
||||
let accounts = metas
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
Ok(KeyedAccountSharedData::new(
|
||||
meta.pubkey,
|
||||
account_fetcher.fetch_raw(&meta.pubkey)?,
|
||||
))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
|
||||
let retriever = FixedOrderAccountRetriever {
|
||||
ais: accounts,
|
||||
n_banks: active_token_len,
|
||||
n_perps: active_perp_len,
|
||||
begin_perp: active_token_len * 2,
|
||||
begin_serum3: active_token_len * 2 + active_perp_len,
|
||||
};
|
||||
new_health_cache(&account.borrow(), &retriever).context("make health cache")
|
||||
}
|
||||
|
||||
pub fn jupiter_market_can_buy(
|
||||
mango_client: &MangoClient,
|
||||
token: TokenIndex,
|
||||
|
@ -112,7 +81,7 @@ pub fn maybe_liquidate_account(
|
|||
|
||||
let account = account_fetcher.fetch_mango_account(pubkey)?;
|
||||
let health_cache =
|
||||
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
|
||||
health_cache::new(&mango_client.context, account_fetcher, &account).expect("always ok");
|
||||
let maint_health = health_cache.health(HealthType::Maint);
|
||||
if !health_cache.is_liquidatable() {
|
||||
return Ok(false);
|
||||
|
@ -130,7 +99,7 @@ pub fn maybe_liquidate_account(
|
|||
// be great at providing timely updates to the account data.
|
||||
let account = account_fetcher.fetch_fresh_mango_account(pubkey)?;
|
||||
let health_cache =
|
||||
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
|
||||
health_cache::new(&mango_client.context, account_fetcher, &account).expect("always ok");
|
||||
if !health_cache.is_liquidatable() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
@ -145,7 +114,7 @@ pub fn maybe_liquidate_account(
|
|||
.map(|token_position| {
|
||||
let token = mango_client.context.token(token_position.token_index);
|
||||
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
|
||||
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
|
||||
let oracle = account_fetcher.fetch_raw_account(&token.mint_info.oracle)?;
|
||||
let price = bank.oracle_price(&KeyedAccountSharedData::new(
|
||||
token.mint_info.oracle,
|
||||
oracle.into(),
|
||||
|
@ -163,7 +132,7 @@ pub fn maybe_liquidate_account(
|
|||
let serum_force_cancels = account
|
||||
.active_serum3_orders()
|
||||
.map(|orders| {
|
||||
let open_orders_account = account_fetcher.fetch_raw_account(orders.open_orders)?;
|
||||
let open_orders_account = account_fetcher.fetch_raw_account(&orders.open_orders)?;
|
||||
let open_orders = mango_v4::serum3_cpi::load_open_orders(&open_orders_account)?;
|
||||
let can_force_cancel = open_orders.native_coin_total > 0
|
||||
|| open_orders.native_pc_total > 0
|
||||
|
@ -190,7 +159,7 @@ pub fn maybe_liquidate_account(
|
|||
return Ok(None);
|
||||
}
|
||||
let perp = mango_client.context.perp(pp.market_index);
|
||||
let oracle = account_fetcher.fetch_raw_account(perp.market.oracle)?;
|
||||
let oracle = account_fetcher.fetch_raw_account(&perp.market.oracle)?;
|
||||
let price = perp.market.oracle_price(&KeyedAccountSharedData::new(
|
||||
perp.market.oracle,
|
||||
oracle.into(),
|
||||
|
@ -218,7 +187,7 @@ pub fn maybe_liquidate_account(
|
|||
liqor.ensure_token_position(target)?;
|
||||
|
||||
let health_cache =
|
||||
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");
|
||||
health_cache::new(&mango_client.context, account_fetcher, &liqor).expect("always ok");
|
||||
|
||||
let source_price = health_cache.token_info(source).unwrap().oracle_price;
|
||||
let target_price = health_cache.token_info(target).unwrap().oracle_price;
|
||||
|
@ -278,7 +247,7 @@ pub fn maybe_liquidate_account(
|
|||
.fetch_fresh_mango_account(&mango_client.mango_account_address)
|
||||
.context("getting liquidator account")?;
|
||||
liqor.ensure_perp_position(*perp_market_index, QUOTE_TOKEN_INDEX)?;
|
||||
let health_cache = new_health_cache_(&mango_client.context, account_fetcher, &liqor)
|
||||
let health_cache = health_cache::new(&mango_client.context, account_fetcher, &liqor)
|
||||
.expect("always ok");
|
||||
health_cache.max_perp_for_health_ratio(
|
||||
*perp_market_index,
|
||||
|
|
|
@ -13,7 +13,6 @@ use solana_sdk::commitment_config::CommitmentConfig;
|
|||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub mod account_shared_data;
|
||||
pub mod liquidate;
|
||||
pub mod metrics;
|
||||
pub mod rebalance;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{account_shared_data::KeyedAccountSharedData, AnyhowWrap};
|
||||
use crate::AnyhowWrap;
|
||||
|
||||
use client::{chain_data, AccountFetcher, MangoClient, TokenContext};
|
||||
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||
use mango_v4::state::{Bank, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX};
|
||||
|
||||
use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
|
||||
|
@ -43,7 +44,7 @@ impl TokenState {
|
|||
bank: &Bank,
|
||||
account_fetcher: &chain_data::AccountFetcher,
|
||||
) -> anyhow::Result<I80F48> {
|
||||
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
|
||||
let oracle = account_fetcher.fetch_raw_account(&token.mint_info.oracle)?;
|
||||
bank.oracle_price(&KeyedAccountSharedData::new(
|
||||
token.mint_info.oracle,
|
||||
oracle.into(),
|
||||
|
|
|
@ -110,6 +110,63 @@ impl<T: solana_sdk::account::ReadableAccount> AccountReader for T {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
#[derive(Clone)]
|
||||
pub struct KeyedAccount {
|
||||
pub key: Pubkey,
|
||||
pub account: solana_sdk::account::Account,
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
impl AccountReader for KeyedAccount {
|
||||
fn owner(&self) -> &Pubkey {
|
||||
self.account.owner()
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
self.account.data()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
impl KeyedAccountReader for KeyedAccount {
|
||||
fn key(&self) -> &Pubkey {
|
||||
&self.key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
#[derive(Clone)]
|
||||
pub struct KeyedAccountSharedData {
|
||||
pub key: Pubkey,
|
||||
pub data: solana_sdk::account::AccountSharedData,
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
impl KeyedAccountSharedData {
|
||||
pub fn new(key: Pubkey, data: solana_sdk::account::AccountSharedData) -> Self {
|
||||
Self { key, data }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
impl AccountReader for KeyedAccountSharedData {
|
||||
fn owner(&self) -> &Pubkey {
|
||||
AccountReader::owner(&self.data)
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
AccountReader::data(&self.data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "solana-sdk")]
|
||||
impl KeyedAccountReader for KeyedAccountSharedData {
|
||||
fn key(&self) -> &Pubkey {
|
||||
&self.key
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Common traits for loading from account data.
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue