mirror of
https://github.com/payden/libwsclient
synced 2026-03-02 04:09:18 +00:00
feat : add strategy
This commit is contained in:
17
strategies/arbitrage.rs
Normal file
17
strategies/arbitrage.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::models::market_conditions::MarketConditions;
|
||||
use crate::models::mev_opportunity::MevOpportunity;
|
||||
use crate::strategies::strategy::Strategy;
|
||||
use async_trait::async_trait;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ArbitrageStrategy {}
|
||||
|
||||
#[async_trait]
|
||||
impl Strategy for ArbitrageStrategy {
|
||||
fn update(&mut self, _market_conditions: &MarketConditions) {}
|
||||
|
||||
async fn find_opportunities(&self, _target_accounts: &HashMap<Pubkey, crate::AccountInfo>) -> Vec<MevOpportunity> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
117
strategies/arbitrage_strategy.rs
Normal file
117
strategies/arbitrage_strategy.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use crate::dex::dex_manager::DexManager;
|
||||
use crate::models::market_conditions::MarketConditions;
|
||||
use crate::models::arbitrage_opportunity::ArbitrageOpportunity;
|
||||
use crate::strategies::strategy::Strategy;
|
||||
use crate::utils::math;
|
||||
use async_trait::async_trait;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ArbitrageStrategy {
|
||||
pub rpc_client: RpcClient,
|
||||
pub dex_manager: DexManager,
|
||||
pub min_profit_threshold: f64,
|
||||
}
|
||||
|
||||
impl ArbitrageStrategy {
|
||||
pub fn new(rpc_client: RpcClient, dex_manager: DexManager, min_profit_threshold: f64) -> Self {
|
||||
ArbitrageStrategy {
|
||||
rpc_client,
|
||||
dex_manager,
|
||||
min_profit_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_arbitrage_opportunities(&self, market_conditions: &MarketConditions) -> Vec<ArbitrageOpportunity> {
|
||||
let mut opportunities = Vec::new();
|
||||
|
||||
let token_prices = market_conditions.token_prices.clone();
|
||||
let token_pairs = self.generate_token_pairs(&token_prices);
|
||||
|
||||
for (token_a, token_b) in token_pairs {
|
||||
if let Some(opportunity) = self.find_arbitrage_opportunity(token_a, token_b, &token_prices).await {
|
||||
if opportunity.expected_profit >= self.min_profit_threshold {
|
||||
opportunities.push(opportunity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opportunities
|
||||
}
|
||||
|
||||
async fn find_arbitrage_opportunity(&self, token_a: &str, token_b: &str, token_prices: &HashMap<String, f64>) -> Option<ArbitrageOpportunity> {
|
||||
let mut best_opportunity: Option<ArbitrageOpportunity> = None;
|
||||
|
||||
if let Some(price_a_b) = token_prices.get(&format!("{}/{}", token_a, token_b)) {
|
||||
if let Some(price_b_a) = token_prices.get(&format!("{}/{}", token_b, token_a)) {
|
||||
let forward_amount = 1.0;
|
||||
let forward_price = price_a_b;
|
||||
let backward_amount = forward_amount * forward_price;
|
||||
let backward_price = price_b_a;
|
||||
|
||||
let forward_trade = self.dex_manager.get_best_trade_route(token_a, token_b, forward_amount).await;
|
||||
let backward_trade = self.dex_manager.get_best_trade_route(token_b, token_a, backward_amount).await;
|
||||
|
||||
if let (Some(forward_trade), Some(backward_trade)) = (forward_trade, backward_trade) {
|
||||
let forward_amount_received = math::checked_div(forward_amount, forward_trade.price).unwrap_or(0.0);
|
||||
let backward_amount_received = math::checked_mul(backward_trade.received_amount, backward_price).unwrap_or(0.0);
|
||||
|
||||
let expected_profit = backward_amount_received - forward_amount;
|
||||
|
||||
if expected_profit > 0.0 {
|
||||
best_opportunity = Some(ArbitrageOpportunity {
|
||||
token_a: token_a.to_string(),
|
||||
token_b: token_b.to_string(),
|
||||
forward_trade,
|
||||
backward_trade,
|
||||
expected_profit,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_opportunity
|
||||
}
|
||||
|
||||
fn generate_token_pairs(&self, token_prices: &HashMap<String, f64>) -> Vec<(String, String)> {
|
||||
let mut pairs = Vec::new();
|
||||
|
||||
for (token_a, _) in token_prices {
|
||||
for (token_b, _) in token_prices {
|
||||
if token_a != token_b {
|
||||
pairs.push((token_a.clone(), token_b.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pairs
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Strategy for ArbitrageStrategy {
|
||||
async fn find_opportunities(&self, market_conditions: &MarketConditions) -> Vec<ArbitrageOpportunity> {
|
||||
self.find_arbitrage_opportunities(market_conditions).await
|
||||
}
|
||||
|
||||
async fn execute_opportunities(&self, opportunities: &[ArbitrageOpportunity]) {
|
||||
for opportunity in opportunities {
|
||||
let forward_trade = &opportunity.forward_trade;
|
||||
let backward_trade = &opportunity.backward_trade;
|
||||
|
||||
let forward_result = self.dex_manager.execute_trade(forward_trade).await;
|
||||
if forward_result.is_ok() {
|
||||
let backward_result = self.dex_manager.execute_trade(backward_trade).await;
|
||||
if backward_result.is_ok() {
|
||||
// Log successful arbitrage execution
|
||||
} else {
|
||||
// Log backward trade failure
|
||||
}
|
||||
} else {
|
||||
// Log forward trade failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
strategies/copy_trade_strategy.rs
Normal file
153
strategies/copy_trade_strategy.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use crate::dex::dex_manager::DexManager;
|
||||
use crate::error::Result;
|
||||
use crate::models::copy_trade_opportunity::CopyTradeOpportunity;
|
||||
use crate::models::market::Market;
|
||||
use crate::models::order::Order;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct CopyTradeStrategy {
|
||||
pub rpc_client: Arc<RpcClient>,
|
||||
pub dex_manager: Arc<Mutex<DexManager>>,
|
||||
pub tracked_traders: Vec<Pubkey>,
|
||||
pub trade_threshold: f64,
|
||||
pub max_trade_amount: f64,
|
||||
}
|
||||
|
||||
impl CopyTradeStrategy {
|
||||
pub fn new(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
dex_manager: Arc<Mutex<DexManager>>,
|
||||
tracked_traders: Vec<Pubkey>,
|
||||
trade_threshold: f64,
|
||||
max_trade_amount: f64,
|
||||
) -> Self {
|
||||
CopyTradeStrategy {
|
||||
rpc_client,
|
||||
dex_manager,
|
||||
tracked_traders,
|
||||
trade_threshold,
|
||||
max_trade_amount,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<()> {
|
||||
loop {
|
||||
let opportunities = self.find_opportunities().await?;
|
||||
|
||||
for opportunity in opportunities {
|
||||
self.execute_copy_trade(&opportunity).await?;
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_opportunities(&self) -> Result<Vec<CopyTradeOpportunity>> {
|
||||
let mut opportunities = Vec::new();
|
||||
|
||||
for trader in &self.tracked_traders {
|
||||
let trades = self.get_recent_trades(trader).await?;
|
||||
|
||||
for trade in trades {
|
||||
if trade.quantity >= self.trade_threshold && trade.quantity <= self.max_trade_amount {
|
||||
let market = self.dex_manager.lock().await.get_market(&trade.market).await?;
|
||||
|
||||
let opportunity = CopyTradeOpportunity {
|
||||
trader: *trader,
|
||||
market,
|
||||
trade,
|
||||
};
|
||||
opportunities.push(opportunity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(opportunities)
|
||||
}
|
||||
|
||||
async fn get_recent_trades(&self, trader: &Pubkey) -> Result<Vec<Order>> {
|
||||
let signature_infos = self.rpc_client.get_signatures_for_address(trader)?;
|
||||
|
||||
let mut trades = Vec::new();
|
||||
|
||||
for signature_info in signature_infos {
|
||||
if let Some(signature) = signature_info.signature {
|
||||
let transaction = self.rpc_client.get_transaction(&signature)?;
|
||||
|
||||
if let Some(transaction) = transaction {
|
||||
for instruction in transaction.transaction.message.instructions {
|
||||
if let Some(dex_instruction) = DexInstruction::unpack(instruction) {
|
||||
match dex_instruction {
|
||||
DexInstruction::NewOrder { .. } => {
|
||||
let order = self.parse_order(&transaction, &instruction)?;
|
||||
trades.push(order);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(trades)
|
||||
}
|
||||
|
||||
fn parse_order(&self, transaction: &TransactionInfo, instruction: &CompiledInstruction) -> Result<Order> {
|
||||
let market_address = instruction.accounts[0];
|
||||
let market = self.dex_manager.lock().await.get_market(&market_address).await?;
|
||||
|
||||
let side = match instruction.data[0] {
|
||||
0 => OrderSide::Bid,
|
||||
1 => OrderSide::Ask,
|
||||
_ => return Err(anyhow!("Invalid order side")),
|
||||
};
|
||||
|
||||
let order_type = match instruction.data[1] {
|
||||
0 => OrderType::Limit,
|
||||
1 => OrderType::ImmediateOrCancel,
|
||||
2 => OrderType::PostOnly,
|
||||
_ => return Err(anyhow!("Invalid order type")),
|
||||
};
|
||||
|
||||
let price = f64::from_le_bytes(instruction.data[2..10].try_into()?);
|
||||
let quantity = f64::from_le_bytes(instruction.data[10..18].try_into()?);
|
||||
|
||||
Ok(Order {
|
||||
id: transaction.transaction.signatures[0].to_string(),
|
||||
market,
|
||||
side,
|
||||
order_type,
|
||||
price,
|
||||
quantity,
|
||||
status: OrderStatus::Filled,
|
||||
})
|
||||
}
|
||||
async fn execute_copy_trade(&self, opportunity: &CopyTradeOpportunity) -> Result<()> {
|
||||
let market = &opportunity.market;
|
||||
let trade = &opportunity.trade;
|
||||
let order = self
|
||||
.dex_manager
|
||||
.lock()
|
||||
.await
|
||||
.place_order(market, trade.order_type, trade.side, trade
|
||||
async fn execute_copy_trade(&self, opportunity: &CopyTradeOpportunity) -> Result<()> {
|
||||
let market = &opportunity.market;
|
||||
let trade = &opportunity.trade;
|
||||
|
||||
let order = self
|
||||
.dex_manager
|
||||
.lock()
|
||||
.await
|
||||
.place_order(market, trade.order_type, trade.side, trade.price, trade.quantity)
|
||||
.await?;
|
||||
|
||||
println!("Placed copy trade order: {:?}", order);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
17
strategies/liquidation.rs
Normal file
17
strategies/liquidation.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::models::market_conditions::MarketConditions;
|
||||
use crate::models::mev_opportunity::MevOpportunity;
|
||||
use crate::strategies::strategy::Strategy;
|
||||
use async_trait::async_trait;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct LiquidationStrategy {}
|
||||
|
||||
#[async_trait]
|
||||
impl Strategy for LiquidationStrategy {
|
||||
fn update(&mut self, _market_conditions: &MarketConditions) {}
|
||||
|
||||
async fn find_opportunities(&self, _target_accounts: &HashMap<Pubkey, crate::AccountInfo>) -> Vec<MevOpportunity> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
4
strategies/mod.rs
Normal file
4
strategies/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod strategy;
|
||||
pub mod arbitrage;
|
||||
pub mod liquidation;
|
||||
pub mod sandwich;
|
||||
17
strategies/sandwich.rs
Normal file
17
strategies/sandwich.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::models::market_conditions::MarketConditions;
|
||||
use crate::models::mev_opportunity::MevOpportunity;
|
||||
use crate::strategies::strategy::Strategy;
|
||||
use async_trait::async_trait;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct SandwichStrategy {}
|
||||
|
||||
#[async_trait]
|
||||
impl Strategy for SandwichStrategy {
|
||||
fn update(&mut self, _market_conditions: &MarketConditions) {}
|
||||
|
||||
async fn find_opportunities(&self, _target_accounts: &HashMap<Pubkey, crate::AccountInfo>) -> Vec<MevOpportunity> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
119
strategies/sniping_strategy.rs
Normal file
119
strategies/sniping_strategy.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::dex::dex_manager::DexManager;
|
||||
use crate::error::Result;
|
||||
use crate::models::market::Market;
|
||||
use crate::models::sniping_opportunity::SnipingOpportunity;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct SnipingStrategy {
|
||||
pub rpc_client: Arc<RpcClient>,
|
||||
pub dex_manager: Arc<Mutex<DexManager>>,
|
||||
pub target_markets: Vec<Pubkey>,
|
||||
pub max_price: f64,
|
||||
pub min_liquidity: f64,
|
||||
}
|
||||
|
||||
impl SnipingStrategy {
|
||||
pub fn new(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
dex_manager: Arc<Mutex<DexManager>>,
|
||||
target_markets: Vec<Pubkey>,
|
||||
max_price: f64,
|
||||
min_liquidity: f64,
|
||||
) -> Self {
|
||||
SnipingStrategy {
|
||||
rpc_client,
|
||||
dex_manager,
|
||||
target_markets,
|
||||
max_price,
|
||||
min_liquidity,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<()> {
|
||||
loop {
|
||||
let markets = self.get_target_markets().await?;
|
||||
|
||||
let opportunities = self.find_opportunities(&markets).await?;
|
||||
|
||||
for opportunity in opportunities {
|
||||
self.execute_sniping(&opportunity).await?;
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_target_markets(&self) -> Result<Vec<Market>> {
|
||||
let mut markets = Vec::new();
|
||||
|
||||
for market_address in &self.target_markets {
|
||||
let market = self.dex_manager.lock().await.get_market(market_address).await?;
|
||||
markets.push(market);
|
||||
}
|
||||
|
||||
Ok(markets)
|
||||
}
|
||||
|
||||
async fn find_opportunities(&self, markets: &[Market]) -> Result<Vec<SnipingOpportunity>> {
|
||||
let mut opportunities = Vec::new();
|
||||
|
||||
for market in markets {
|
||||
let orderbook = self.dex_manager.lock().await.get_orderbook(market).await?;
|
||||
|
||||
if let Some(best_bid) = orderbook.bids.first() {
|
||||
if best_bid.price <= self.max_price {
|
||||
let liquidity = self.calculate_liquidity(market, &orderbook).await?;
|
||||
|
||||
if liquidity >= self.min_liquidity {
|
||||
let opportunity = SnipingOpportunity {
|
||||
market: market.clone(),
|
||||
price: best_bid.price,
|
||||
liquidity,
|
||||
};
|
||||
opportunities.push(opportunity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(opportunities)
|
||||
}
|
||||
|
||||
async fn calculate_liquidity(&self, market: &Market, orderbook: &Orderbook) -> Result<f64> {
|
||||
let bids_volume = orderbook.bids.iter().map(|order| order.quantity).sum();
|
||||
let asks_volume = orderbook.asks.iter().map(|order| order.quantity).sum();
|
||||
|
||||
let mid_price = (orderbook.bids[0].price + orderbook.asks[0].price) / 2.0;
|
||||
|
||||
let base_volume = bids_volume + asks_volume;
|
||||
let quote_volume = base_volume * mid_price;
|
||||
|
||||
let base_decimals = market.base_decimals;
|
||||
let quote_decimals = market.quote_decimals;
|
||||
|
||||
let liquidity = base_volume / 10_usize.pow(base_decimals as u32) as f64
|
||||
+ quote_volume / 10_usize.pow(quote_decimals as u32) as f64;
|
||||
|
||||
Ok(liquidity)
|
||||
}
|
||||
|
||||
async fn execute_sniping(&self, opportunity: &SnipingOpportunity) -> Result<()> {
|
||||
let market = &opportunity.market;
|
||||
let price = opportunity.price;
|
||||
let quantity = opportunity.liquidity / price;
|
||||
|
||||
let order = self
|
||||
.dex_manager
|
||||
.lock()
|
||||
.await
|
||||
.place_order(market, OrderType::Limit, OrderSide::Bid, price, quantity)
|
||||
.await?;
|
||||
|
||||
println!("Placed sniping order: {:?}", order);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
strategies/strategy.rs
Normal file
11
strategies/strategy.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::models::market_conditions::MarketConditions;
|
||||
use crate::models::mev_opportunity::MevOpportunity;
|
||||
use async_trait::async_trait;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Strategy {
|
||||
fn update(&mut self, market_conditions: &MarketConditions);
|
||||
async fn find_opportunities(&self, target_accounts: &HashMap<Pubkey, crate::AccountInfo>) -> Vec<MevOpportunity>;
|
||||
}
|
||||
Reference in New Issue
Block a user