mango-v4/bin/liquidator/src/token_swap_info.rs

186 lines
5.6 KiB
Rust

use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use itertools::Itertools;
use tracing::*;
use mango_v4::state::TokenIndex;
use mango_v4_client::jupiter;
use mango_v4_client::MangoClient;
pub struct Config {
pub quote_index: TokenIndex,
pub quote_amount: u64,
pub jupiter_version: jupiter::Version,
}
#[derive(Clone)]
pub struct TokenSwapInfo {
pub last_update: std::time::SystemTime,
// in USDC per token, not the literal oracle price (which is in USD per token)
pub quote_per_token_oracle: f64,
pub quote_per_token_buy: f64,
pub quote_per_token_sell: f64,
}
impl TokenSwapInfo {
/// multiplier to the oracle price for executing a buy, so 1.5 would mean buying 50% over oracle price
pub fn buy_over_oracle(&self) -> f64 {
self.quote_per_token_buy / self.quote_per_token_oracle
}
/// multiplier to the oracle price for executing a sell,
/// but with the price inverted, so values > 1 mean a worse deal than oracle price
pub fn sell_over_oracle(&self) -> f64 {
self.quote_per_token_oracle / self.quote_per_token_sell
}
}
/// Track the buy/sell slippage for tokens
///
/// Needed to evaluate whether a token conditional swap premium might be good enough
/// without having to query each time.
pub struct TokenSwapInfoUpdater {
mango_client: Arc<MangoClient>,
swap_infos: RwLock<HashMap<TokenIndex, TokenSwapInfo>>,
config: Config,
}
impl TokenSwapInfoUpdater {
pub fn new(mango_client: Arc<MangoClient>, config: Config) -> Self {
Self {
mango_client,
swap_infos: RwLock::new(HashMap::new()),
config,
}
}
pub fn mango_client(&self) -> &Arc<MangoClient> {
&self.mango_client
}
fn update(&self, token_index: TokenIndex, slippage: TokenSwapInfo) {
let mut lock = self.swap_infos.write().unwrap();
lock.insert(token_index, slippage);
}
pub fn swap_info(&self, token_index: TokenIndex) -> Option<TokenSwapInfo> {
let lock = self.swap_infos.read().unwrap();
lock.get(&token_index).cloned()
}
fn in_per_out_price(route: &jupiter::Quote) -> f64 {
let in_amount = route.in_amount as f64;
let out_amount = route.out_amount as f64;
in_amount / out_amount
}
pub async fn update_one(&self, token_index: TokenIndex) -> anyhow::Result<()> {
// since we're only quoting, the slippage does not matter
let slippage = 100;
let quote_index = self.config.quote_index;
if token_index == quote_index {
self.update(
quote_index,
TokenSwapInfo {
last_update: std::time::SystemTime::now(),
quote_per_token_oracle: 1.0,
quote_per_token_buy: 1.0,
quote_per_token_sell: 1.0,
},
);
return Ok(());
}
let token_mint = self.mango_client.context.token(token_index).mint;
let quote_mint = self.mango_client.context.token(quote_index).mint;
// these prices are in USD, which doesn't exist on chain
let token_price = self
.mango_client
.bank_oracle_price(token_index)
.await?
.to_num::<f64>();
let quote_price = self
.mango_client
.bank_oracle_price(quote_index)
.await?
.to_num::<f64>();
// prices for the pair
let quote_per_token_oracle = token_price / quote_price;
let token_per_quote_oracle = quote_price / token_price;
let token_amount = (self.config.quote_amount as f64 * token_per_quote_oracle) as u64;
let sell_route = self
.mango_client
.jupiter()
.quote(
token_mint,
quote_mint,
token_amount,
slippage,
false,
self.config.jupiter_version,
)
.await?;
let buy_route = self
.mango_client
.jupiter()
.quote(
quote_mint,
token_mint,
self.config.quote_amount,
slippage,
false,
self.config.jupiter_version,
)
.await?;
let quote_per_token_buy = Self::in_per_out_price(&buy_route);
let token_per_quote_sell = Self::in_per_out_price(&sell_route);
self.update(
token_index,
TokenSwapInfo {
last_update: std::time::SystemTime::now(),
quote_per_token_oracle,
quote_per_token_buy,
quote_per_token_sell: 1.0 / token_per_quote_sell,
},
);
Ok(())
}
pub fn log_all(&self) {
let mut tokens = self
.mango_client
.context
.token_indexes_by_name
.clone()
.into_iter()
.collect_vec();
tokens.sort_by(|a, b| a.0.cmp(&b.0));
let infos = self.swap_infos.read().unwrap();
let mut msg = String::new();
for (token, token_index) in tokens {
let info = infos
.get(&token_index)
.map(|info| {
format!(
"oracle {}, buy {}, sell {}",
info.quote_per_token_oracle,
info.quote_per_token_buy,
info.quote_per_token_sell
)
})
.unwrap_or_else(|| "no data".into());
msg.push_str(&format!("token {token}, {info}"));
}
trace!("swap infos:{}", msg);
}
}