use super::types::MomentumScore;
use super::types::WeightScheme;
pub(super) fn assign_weights(
indices: &[usize],
scores: &[MomentumScore],
scheme: WeightScheme,
) -> Vec<(usize, f64)> {
if indices.is_empty() {
return Vec::new();
}
match scheme {
WeightScheme::Equal => {
let w = 1.0 / indices.len() as f64;
indices.iter().map(|&idx| (idx, w)).collect()
}
WeightScheme::ScoreWeighted => {
let raw: Vec<f64> = indices
.iter()
.map(|&idx| scores[idx].momentum_score.abs())
.collect();
let total: f64 = raw.iter().sum();
if total < 1e-15 {
let w = 1.0 / indices.len() as f64;
return indices.iter().map(|&idx| (idx, w)).collect();
}
indices
.iter()
.zip(raw.iter())
.map(|(&idx, &v)| (idx, v / total))
.collect()
}
}
}
pub(super) fn compute_portfolio_vol(
signed_positions: &[(usize, f64)],
scores: &[MomentumScore],
corr: Option<&[Vec<f64>]>,
) -> f64 {
if signed_positions.is_empty() {
return 0.0;
}
let sigmas: Vec<f64> = signed_positions
.iter()
.map(|(idx, _)| scores[*idx].predicted_vol.max(0.0))
.collect();
if let Some(corr) = corr {
let mut var = 0.0;
for (i, (idx_i, w_i)) in signed_positions.iter().enumerate() {
for (j, (idx_j, w_j)) in signed_positions.iter().enumerate() {
let c_ij = corr
.get(*idx_i)
.and_then(|row| row.get(*idx_j))
.copied()
.unwrap_or(if idx_i == idx_j { 1.0 } else { 0.0 });
var += w_i * w_j * sigmas[i] * sigmas[j] * c_ij;
}
}
return var.abs().sqrt();
}
let var: f64 = signed_positions
.iter()
.zip(sigmas.iter())
.map(|((_, w), s)| (w * s).powi(2))
.sum();
var.sqrt()
}