187 lines
5.1 KiB
Rust
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()
|
|
}
|
|
} |