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

115 lines
3.1 KiB
Rust

//! 仓位计算器
//!
//! 根据评分和风控参数计算最优仓位大小
use crate::config::DecisionConfig;
/// 仓位计算器
pub struct PositionSizer {
max_position_sol: f64,
min_position_sol: f64,
}
impl PositionSizer {
pub fn new(config: &DecisionConfig) -> Self {
// 从全局配置获取 (这里简化,实际应从 PositionConfig 获取)
Self {
max_position_sol: 0.1, // 默认值,实际从配置读取
min_position_sol: 0.01,
}
}
/// 使用完整配置初始化
pub fn with_limits(max_sol: f64, min_sol: f64) -> Self {
Self {
max_position_sol: max_sol,
min_position_sol: min_sol,
}
}
/// 根据评分计算仓位
///
/// 策略: 线性映射
/// - 评分 60 (阈值) → min_position
/// - 评分 100 (满分) → max_position
///
/// 公式: position = min + (max - min) * (score - threshold) / (100 - threshold)
pub fn calculate(&self, score: f64) -> f64 {
let threshold = 60.0; // 最低通过分数
if score <= threshold {
return 0.0;
}
let normalized = (score - threshold) / (100.0 - threshold);
let position = self.min_position_sol
+ (self.max_position_sol - self.min_position_sol) * normalized;
position.clamp(self.min_position_sol, self.max_position_sol)
}
/// Kelly 公式仓位计算 (高级)
///
/// f* = (bp - q) / b
/// 其中:
/// - b = 赔率 (预期收益/预期亏损)
/// - p = 胜率
/// - q = 1 - p
///
/// 使用 Half-Kelly 降低风险
pub fn kelly_fraction(&self, win_rate: f64, avg_win: f64, avg_loss: f64) -> f64 {
if avg_loss <= 0.0 || win_rate <= 0.0 || win_rate >= 1.0 {
return 0.0;
}
let b = avg_win / avg_loss; // 赔率
let p = win_rate;
let q = 1.0 - p;
let kelly = (b * p - q) / b;
// Half-Kelly (更保守)
let half_kelly = kelly * 0.5;
half_kelly.clamp(0.0, 0.1) // 最大 10% 仓位
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_sizing() {
let sizer = PositionSizer::with_limits(0.1, 0.01);
// 评分 60 (阈值) → 最小仓位
let pos_60 = sizer.calculate(60.0);
assert!(pos_60 <= 0.011); // 接近 min
// 评分 100 → 最大仓位
let pos_100 = sizer.calculate(100.0);
assert!((pos_100 - 0.1).abs() < 0.001);
// 评分 80 → 中间
let pos_80 = sizer.calculate(80.0);
assert!(pos_80 > 0.01 && pos_80 < 0.1);
// 评分 50 (低于阈值) → 0
let pos_50 = sizer.calculate(50.0);
assert_eq!(pos_50, 0.0);
}
#[test]
fn test_kelly_fraction() {
let sizer = PositionSizer::with_limits(0.1, 0.01);
// 胜率 60%, 赔率 3:1
let f = sizer.kelly_fraction(0.6, 3.0, 1.0);
assert!(f > 0.0 && f <= 0.1);
// 胜率 50%, 赔率 1:1 → Kelly = 0
let f2 = sizer.kelly_fraction(0.5, 1.0, 1.0);
assert_eq!(f2, 0.0);
}
}