2022-08-05 11:28:14 -07:00
|
|
|
use crate::{account_shared_data::KeyedAccountSharedData, AnyhowWrap};
|
|
|
|
|
|
|
|
use client::{chain_data, AccountFetcher, MangoClient, TokenContext};
|
|
|
|
use mango_v4::state::{oracle_price, Bank, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX};
|
|
|
|
|
|
|
|
use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
|
|
|
|
|
2022-08-07 11:04:19 -07:00
|
|
|
use std::{collections::HashMap, time::Duration};
|
2022-08-05 11:28:14 -07:00
|
|
|
|
|
|
|
pub struct Config {
|
|
|
|
pub slippage: f64,
|
2022-08-07 11:04:19 -07:00
|
|
|
pub refresh_timeout: Duration,
|
2022-08-05 11:28:14 -07:00
|
|
|
}
|
|
|
|
|
2022-08-07 11:04:19 -07:00
|
|
|
#[derive(Debug)]
|
2022-08-05 11:28:14 -07:00
|
|
|
struct TokenState {
|
|
|
|
_price: I80F48,
|
|
|
|
native_position: I80F48,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TokenState {
|
|
|
|
fn new_position(
|
|
|
|
token: &TokenContext,
|
|
|
|
position: &TokenPosition,
|
|
|
|
account_fetcher: &chain_data::AccountFetcher,
|
|
|
|
) -> anyhow::Result<Self> {
|
|
|
|
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
|
|
|
|
Ok(Self {
|
|
|
|
_price: Self::fetch_price(token, &bank, account_fetcher)?,
|
|
|
|
native_position: position.native(&bank),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fetch_price(
|
|
|
|
token: &TokenContext,
|
|
|
|
bank: &Bank,
|
|
|
|
account_fetcher: &chain_data::AccountFetcher,
|
|
|
|
) -> anyhow::Result<I80F48> {
|
|
|
|
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
|
|
|
|
oracle_price(
|
|
|
|
&KeyedAccountSharedData::new(token.mint_info.oracle, oracle.into()),
|
|
|
|
bank.oracle_config.conf_filter,
|
|
|
|
bank.mint_decimals,
|
|
|
|
)
|
|
|
|
.map_err_anyhow()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn zero_all_non_quote(
|
|
|
|
mango_client: &MangoClient,
|
|
|
|
account_fetcher: &chain_data::AccountFetcher,
|
|
|
|
mango_account_address: &Pubkey,
|
|
|
|
config: &Config,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
log::trace!("checking for rebalance: {}", mango_account_address);
|
|
|
|
|
|
|
|
// TODO: configurable?
|
|
|
|
let quote_token = mango_client.context.token(QUOTE_TOKEN_INDEX);
|
|
|
|
|
|
|
|
let account = account_fetcher.fetch_mango_account(mango_account_address)?;
|
|
|
|
|
|
|
|
let tokens = account
|
|
|
|
.token_iter_active()
|
|
|
|
.map(|token_position| {
|
|
|
|
let token = mango_client.context.token(token_position.token_index);
|
|
|
|
Ok((
|
|
|
|
token.token_index,
|
|
|
|
TokenState::new_position(token, token_position, account_fetcher)?,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
.collect::<anyhow::Result<HashMap<TokenIndex, TokenState>>>()?;
|
2022-08-07 11:04:19 -07:00
|
|
|
log::trace!("account tokens: {:?}", tokens);
|
2022-08-05 11:28:14 -07:00
|
|
|
|
2022-08-07 11:04:19 -07:00
|
|
|
let mut txsigs = vec![];
|
2022-08-05 11:28:14 -07:00
|
|
|
for (token_index, token_state) in tokens {
|
|
|
|
let token = mango_client.context.token(token_index);
|
|
|
|
if token_index == quote_token.token_index {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if token_state.native_position > 0 {
|
2022-08-07 11:04:19 -07:00
|
|
|
let amount = token_state.native_position;
|
|
|
|
let txsig = mango_client.jupiter_swap(
|
2022-08-05 11:28:14 -07:00
|
|
|
token.mint_info.mint,
|
|
|
|
quote_token.mint_info.mint,
|
2022-08-07 11:04:19 -07:00
|
|
|
amount.to_num::<u64>(),
|
2022-08-05 11:28:14 -07:00
|
|
|
config.slippage,
|
|
|
|
client::JupiterSwapMode::ExactIn,
|
|
|
|
)?;
|
2022-08-07 11:04:19 -07:00
|
|
|
log::info!(
|
|
|
|
"sold {} {} for {} in tx {}",
|
|
|
|
token.native_to_ui(amount),
|
|
|
|
token.name,
|
|
|
|
quote_token.name,
|
|
|
|
txsig
|
|
|
|
);
|
|
|
|
txsigs.push(txsig);
|
2022-08-05 11:28:14 -07:00
|
|
|
} else if token_state.native_position < 0 {
|
2022-08-07 11:04:19 -07:00
|
|
|
let amount = -token_state.native_position;
|
|
|
|
let txsig = mango_client.jupiter_swap(
|
2022-08-05 11:28:14 -07:00
|
|
|
quote_token.mint_info.mint,
|
|
|
|
token.mint_info.mint,
|
2022-08-07 11:04:19 -07:00
|
|
|
amount.to_num::<u64>(),
|
2022-08-05 11:28:14 -07:00
|
|
|
config.slippage,
|
|
|
|
client::JupiterSwapMode::ExactOut,
|
|
|
|
)?;
|
2022-08-07 11:04:19 -07:00
|
|
|
log::info!(
|
|
|
|
"bought {} {} for {} in tx {}",
|
|
|
|
token.native_to_ui(amount),
|
|
|
|
token.name,
|
|
|
|
quote_token.name,
|
|
|
|
txsig
|
|
|
|
);
|
|
|
|
txsigs.push(txsig);
|
2022-08-05 11:28:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-07 11:04:19 -07:00
|
|
|
let max_slot = account_fetcher.transaction_max_slot(&txsigs)?;
|
|
|
|
if let Err(e) = account_fetcher.refresh_accounts_via_rpc_until_slot(
|
|
|
|
&[*mango_account_address],
|
|
|
|
max_slot,
|
|
|
|
config.refresh_timeout,
|
|
|
|
) {
|
|
|
|
// If we don't get fresh data, maybe the tx landed on a fork?
|
|
|
|
// Rebalance is technically still ok.
|
|
|
|
log::info!("could not refresh account data: {}", e);
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:28:14 -07:00
|
|
|
Ok(())
|
|
|
|
}
|