fix(adaptive-strategy): replace fake Kelly return history with error + fallback

get_historical_returns was returning a hardcoded 20-value vector of fake
returns for ALL symbols, causing Kelly criterion to compute position sizes
based on fabricated data. Now returns an error explaining that no market
data feed is connected.

calculate_position_size catches the Kelly/PPO sizing errors and falls
back to standard fixed-fraction sizing with a warning log, instead of
propagating the error to callers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-02-23 23:22:50 +01:00
parent 477dd47d3f
commit 434f9fbf7a

View File

@@ -19,7 +19,7 @@ use anyhow::Result;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::{debug, info};
use tracing::{debug, info, warn};
// Add missing core types
use super::config::{PositionSizingMethod, RiskConfig};
@@ -382,26 +382,50 @@ impl RiskManager {
symbol, expected_return
);
// Use enhanced Kelly sizer if available and method is Kelly
// Use enhanced Kelly sizer if available and method is Kelly.
// Falls back to standard sizing if historical data is unavailable.
if let PositionSizingMethod::Kelly = &self.config.position_sizing_method {
if self.kelly_sizer.is_some() {
return self
match self
.calculate_kelly_position_size(
symbol,
expected_return,
confidence,
current_price,
)
.await;
.await
{
Ok(recommendation) => return Ok(recommendation),
Err(e) => {
warn!(
symbol = %symbol,
error = %e,
"Kelly sizing failed (no historical data?), falling back to fixed-fraction sizing"
);
// Fall through to standard sizing below
}
}
}
}
// Use PPO sizer if available and method is PPO
// Use PPO sizer if available and method is PPO.
// Falls back to standard sizing if historical data is unavailable.
if let PositionSizingMethod::PPO = &self.config.position_sizing_method {
if self.ppo_sizer.is_some() {
return self
match self
.calculate_ppo_position_size(symbol, expected_return, confidence, current_price)
.await;
.await
{
Ok(recommendation) => return Ok(recommendation),
Err(e) => {
warn!(
symbol = %symbol,
error = %e,
"PPO sizing failed (no historical data?), falling back to fixed-fraction sizing"
);
// Fall through to standard sizing below
}
}
}
}
@@ -555,15 +579,17 @@ impl RiskManager {
})
}
/// Get historical returns for a symbol (production implementation)
async fn get_historical_returns(&self, _symbol: &str) -> Result<Vec<f64>> {
// In production, this would fetch from market data service
// For now, return sample data
Ok(vec![
0.05_f64, -0.02_f64, 0.08_f64, -0.03_f64, 0.06_f64, -0.01_f64, 0.04_f64, -0.02_f64,
0.07_f64, -0.01_f64, 0.03_f64, -0.04_f64, 0.09_f64, -0.02_f64, 0.05_f64, -0.03_f64,
0.06_f64, -0.01_f64, 0.08_f64, -0.02_f64,
])
/// Get historical returns for a symbol.
///
/// Returns an error because no market data feed is connected yet.
/// Callers should fall back to fixed-fraction position sizing when this fails.
async fn get_historical_returns(&self, symbol: &str) -> Result<Vec<f64>> {
anyhow::bail!(
"No historical return data available for '{}'. \
A market data service must be integrated to provide real return history. \
Falling back to fixed-fraction sizing is recommended.",
symbol
)
}
/// Build market data for Kelly calculation