refactor: restructure trading modules and add price calculation utilities

- Remove redundant pool.rs files from trading protocols
- Consolidate protocol logic into common.rs files
- Add comprehensive price calculation utilities for all protocols
- Add decimals constants module
- Restructure utils module for better organization
- Update documentation with price utilities information

Breaking changes:
- Removed bonding_curve.rs from pumpfun module
- Consolidated trading logic across protocols
- Refactored utils module structure
This commit is contained in:
ysq
2025-08-14 22:45:33 +08:00
parent 29179519a6
commit 63244f4073
28 changed files with 688 additions and 693 deletions
+152
View File
@@ -0,0 +1,152 @@
pub mod price;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::trading;
use crate::SolanaTrade;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
impl SolanaTrade {
#[inline]
pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, payer).await
}
#[inline]
pub async fn get_payer_sol_balance(&self) -> Result<u64, anyhow::Error> {
trading::common::utils::get_sol_balance(&self.rpc, &self.payer.pubkey()).await
}
#[inline]
pub async fn get_token_balance(
&self,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
trading::common::utils::get_token_balance(&self.rpc, payer, mint).await
}
#[inline]
pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
trading::common::utils::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await
}
#[inline]
pub fn get_payer_pubkey(&self) -> Pubkey {
self.payer.pubkey()
}
#[inline]
pub fn get_payer(&self) -> &Keypair {
self.payer.as_ref()
}
#[inline]
pub async fn transfer_sol(
&self,
payer: &Keypair,
receive_wallet: &Pubkey,
amount: u64,
) -> Result<(), anyhow::Error> {
trading::common::utils::transfer_sol(&self.rpc, payer, receive_wallet, amount).await
}
#[inline]
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
}
// -------------------------------- PumpFun --------------------------------
#[inline]
pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
trading::pumpfun::common::get_buy_price(amount, trade_info)
}
#[inline]
pub async fn get_pumpfun_token_current_price(
&self,
mint: &Pubkey,
) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(price::pumpfun::price_token_in_sol(virtual_sol_reserves, virtual_token_reserves))
}
#[inline]
pub async fn get_pumpfun_token_real_sol_reserves(
&self,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
Ok(actual_sol_reserves)
}
#[inline]
pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let creator = bonding_curve.creator;
Ok(creator)
}
// -------------------------------- PumpSwap --------------------------------
#[inline]
pub async fn get_pumpswap_token_current_price(
&self,
pool_address: &Pubkey,
) -> Result<f64, anyhow::Error> {
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, quote_amount) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
// Calculate price using constant product formula (x * y = k)
// Price = quote_amount / base_amount
if base_amount == 0 {
return Err(anyhow::anyhow!("Base amount is zero, cannot calculate price"));
}
let price = quote_amount as f64 / base_amount as f64;
Ok(price)
}
#[inline]
pub async fn get_pumpswap_token_real_sol_reserves(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (_, quote_amount) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
Ok(quote_amount)
}
#[inline]
pub async fn get_pumpswap_payer_token_balance(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, _) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
Ok(base_amount)
}
}
+117
View File
@@ -0,0 +1,117 @@
use solana_streamer_sdk::streaming::event_parser::protocols::bonk::types::PoolState;
use crate::constants::{
bonk::accounts::WSOL_TOKEN_ACCOUNT,
decimals::{DEFAULT_TOKEN_DECIMALS, SOL_DECIMALS},
};
/// Calculate the token price in WSOL based on pool state
///
/// # Arguments
/// * `pool_state` - Pool state
///
/// # Returns
/// Token price in WSOL as f64
pub fn price_token_in_wsol_with_pool_state(pool_state: &PoolState) -> f64 {
if pool_state.quote_mint != WSOL_TOKEN_ACCOUNT {
log::error!("Quote mint is not WSOL: {:?}", pool_state.quote_mint);
return 0.0;
}
price_base_in_quote(
pool_state.virtual_base,
pool_state.virtual_quote,
pool_state.real_base,
pool_state.real_quote,
pool_state.base_decimals,
pool_state.quote_decimals,
)
}
/// Calculate the price of base in quote based on pool state
///
/// # Arguments
/// * `pool_state` - Pool state
///
/// # Returns
/// The price of base in quote
pub fn price_base_in_quote_with_pool_state(pool_state: &PoolState) -> f64 {
price_base_in_quote(
pool_state.virtual_base,
pool_state.virtual_quote,
pool_state.real_base,
pool_state.real_quote,
pool_state.base_decimals,
pool_state.quote_decimals,
)
}
/// Calculate the price of token in WSOL
///
/// # Arguments
/// * `virtual_base` - Virtual base reserves
/// * `virtual_quote` - Virtual quote reserves
/// * `real_base` - Real base reserves
/// * `real_quote` - Real quote reserves
///
/// # Returns
/// The price of token in WSOL
pub fn price_token_in_wsol(
virtual_base: u64,
virtual_quote: u64,
real_base: u64,
real_quote: u64,
) -> f64 {
price_base_in_quote(
virtual_base,
virtual_quote,
real_base,
real_quote,
DEFAULT_TOKEN_DECIMALS,
SOL_DECIMALS,
)
}
/// Calculate the price of base in quote
///
/// # Arguments
/// * `virtual_base` - Virtual base reserves
/// * `virtual_quote` - Virtual quote reserves
/// * `real_base` - Real base reserves
/// * `real_quote` - Real quote reserves
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// The price of base in quote
pub fn price_base_in_quote(
virtual_base: u64,
virtual_quote: u64,
real_base: u64,
real_quote: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
// Calculate decimal places difference
let decimal_diff = quote_decimals as i32 - base_decimals as i32;
let decimal_factor = if decimal_diff >= 0 {
10_f64.powi(decimal_diff)
} else {
1.0 / 10_f64.powi(-decimal_diff)
};
// Calculate reserves state before price calculation
let quote_reserves = virtual_quote.checked_add(real_quote).unwrap_or(0);
let base_reserves = virtual_base.checked_sub(real_base).unwrap_or(0);
if base_reserves == 0 {
return 0.0;
}
if decimal_factor == 0.0 {
return 0.0;
}
// Use floating point calculation to avoid precision loss from integer division
let price = (quote_reserves as f64) / (base_reserves as f64) / decimal_factor;
price
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
let base = base_reserve as f64 / 10f64.powi(base_decimals as i32);
let quote = quote_reserve as f64 / 10f64.powi(quote_decimals as i32);
if base == 0.0 {
return 0.0;
}
quote / base
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
let base = base_reserve as f64 / 10f64.powi(base_decimals as i32);
let quote = quote_reserve as f64 / 10f64.powi(quote_decimals as i32);
if quote == 0.0 {
return 0.0;
}
base / quote
}
+7
View File
@@ -0,0 +1,7 @@
pub mod bonk;
pub mod pumpfun;
pub mod pumpswap;
pub mod raydium_amm_v4;
pub mod raydium_clmm;
pub mod raydium_cpmm;
pub mod common;
+31
View File
@@ -0,0 +1,31 @@
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve;
use crate::constants::pumpfun::global_constants::{LAMPORTS_PER_SOL, SCALE};
/// Calculate the token price in SOL based on virtual reserves
///
/// # Arguments
/// * `bonding_curve` - Bonding curve account
///
/// # Returns
/// Token price in SOL as f64
pub fn price_token_in_sol_with_bonding_curve(bonding_curve: &BondingCurve) -> f64 {
price_token_in_sol(bonding_curve.virtual_sol_reserves, bonding_curve.virtual_token_reserves)
}
/// Calculate the token price in SOL based on virtual reserves
///
/// # Arguments
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
///
/// # Returns
/// Token price in SOL as f64
pub fn price_token_in_sol(virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
let v_sol = virtual_sol_reserves as f64 / LAMPORTS_PER_SOL as f64;
let v_tokens = virtual_token_reserves as f64 / SCALE as f64;
if v_tokens == 0.0 {
return 0.0;
}
v_sol / v_tokens
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
+68
View File
@@ -0,0 +1,68 @@
use solana_streamer_sdk::streaming::event_parser::protocols::raydium_clmm::types::PoolState;
/// Calculate the price of token0 in token1
///
/// # Arguments
/// * `sqrt_price_x64` - The sqrt price of the pool
/// * `decimals_token0` - The decimals of token0
/// * `decimals_token1` - The decimals of token1
///
/// # Returns
/// The price of token0 in token1
pub fn price_token0_in_token1(
sqrt_price_x64: u128,
decimals_token0: u8,
decimals_token1: u8,
) -> f64 {
let sqrt_price = sqrt_price_x64 as f64 / (1u128 << 64) as f64; // Q64.64 转浮点
let price_raw = sqrt_price * sqrt_price; // 未调整小数位的价格
let scale = 10f64.powi((decimals_token0 as i32) - (decimals_token1 as i32));
price_raw * scale
}
/// Calculate the price of token1 in token0
///
/// # Arguments
/// * `sqrt_price_x64` - The sqrt price of the pool
/// * `decimals_token0` - The decimals of token0
/// * `decimals_token1` - The decimals of token1
///
/// # Returns
/// The price of token1 in token0
pub fn price_token1_in_token0(
sqrt_price_x64: u128,
decimals_token0: u8,
decimals_token1: u8,
) -> f64 {
1.0 / price_token0_in_token1(sqrt_price_x64, decimals_token0, decimals_token1)
}
/// Calculate the price of token0 in token1 based on pool state
///
/// # Arguments
/// * `pool_state` - The pool state
///
/// # Returns
/// The price of token0 in token1
pub fn price_token0_in_token1_with_pool_state(pool_state: &PoolState) -> f64 {
price_token0_in_token1(
pool_state.sqrt_price_x64,
pool_state.mint_decimals0,
pool_state.mint_decimals1,
)
}
/// Calculate the price of token1 in token0 based on pool state
///
/// # Arguments
/// * `pool_state` - The pool state
///
/// # Returns
/// The price of token1 in token0
pub fn price_token1_in_token0_with_pool_state(pool_state: &PoolState) -> f64 {
price_token1_in_token0(
pool_state.sqrt_price_x64,
pool_state.mint_decimals0,
pool_state.mint_decimals1,
)
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}