Debugging

This commit is contained in:
2026-08-07 04:34:21 +02:00
parent eea18463e0
commit 3bd8d7e876
4 changed files with 102 additions and 31 deletions
+2 -2
View File
@@ -127,8 +127,8 @@ impl Bot {
ws = self.ws.lock().await; ws = self.ws.lock().await;
} }
Ok(event) => { Ok(_event) => {
println!("{:?}", event); // println!("{:?}", event);
} }
Err(err) => { Err(err) => {
+3 -3
View File
@@ -93,7 +93,7 @@ impl Executor for PumpDev {
priority: Decimal, priority: Decimal,
slippage: u16, slippage: u16,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
println!("BUY {mint} {amount} SOL"); log::info!("BUY {mint} {amount} SOL");
Ok(()) Ok(())
// self.trade( // self.trade(
// "buy", // "buy",
@@ -113,7 +113,7 @@ impl Executor for PumpDev {
priority: Decimal, priority: Decimal,
slippage: u16, slippage: u16,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
println!("SELL {mint} {amount} SOL"); log::info!("SELL {mint} {amount} SOL");
Ok(()) Ok(())
// self.trade( // self.trade(
// "sell", // "sell",
@@ -133,7 +133,7 @@ impl Executor for PumpDev {
priority: Decimal, priority: Decimal,
slippage: u16, slippage: u16,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
println!("SELL {mint} {amount}%"); log::info!("SELL {mint} {amount}%");
Ok(()) Ok(())
// self.trade( // self.trade(
// "sell", // "sell",
+91 -26
View File
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use log::{debug, info, trace, warn};
use rust_decimal::{Decimal, dec}; use rust_decimal::{Decimal, dec};
use crate::bot::Bot; use crate::bot::Bot;
@@ -41,8 +42,8 @@ pub struct MomentumVelocityStrategy {
impl MomentumVelocityStrategy { impl MomentumVelocityStrategy {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
min_unique_buyers: 4, min_unique_buyers: 2,
min_net_sol_flow: 1.5, min_net_sol_flow: ,
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(),
@@ -59,7 +60,10 @@ impl MomentumVelocityStrategy {
/// Internal helper to safely handle unsubscribing and cleaning state /// Internal helper to safely handle unsubscribing and cleaning state
async fn cleanup_and_unsubscribe(&mut self, bot: &Arc<Bot>, mint: &str) -> anyhow::Result<()> { async fn cleanup_and_unsubscribe(&mut self, bot: &Arc<Bot>, mint: &str) -> anyhow::Result<()> {
bot.unsubscribe(mint).await?; debug!("[{}] Cleaning up state and unsubscribing", mint);
if let Err(e) = bot.unsubscribe(mint).await {
warn!("[{}] Unsubscribe request failed: {:?}", mint, e);
}
self.trackers.remove(mint); self.trackers.remove(mint);
self.active_subscriptions.retain(|m| m != mint); self.active_subscriptions.retain(|m| m != mint);
Ok(()) Ok(())
@@ -69,28 +73,45 @@ impl MomentumVelocityStrategy {
#[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<()> {
// If we hold an active position in this mint already, skip tracking setup trace!("[NEW COIN] Event received for token: {}", token.mint);
if self.positions.contains_key(&token.mint) {
if self.positions.contains_key(&token.mint) || self.trackers.contains_key(&token.mint) {
trace!(
"[{}] Already tracking or holding position. Skipping.",
token.mint
);
return Ok(()); return Ok(());
} }
// Enforce maximum 5 active subscriptions // Evict oldest tracked token that DOES NOT have an active open position
while self.active_subscriptions.len() >= MAX_SUBSCRIBED_TOKENS { while self.active_subscriptions.len() >= MAX_SUBSCRIBED_TOKENS {
if let Some(oldest_mint) = self.active_subscriptions.pop_front() { let eviction_index = self
// Do not drop subscription if we currently hold an open position in it .active_subscriptions
if self.positions.contains_key(&oldest_mint) { .iter()
continue; .position(|mint| !self.positions.contains_key(mint));
if let Some(idx) = eviction_index {
if let Some(mint_to_remove) = self.active_subscriptions.remove(idx) {
info!(
"[{}] Capacity reached ({}/{}). Evicting un-bought token from queue.",
mint_to_remove, MAX_SUBSCRIBED_TOKENS, MAX_SUBSCRIBED_TOKENS
);
let _ = bot.unsubscribe(&mint_to_remove).await;
self.trackers.remove(&mint_to_remove);
} }
bot.unsubscribe(&oldest_mint).await?; } else {
self.trackers.remove(&oldest_mint); warn!(
"[QUEUE FULL] All {} slots are occupied by active positions. Cannot track {}",
MAX_SUBSCRIBED_TOKENS, token.mint
);
return Ok(());
} }
} }
// Subscribe to new token info!("[{}] Subscribing and creating tracker.", token.mint);
bot.subscribe(&token.mint).await?; bot.subscribe(&token.mint).await?;
self.active_subscriptions.push_back(token.mint.clone()); self.active_subscriptions.push_back(token.mint.clone());
// Initialize tracking
self.trackers.insert( self.trackers.insert(
token.mint, token.mint,
TokenTracker { TokenTracker {
@@ -117,19 +138,36 @@ impl Strategy for MomentumVelocityStrategy {
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();
trace!("[{}] New high reached: {:.9} SOL", mint, current_price);
} }
let drop_from_peak = (pos.highest_price_sol - current_price) / pos.highest_price_sol; let drop_from_peak = (pos.highest_price_sol - current_price) / pos.highest_price_sol;
let should_sell = match () { let (should_sell, reason) = match () {
_ if price_change_pct >= 0.40 => true, // Take Profit: +40% _ if price_change_pct >= 0.40 => (
_ if price_change_pct <= -0.15 => true, // Hard Stop Loss: -15% true,
_ if drop_from_peak >= 0.12 && price_change_pct > 0.10 => true, // Trailing stop: 12% drop from peak format!("Take Profit (+{:.1}%)", price_change_pct * 100.0),
_ if pos.last_high_time.elapsed() >= Duration::from_secs(25) => true, // Momentum stalled for 25s ),
_ => false, _ if price_change_pct <= -0.15 => (
true,
format!("Hard Stop Loss ({:.1}%)", price_change_pct * 100.0),
),
_ if drop_from_peak >= 0.12 && price_change_pct > 0.10 => (
true,
format!(
"Trailing Stop (Peak drop: {:.1}%, gain: +{:.1}%)",
drop_from_peak * 100.0,
price_change_pct * 100.0
),
),
_ if pos.last_high_time.elapsed() >= Duration::from_secs(25) => {
(true, format!("Momentum Stalled (no high for 25s)"))
}
_ => (false, String::new()),
}; };
if should_sell { if should_sell {
info!("[{}] EXECUTING SELL. Reason: {}", mint, reason);
bot.executor bot.executor
.lock() .lock()
.await .await
@@ -147,17 +185,20 @@ 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) {
// Unsubscribe & remove if evaluation window expired without a signal let elapsed = tracker.created_at.elapsed();
if tracker.created_at.elapsed() > self.max_tracking_duration { if elapsed > self.max_tracking_duration {
info!(
"[{}] Tracking window expired ({:?} > {:?}). Cleaning up.",
mint, elapsed, self.max_tracking_duration
);
self.cleanup_and_unsubscribe(&bot, mint).await?; self.cleanup_and_unsubscribe(&bot, mint).await?;
return Ok(()); return Ok(());
} }
// Update trade metrics
tracker.trade_count += 1; tracker.trade_count += 1;
match trade.tx_type { match trade.tx_type {
TradeType::Buy => { TradeType::Buy => {
tracker.unique_buyers.insert(trade.trader); tracker.unique_buyers.insert(trade.trader.clone());
tracker.net_sol_flow += trade.sol_amount; tracker.net_sol_flow += trade.sol_amount;
} }
TradeType::Sell => { TradeType::Sell => {
@@ -169,14 +210,37 @@ impl Strategy for MomentumVelocityStrategy {
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;
let is_early_curve = trade.v_sol_in_bonding_curve < 60.0; let is_early_curve = trade.v_sol_in_bonding_curve < 60.0;
// Log detailed status of buy criteria evaluation on every trade
debug!(
"[{}] Trade #{} ({:?}) | Buyers: {}/{} [{}] | Net Flow: {:.3}/{:.3} SOL [{}] | Curve SOL: {:.2} < 60 [{}]",
mint,
tracker.trade_count,
trade.tx_type,
tracker.unique_buyers.len(),
self.min_unique_buyers,
if has_enough_buyers { "PASS" } else { "FAIL" },
tracker.net_sol_flow,
self.min_net_sol_flow,
if has_volume_surge { "PASS" } else { "FAIL" },
trade.v_sol_in_bonding_curve,
if is_early_curve { "PASS" } else { "FAIL" }
);
if has_enough_buyers && has_volume_surge && is_early_curve { if has_enough_buyers && has_volume_surge && is_early_curve {
info!(
"🚀 BUY SIGNAL TRIGGERED for {}! Unique Buyers: {}, Net Flow: {:.3} SOL, Curve SOL: {:.2}",
mint,
tracker.unique_buyers.len(),
tracker.net_sol_flow,
trade.v_sol_in_bonding_curve
);
bot.executor bot.executor
.lock() .lock()
.await .await
.buy(mint, BUY_AMOUNT_SOL, PRIORITY, SLIPPAGE) .buy(mint, BUY_AMOUNT_SOL, PRIORITY, SLIPPAGE)
.await?; .await?;
// Register open position
self.positions.insert( self.positions.insert(
mint.clone(), mint.clone(),
OpenPosition { OpenPosition {
@@ -186,9 +250,10 @@ impl Strategy for MomentumVelocityStrategy {
}, },
); );
// Stop tracking buy metrics (subscription remains active while holding position)
self.trackers.remove(mint); self.trackers.remove(mint);
} }
} else {
trace!("[{}] Received trade for untracked mint.", mint);
} }
Ok(()) Ok(())
+6
View File
@@ -6,6 +6,7 @@ pub enum PumpDevEvent {
Connected { client_id: u64, message: String }, Connected { client_id: u64, message: String },
ConnectionStatus { connected: bool, timestamp: u64 }, ConnectionStatus { connected: bool, timestamp: u64 },
Subscribed { method: String }, Subscribed { method: String },
Unsubscribed { method: String, keys: Vec<String> },
Create(NewToken), Create(NewToken),
Trade(Trade), Trade(Trade),
} }
@@ -40,6 +41,8 @@ impl<'de> Deserialize<'de> for PumpDevEvent {
ConnectionStatus { connected: bool, timestamp: u64 }, ConnectionStatus { connected: bool, timestamp: u64 },
#[serde(rename = "subscribed")] #[serde(rename = "subscribed")]
Subscribed { method: String }, Subscribed { method: String },
#[serde(rename = "unsubscribed")]
Unsubscribed { method: String, keys: Vec<String> },
} }
match serde_json::from_value(value).map_err(serde::de::Error::custom)? { match serde_json::from_value(value).map_err(serde::de::Error::custom)? {
@@ -54,6 +57,9 @@ impl<'de> Deserialize<'de> for PumpDevEvent {
timestamp, timestamp,
}), }),
Tagged::Subscribed { method } => Ok(PumpDevEvent::Subscribed { method }), Tagged::Subscribed { method } => Ok(PumpDevEvent::Subscribed { method }),
Tagged::Unsubscribed { method, keys } => {
Ok(PumpDevEvent::Unsubscribed { method, keys })
}
} }
} }
} }