Debugging
This commit is contained in:
+2
-2
@@ -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) => {
|
||||||
|
|||||||
@@ -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
@@ -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,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 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user