Skip to main content

sol_parser_sdk/utils/
market.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4pub enum NormalizedTradeSide {
5    Buy,
6    Sell,
7}
8
9impl NormalizedTradeSide {
10    #[inline]
11    pub const fn as_str(self) -> &'static str {
12        match self {
13            Self::Buy => "Buy",
14            Self::Sell => "Sell",
15        }
16    }
17}
18
19/// Convert a Q64.64 sqrt price into a UI price.
20///
21/// The returned price is quote-token units per one base token, adjusted by
22/// token decimals: `(sqrt_price_x64 / 2^64)^2 * 10^(base_decimals - quote_decimals)`.
23#[inline]
24pub fn sqrt_price_x64_to_price(sqrt_price_x64: u128, base_decimals: u8, quote_decimals: u8) -> f64 {
25    let sqrt = sqrt_price_x64 as f64 / 2_f64.powi(64);
26    let decimal_adjustment = 10_f64.powi(i32::from(base_decimals) - i32::from(quote_decimals));
27    sqrt * sqrt * decimal_adjustment
28}
29
30/// Compute quote-token price per one base token from raw vault balances.
31#[inline]
32pub fn vault_price_from_balances(
33    base_raw: u128,
34    quote_raw: u128,
35    base_decimals: u8,
36    quote_decimals: u8,
37) -> Option<f64> {
38    if base_raw == 0 {
39        return None;
40    }
41    let decimal_adjustment = 10_f64.powi(i32::from(base_decimals) - i32::from(quote_decimals));
42    Some((quote_raw as f64 / base_raw as f64) * decimal_adjustment)
43}
44
45/// Normalize a watched/base token balance delta into buy/sell direction.
46///
47/// Positive means the wallet received the watched token (Buy). Negative means
48/// the wallet spent the watched token (Sell).
49#[inline]
50pub fn normalize_buy_sell_from_token_delta(token_delta: i128) -> Option<NormalizedTradeSide> {
51    match token_delta.cmp(&0) {
52        std::cmp::Ordering::Greater => Some(NormalizedTradeSide::Buy),
53        std::cmp::Ordering::Less => Some(NormalizedTradeSide::Sell),
54        std::cmp::Ordering::Equal => None,
55    }
56}
57
58/// Normalize direction from the input mint when base/quote mints are known.
59///
60/// If the input mint is quote, the user buys base. If the input mint is base,
61/// the user sells base.
62#[inline]
63pub fn normalize_buy_sell_from_input_mint(
64    input_mint: &str,
65    base_mint: &str,
66    quote_mint: &str,
67) -> Option<NormalizedTradeSide> {
68    if base_mint == quote_mint {
69        return None;
70    }
71    if input_mint == quote_mint {
72        Some(NormalizedTradeSide::Buy)
73    } else if input_mint == base_mint {
74        Some(NormalizedTradeSide::Sell)
75    } else {
76        None
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn sqrt_price_x64_identity_price() {
86        let q64 = 1_u128 << 64;
87        assert_eq!(sqrt_price_x64_to_price(q64, 6, 6), 1.0);
88        assert_eq!(sqrt_price_x64_to_price(q64, 9, 6), 1000.0);
89    }
90
91    #[test]
92    fn vault_price_handles_decimals_and_zero_base() {
93        assert_eq!(vault_price_from_balances(1_000_000, 2_000_000, 6, 6), Some(2.0));
94        assert_eq!(vault_price_from_balances(1_000_000_000, 2_000_000, 9, 6), Some(2.0));
95        assert_eq!(vault_price_from_balances(0, 2_000_000, 6, 6), None);
96    }
97
98    #[test]
99    fn direction_helpers_are_token_relative() {
100        assert_eq!(normalize_buy_sell_from_token_delta(1), Some(NormalizedTradeSide::Buy));
101        assert_eq!(normalize_buy_sell_from_token_delta(-1), Some(NormalizedTradeSide::Sell));
102        assert_eq!(normalize_buy_sell_from_token_delta(0), None);
103        assert_eq!(
104            normalize_buy_sell_from_input_mint("USDC", "SOL", "USDC"),
105            Some(NormalizedTradeSide::Buy)
106        );
107        assert_eq!(
108            normalize_buy_sell_from_input_mint("SOL", "SOL", "USDC"),
109            Some(NormalizedTradeSide::Sell)
110        );
111    }
112}