115 lines
3.1 KiB
Rust
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);
|
|
}
|
|
} |