Files
my_solana_bot/src/position/manager.rs
T
2026-07-21 00:14:46 +08:00

187 lines
5.1 KiB
Rust

//! 仓位管理器
//!
//! 跟踪所有活跃仓位,执行止盈止损逻辑
use std::collections::HashMap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use tracing::{info, warn};
use crate::bonding_curve::{BondingCurveMath, LAMPORTS_PER_SOL, TOKEN_SCALING};
use crate::config::PositionConfig;
use super::exit_strategy::ExitStrategy;
/// 单个仓位
#[derive(Debug, Clone)]
pub struct Position {
/// 代币 Mint
pub mint: String,
/// Bonding Curve PDA
pub bonding_curve: String,
/// 买入 SOL 数量
pub entry_sol: f64,
/// 买入代币数量 (raw units)
pub entry_tokens: u64,
/// 当前持有代币数量
pub current_tokens: u64,
/// 买入交易签名
pub entry_tx: String,
/// 买入时间
pub entry_time: DateTime<Utc>,
/// 买入时价格
pub entry_price: f64,
/// 当前价格
pub current_price: f64,
/// 已实现收益 (SOL)
pub realized_pnl: f64,
/// 是否已毕业
pub graduated: bool,
/// PumpSwap 池地址 (毕业后)
pub pumpswap_pool: Option<String>,
/// 已触发的止盈级别
pub take_profit_level: u8,
}
impl Position {
/// 当前未实现收益 (SOL)
pub fn unrealized_pnl(&self) -> f64 {
let current_value = self.current_tokens as f64 * self.current_price;
let entry_value = self.entry_sol;
current_value - entry_value
}
/// 当前收益率
pub fn roi(&self) -> f64 {
if self.entry_sol <= 0.0 {
return 0.0;
}
let current_value = self.current_tokens as f64 * self.current_price;
current_value / self.entry_sol
}
/// 持仓时间 (秒)
pub fn holding_secs(&self) -> i64 {
Utc::now().timestamp() - self.entry_time.timestamp()
}
}
/// 仓位管理器
pub struct PositionManager {
config: PositionConfig,
positions: Arc<RwLock<HashMap<String, Position>>>,
exit_strategy: ExitStrategy,
}
impl PositionManager {
pub fn new(config: PositionConfig) -> Self {
let exit_strategy = ExitStrategy::new(&config);
Self {
config,
positions: Arc::new(RwLock::new(HashMap::new())),
exit_strategy,
}
}
/// 注册新仓位
pub fn register_position(
&self,
mint: &str,
bonding_curve: &str,
entry_sol: f64,
entry_tokens: u64,
entry_tx: String,
) {
let entry_price = BondingCurveMath::spot_price_sol_per_token(
crate::bonding_curve::INITIAL_VIRTUAL_SOL,
crate::bonding_curve::INITIAL_VIRTUAL_TOKENS,
);
let position = Position {
mint: mint.to_string(),
bonding_curve: bonding_curve.to_string(),
entry_sol,
entry_tokens,
current_tokens: entry_tokens,
entry_tx,
entry_time: Utc::now(),
entry_price,
current_price: entry_price,
realized_pnl: 0.0,
graduated: false,
pumpswap_pool: None,
take_profit_level: 0,
};
info!(
"📝 注册仓位: mint={} | {:.4} SOL | {} tokens",
mint,
entry_sol,
entry_tokens / TOKEN_SCALING,
);
self.positions.write().insert(mint.to_string(), position);
}
/// 更新代币价格
pub async fn update_price(
&self,
mint: &str,
virtual_sol: u64,
virtual_token: u64,
) {
let price = BondingCurveMath::spot_price_sol_per_token(virtual_sol, virtual_token);
let mut positions = self.positions.write();
if let Some(pos) = positions.get_mut(mint) {
pos.current_price = price;
// 检查退出条件
let exit_signal = self.exit_strategy.evaluate(pos);
if let Some(signal) = exit_signal {
info!(
"🚪 退出信号: mint={} | {} | ROI={:.2}x | 持仓{}s",
mint,
signal.reason,
pos.roi(),
pos.holding_secs(),
);
// TODO: 触发卖出交易
}
}
}
/// 毕业事件处理
pub async fn on_graduation(&self, mint: &str, pool: &str) {
let mut positions = self.positions.write();
if let Some(pos) = positions.get_mut(mint) {
pos.graduated = true;
pos.pumpswap_pool = Some(pool.to_string());
info!("🎓 仓位毕业: mint={} → pool={}", mint, pool);
}
}
/// 获取活跃仓位数量
pub fn active_count(&self) -> usize {
self.positions.read().len()
}
/// 获取所有仓位
pub fn all_positions(&self) -> Vec<Position> {
self.positions.read().values().cloned().collect()
}
/// 移除已关闭的仓位
pub fn close_position(&self, mint: &str) -> Option<Position> {
self.positions.write().remove(mint)
}
/// 总未实现 PnL
pub fn total_unrealized_pnl(&self) -> f64 {
self.positions
.read()
.values()
.map(|p| p.unrealized_pnl())
.sum()
}
}