portfolio_analyzer/
analysis.rs1use std::collections::{HashMap, HashSet};
3pub fn overlap_pct(
5 portfolio_weights: &HashMap<String, f64>,
6 target_tickers: &HashSet<String>,
7) -> f64 {
8 portfolio_weights
9 .iter()
10 .filter(|(sym, _)| target_tickers.contains(*sym))
11 .map(|(_, &w)| w)
12 .sum::<f64>()
13 * 100.0
14}
15pub fn diversity(weights: &HashMap<String, f64>) -> (f64, f64) {
17 let total: f64 = weights.values().sum();
18 if total == 0.0 {
19 return (1.0, 1.0);
20 }
21 let hhi: f64 = weights.values().map(|&w| (w / total).powi(2)).sum();
22 let eff_n = if hhi > 0.0 { 1.0 / hhi } else { 0.0 };
23 (hhi, eff_n)
24}
25
26pub fn portfolio_sd(weights: &HashMap<String, f64>, scores: &HashMap<String, f64>) -> f64 {
28 let mut numerator = 0.0f64;
29 let mut denom = 0.0f64;
30 for (sym, &w) in weights {
31 if let Some(&s) = scores.get(sym) {
32 numerator += w * s;
33 denom += w;
34 }
35 }
36 if denom > 0.0 {
37 numerator / denom
38 } else {
39 0.5
40 }
41}
42
43pub fn blend_target(
45 mom_weights: &HashMap<String, f64>,
46 intl_weights: &HashMap<String, f64>,
47 p_m: f64,
48 p_i: f64,
49) -> HashMap<String, f64> {
50 let mut target: HashMap<String, f64> = HashMap::new();
51 for (sym, &w) in mom_weights {
52 *target.entry(sym.clone()).or_insert(0.0) += p_m * w;
53 }
54 for (sym, &w) in intl_weights {
55 *target.entry(sym.clone()).or_insert(0.0) += p_i * w;
56 }
57 target
58}
59
60pub struct OptimalWeights {
61 pub p_m: f64,
62 pub p_i: f64,
63 pub a1: f64,
64 pub a2: f64,
65 pub a3: f64,
66 pub a4: f64,
67 pub p_ratio: f64,
68}
69
70pub fn compute_optimal_weights(
71 t_val: f64,
72 n_val: f64,
73 sd_i: f64,
74 sd_m: f64,
75 sd_t: f64,
76 sd_n: f64,
77 o_m: f64,
78 o_i: f64,
79 o_j: f64,
80 o_mn: f64,
81) -> OptimalWeights {
82 let p = t_val / (t_val + n_val);
83
84 let o_i_frac = o_i / 100.0;
85 let o_j_frac = o_j / 100.0;
86 let o_m_frac = o_m / 100.0;
87 let o_mn_frac = o_mn / 100.0;
88
89 let a1_raw = sd_i * (1.0 - o_i_frac) * p * sd_t; let a2_raw = sd_i * (1.0 - o_j_frac) * (1.0 - p) * sd_n; let a3_raw = sd_m * (1.0 - o_m_frac) * p * sd_t; let a4_raw = sd_m * (1.0 - o_mn_frac) * (1.0 - p) * sd_n; let z = a1_raw + a2_raw + a3_raw + a4_raw;
97 let (a1, a2, a3, a4) = if z > 1e-10 {
98 (a1_raw / z, a2_raw / z, a3_raw / z, a4_raw / z)
99 } else {
100 (0.25, 0.25, 0.25, 0.25)
101 };
102
103 let p_m = a3 + a4;
104 let p_i = a1 + a2;
105
106 OptimalWeights {
107 p_m,
108 p_i,
109 a1,
110 a2,
111 a3,
112 a4,
113 p_ratio: p,
114 }
115}