Trade log
This commit is contained in:
@@ -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()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user