Trade log

This commit is contained in:
2026-08-07 05:04:16 +02:00
parent b09423d82b
commit d65e1aec0e
4 changed files with 111 additions and 26 deletions
+3
View File
@@ -10,6 +10,7 @@ use crate::{
account::AccountManager, account::AccountManager,
executor::Executor, executor::Executor,
strategy::{Strategy, veloc::MomentumVelocityStrategy}, strategy::{Strategy, veloc::MomentumVelocityStrategy},
tradelog::TradeLog,
}; };
pub struct Bot { pub struct Bot {
@@ -17,6 +18,7 @@ pub struct Bot {
pub accounts: Mutex<AccountManager>, pub accounts: Mutex<AccountManager>,
pub executor: Mutex<Box<dyn Executor>>, pub executor: Mutex<Box<dyn Executor>>,
pub strategy: Mutex<Box<dyn Strategy>>, pub strategy: Mutex<Box<dyn Strategy>>,
pub trade_log: Mutex<Vec<TradeLog>>,
} }
impl Bot { impl Bot {
@@ -70,6 +72,7 @@ impl Bot {
executor: Mutex::new(Box::new(account.executor())), executor: Mutex::new(Box::new(account.executor())),
accounts: Mutex::new(accounts), accounts: Mutex::new(accounts),
strategy: Mutex::new(Box::new(MomentumVelocityStrategy::new())), strategy: Mutex::new(Box::new(MomentumVelocityStrategy::new())),
trade_log: Mutex::new(Vec::new()),
})) }))
} }
+1
View File
@@ -3,6 +3,7 @@ pub mod bot;
pub mod executor; pub mod executor;
pub mod strategy; pub mod strategy;
pub mod types; pub mod types;
pub mod tradelog;
use crate::bot::Bot; use crate::bot::Bot;
+35 -26
View File
@@ -7,6 +7,7 @@ use rust_decimal::{Decimal, dec};
use crate::bot::Bot; use crate::bot::Bot;
use crate::strategy::Strategy; use crate::strategy::Strategy;
use crate::tradelog::{ExitReason, TradeLog};
use crate::types::{NewToken, Trade, TradeType}; use crate::types::{NewToken, Trade, TradeType};
const BUY_AMOUNT_SOL: Decimal = dec!(0.2); const BUY_AMOUNT_SOL: Decimal = dec!(0.2);
@@ -22,7 +23,8 @@ struct TokenTracker {
} }
struct OpenPosition { struct OpenPosition {
entry_price_sol: f64, trade: TradeLog,
highest_price_sol: f64, highest_price_sol: f64,
last_high_time: Instant, last_high_time: Instant,
} }
@@ -133,7 +135,8 @@ impl Strategy for MomentumVelocityStrategy {
// 1. Manage Active Positions (Take Profit / Stop Loss / Stall) // 1. Manage Active Positions (Take Profit / Stop Loss / Stall)
// ------------------------------------------------------------- // -------------------------------------------------------------
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.trade.entry_price_sol) / pos.trade.entry_price_sol;
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;
@@ -143,38 +146,38 @@ impl Strategy for MomentumVelocityStrategy {
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, reason) = match () { let should_sell = match () {
_ if price_change_pct >= 0.40 => ( _ if price_change_pct >= 0.40 => Some(ExitReason::TakeProfit),
true, _ if price_change_pct <= -0.1 => Some(ExitReason::StopLoss),
format!("Take Profit (+{:.1}%)", price_change_pct * 100.0), _ if drop_from_peak >= 0.12 && price_change_pct > 0.10 => {
), Some(ExitReason::TrailingStop)
_ if price_change_pct <= -0.1 => (
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 pos.last_high_time.elapsed() >= Duration::from_secs(25) => {
Some(ExitReason::MomentumStalled)
}
_ => None,
}; };
if should_sell { if let Some(reason) = should_sell {
info!("[{}] EXECUTING SELL. Reason: {}", mint, reason); info!(
"[{}] EXECUTING SELL. Reason: {:?} {:.1}%",
mint,
reason,
price_change_pct * 100.0
);
bot.executor bot.executor
.lock() .lock()
.await .await
.sell_percent(mint, 100, PRIORITY, SLIPPAGE) .sell_percent(mint, 100, PRIORITY, SLIPPAGE)
.await?; .await?;
self.positions.remove(mint); if let Some(mut pos) = self.positions.remove(mint) {
pos.trade.close(current_price, reason);
info!("TRADE RESULT: {:?}", pos.trade);
}
self.cleanup_and_unsubscribe(&bot, mint).await?; self.cleanup_and_unsubscribe(&bot, mint).await?;
} }
@@ -245,7 +248,13 @@ impl Strategy for MomentumVelocityStrategy {
self.positions.insert( self.positions.insert(
mint.clone(), mint.clone(),
OpenPosition { OpenPosition {
entry_price_sol: current_price, trade: TradeLog::new(
mint.clone(),
current_price,
tracker.unique_buyers.len(),
tracker.net_sol_flow,
trade.v_sol_in_bonding_curve,
),
highest_price_sol: current_price, highest_price_sol: current_price,
last_high_time: Instant::now(), last_high_time: Instant::now(),
}, },
+72
View File
@@ -0,0 +1,72 @@
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub enum ExitReason {
TakeProfit,
StopLoss,
TrailingStop,
MomentumStalled,
}
#[derive(Debug, Clone)]
pub struct TradeLog {
pub mint: String,
pub opened_at: Instant,
pub closed_at: Option<Instant>,
pub entry_price_sol: f64,
pub exit_price_sol: Option<f64>,
pub pnl_percent: Option<f64>,
pub duration: Option<Duration>,
pub exit_reason: Option<ExitReason>,
// Conditions when entered
pub unique_buyers: usize,
pub net_sol_flow: f64,
pub curve_sol: f64,
}
impl TradeLog {
pub fn new(
mint: String,
entry_price_sol: f64,
unique_buyers: usize,
net_sol_flow: f64,
curve_sol: f64,
) -> Self {
Self {
mint,
opened_at: Instant::now(),
closed_at: None,
entry_price_sol,
exit_price_sol: None,
pnl_percent: None,
duration: None,
exit_reason: None,
unique_buyers,
net_sol_flow,
curve_sol,
}
}
pub fn close(&mut self, exit_price_sol: f64, reason: ExitReason) {
let now = Instant::now();
let pnl = ((exit_price_sol - self.entry_price_sol) / self.entry_price_sol) * 100.0;
self.exit_price_sol = Some(exit_price_sol);
self.pnl_percent = Some(pnl);
self.closed_at = Some(now);
self.duration = Some(now.duration_since(self.opened_at));
self.exit_reason = Some(reason);
}
}