FIx and testing strategy
This commit is contained in:
+33
-27
@@ -93,15 +93,17 @@ impl Executor for PumpDev {
|
|||||||
priority: Decimal,
|
priority: Decimal,
|
||||||
slippage: u16,
|
slippage: u16,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.trade(
|
println!("BUY {mint} {amount} SOL");
|
||||||
"buy",
|
Ok(())
|
||||||
mint,
|
// self.trade(
|
||||||
amount.round_dp(3).to_string(),
|
// "buy",
|
||||||
priority,
|
// mint,
|
||||||
slippage,
|
// amount.round_dp(3).to_string(),
|
||||||
true,
|
// priority,
|
||||||
)
|
// slippage,
|
||||||
.await
|
// true,
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sell(
|
async fn sell(
|
||||||
@@ -111,15 +113,17 @@ impl Executor for PumpDev {
|
|||||||
priority: Decimal,
|
priority: Decimal,
|
||||||
slippage: u16,
|
slippage: u16,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.trade(
|
println!("SELL {mint} {amount} SOL");
|
||||||
"sell",
|
Ok(())
|
||||||
mint,
|
// self.trade(
|
||||||
amount.round_dp(3).to_string(),
|
// "sell",
|
||||||
priority,
|
// mint,
|
||||||
slippage,
|
// amount.round_dp(3).to_string(),
|
||||||
false,
|
// priority,
|
||||||
)
|
// slippage,
|
||||||
.await
|
// false,
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sell_percent(
|
async fn sell_percent(
|
||||||
@@ -129,14 +133,16 @@ impl Executor for PumpDev {
|
|||||||
priority: Decimal,
|
priority: Decimal,
|
||||||
slippage: u16,
|
slippage: u16,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.trade(
|
println!("SELL {mint} {amount}%");
|
||||||
"sell",
|
Ok(())
|
||||||
mint,
|
// self.trade(
|
||||||
format!("{amount}%"),
|
// "sell",
|
||||||
priority,
|
// mint,
|
||||||
slippage,
|
// format!("{amount}%"),
|
||||||
false,
|
// priority,
|
||||||
)
|
// slippage,
|
||||||
.await
|
// false,
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-20
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -11,8 +11,7 @@ use crate::types::{NewToken, Trade, TradeType};
|
|||||||
const BUY_AMOUNT_SOL: Decimal = dec!(0.2);
|
const BUY_AMOUNT_SOL: Decimal = dec!(0.2);
|
||||||
const PRIORITY: Decimal = dec!(0.0002);
|
const PRIORITY: Decimal = dec!(0.0002);
|
||||||
const SLIPPAGE: u16 = 10;
|
const SLIPPAGE: u16 = 10;
|
||||||
|
const MAX_SUBSCRIBED_TOKENS: usize = 5;
|
||||||
// --- Strategy Implementation ---
|
|
||||||
|
|
||||||
struct TokenTracker {
|
struct TokenTracker {
|
||||||
created_at: Instant,
|
created_at: Instant,
|
||||||
@@ -28,14 +27,15 @@ struct OpenPosition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct MomentumVelocityStrategy {
|
pub struct MomentumVelocityStrategy {
|
||||||
// Configurable thresholds
|
|
||||||
min_unique_buyers: usize,
|
min_unique_buyers: usize,
|
||||||
min_net_sol_flow: f64,
|
min_net_sol_flow: f64,
|
||||||
max_tracking_duration: Duration,
|
max_tracking_duration: Duration,
|
||||||
|
|
||||||
// In-memory state tracking
|
|
||||||
trackers: HashMap<String, TokenTracker>,
|
trackers: HashMap<String, TokenTracker>,
|
||||||
positions: HashMap<String, OpenPosition>,
|
positions: HashMap<String, OpenPosition>,
|
||||||
|
|
||||||
|
// Tracks active token subscriptions to enforce <= 5 limit
|
||||||
|
active_subscriptions: VecDeque<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MomentumVelocityStrategy {
|
impl MomentumVelocityStrategy {
|
||||||
@@ -46,26 +46,51 @@ impl MomentumVelocityStrategy {
|
|||||||
max_tracking_duration: Duration::from_secs(45),
|
max_tracking_duration: Duration::from_secs(45),
|
||||||
trackers: HashMap::new(),
|
trackers: HashMap::new(),
|
||||||
positions: HashMap::new(),
|
positions: HashMap::new(),
|
||||||
|
active_subscriptions: VecDeque::with_capacity(MAX_SUBSCRIBED_TOKENS),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate approximate token price in SOL using the bonding curve reserves
|
|
||||||
fn calculate_price_sol(&self, trade: &Trade) -> f64 {
|
fn calculate_price_sol(&self, trade: &Trade) -> f64 {
|
||||||
if trade.v_tokens_in_bonding_curve == 0.0 {
|
if trade.v_tokens_in_bonding_curve == 0.0 {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
trade.v_sol_in_bonding_curve / trade.v_tokens_in_bonding_curve
|
trade.v_sol_in_bonding_curve / trade.v_tokens_in_bonding_curve
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper to safely handle unsubscribing and cleaning state
|
||||||
|
async fn cleanup_and_unsubscribe(&mut self, bot: &Arc<Bot>, mint: &str) -> anyhow::Result<()> {
|
||||||
|
bot.unsubscribe(mint).await?;
|
||||||
|
self.trackers.remove(mint);
|
||||||
|
self.active_subscriptions.retain(|m| m != mint);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Strategy for MomentumVelocityStrategy {
|
impl Strategy for MomentumVelocityStrategy {
|
||||||
async fn on_new_coin(&mut self, _bot: Arc<Bot>, token: NewToken) -> anyhow::Result<()> {
|
async fn on_new_coin(&mut self, bot: Arc<Bot>, token: NewToken) -> anyhow::Result<()> {
|
||||||
// Cleanup old untracked tokens to keep memory lean
|
// If we hold an active position in this mint already, skip tracking setup
|
||||||
self.trackers
|
if self.positions.contains_key(&token.mint) {
|
||||||
.retain(|_, v| v.created_at.elapsed() < Duration::from_secs(120));
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize tracking for new token
|
// Enforce maximum 5 active subscriptions
|
||||||
|
while self.active_subscriptions.len() >= MAX_SUBSCRIBED_TOKENS {
|
||||||
|
if let Some(oldest_mint) = self.active_subscriptions.pop_front() {
|
||||||
|
// Do not drop subscription if we currently hold an open position in it
|
||||||
|
if self.positions.contains_key(&oldest_mint) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bot.unsubscribe(&oldest_mint).await?;
|
||||||
|
self.trackers.remove(&oldest_mint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to new token
|
||||||
|
bot.subscribe(&token.mint).await?;
|
||||||
|
self.active_subscriptions.push_back(token.mint.clone());
|
||||||
|
|
||||||
|
// Initialize tracking
|
||||||
self.trackers.insert(
|
self.trackers.insert(
|
||||||
token.mint,
|
token.mint,
|
||||||
TokenTracker {
|
TokenTracker {
|
||||||
@@ -89,7 +114,6 @@ impl Strategy for MomentumVelocityStrategy {
|
|||||||
if let Some(pos) = self.positions.get_mut(mint) {
|
if let Some(pos) = self.positions.get_mut(mint) {
|
||||||
let price_change_pct = (current_price - pos.entry_price_sol) / pos.entry_price_sol;
|
let price_change_pct = (current_price - pos.entry_price_sol) / pos.entry_price_sol;
|
||||||
|
|
||||||
// Track peak price for trailing stop logic
|
|
||||||
if current_price > pos.highest_price_sol {
|
if current_price > pos.highest_price_sol {
|
||||||
pos.highest_price_sol = current_price;
|
pos.highest_price_sol = current_price;
|
||||||
pos.last_high_time = Instant::now();
|
pos.last_high_time = Instant::now();
|
||||||
@@ -113,7 +137,7 @@ impl Strategy for MomentumVelocityStrategy {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.positions.remove(mint);
|
self.positions.remove(mint);
|
||||||
self.trackers.remove(mint);
|
self.cleanup_and_unsubscribe(&bot, mint).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -123,13 +147,13 @@ impl Strategy for MomentumVelocityStrategy {
|
|||||||
// 2. Evaluate Potential Buys
|
// 2. Evaluate Potential Buys
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
if let Some(tracker) = self.trackers.get_mut(mint) {
|
if let Some(tracker) = self.trackers.get_mut(mint) {
|
||||||
// Stop monitoring if the token is older than maximum initial evaluation window
|
// Unsubscribe & remove if evaluation window expired without a signal
|
||||||
if tracker.created_at.elapsed() > self.max_tracking_duration {
|
if tracker.created_at.elapsed() > self.max_tracking_duration {
|
||||||
self.trackers.remove(mint);
|
self.cleanup_and_unsubscribe(&bot, mint).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats
|
// Update trade metrics
|
||||||
tracker.trade_count += 1;
|
tracker.trade_count += 1;
|
||||||
match trade.tx_type {
|
match trade.tx_type {
|
||||||
TradeType::Buy => {
|
TradeType::Buy => {
|
||||||
@@ -141,11 +165,8 @@ impl Strategy for MomentumVelocityStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check BUY conditions
|
|
||||||
let has_enough_buyers = tracker.unique_buyers.len() >= self.min_unique_buyers;
|
let has_enough_buyers = tracker.unique_buyers.len() >= self.min_unique_buyers;
|
||||||
let has_volume_surge = tracker.net_sol_flow >= self.min_net_sol_flow;
|
let has_volume_surge = tracker.net_sol_flow >= self.min_net_sol_flow;
|
||||||
|
|
||||||
// Skip if bonding curve is already close to completing (e.g. >30 SOL in curve)
|
|
||||||
let is_early_curve = trade.v_sol_in_bonding_curve < 60.0;
|
let is_early_curve = trade.v_sol_in_bonding_curve < 60.0;
|
||||||
|
|
||||||
if has_enough_buyers && has_volume_surge && is_early_curve {
|
if has_enough_buyers && has_volume_surge && is_early_curve {
|
||||||
@@ -165,7 +186,7 @@ impl Strategy for MomentumVelocityStrategy {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stop tracking buy metrics for this token
|
// Stop tracking buy metrics (subscription remains active while holding position)
|
||||||
self.trackers.remove(mint);
|
self.trackers.remove(mint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user