1use std::error::Error;
4
5use ledge::{FactorCovariance, Matrix, PortfolioProblem, Solution, SolveStatus};
6
7fn portfolio(expected_returns: Vec<f64>) -> Result<PortfolioProblem, Box<dyn Error>> {
8 let factors = Matrix::new(
9 6,
10 2,
11 vec![
12 0.8, 0.1, 0.7, -0.2, 0.2, 0.9, -0.1, 0.8, -0.5, 0.3, -0.6, -0.4,
13 ],
14 )?;
15 Ok(PortfolioProblem::new(
16 factors,
17 FactorCovariance::Diagonal(vec![0.08, 0.05]),
18 vec![0.12, 0.10, 0.09, 0.11, 0.08, 0.10],
19 expected_returns,
20 )?
21 .with_risk_aversion(6.0)?
22 .with_bounds(vec![0.0; 6], vec![0.35; 6])?)
23}
24
25fn require_solved(solution: &Solution) -> Result<(), Box<dyn Error>> {
26 if solution.status == SolveStatus::Solved {
27 Ok(())
28 } else {
29 Err(format!(
30 "solve stopped with status '{}' after {} iterations (primal {:.3e}, dual {:.3e})",
31 solution.status,
32 solution.iterations,
33 solution.residuals.primal,
34 solution.residuals.dual
35 )
36 .into())
37 }
38}
39
40fn main() -> Result<(), Box<dyn Error>> {
41 let first = portfolio(vec![0.09, 0.08, 0.07, 0.06, 0.05, 0.04])?.solve(None)?;
42 require_solved(&first)?;
43
44 let warm_start = first.warm_start();
45 let second = portfolio(vec![0.07, 0.08, 0.10, 0.05, 0.06, 0.04])?
46 .with_quadratic_turnover(first.x.clone(), 0.4)?
47 .solve(Some(&warm_start))?;
48 require_solved(&second)?;
49
50 let budget: f64 = second.x.iter().sum();
51 let one_way_turnover: f64 = first
52 .x
53 .iter()
54 .zip(&second.x)
55 .map(|(old, new)| (new - old).abs())
56 .sum::<f64>()
57 / 2.0;
58 println!("first weights: {:.6?}", first.x);
59 println!("second weights: {:.6?}", second.x);
60 println!("budget: {budget:.10}");
61 println!("one-way turnover: {one_way_turnover:.6}");
62 println!(
63 "status: {}, iterations: {}, primal residual: {:.3e}, dual residual: {:.3e}",
64 second.status, second.iterations, second.residuals.primal, second.residuals.dual
65 );
66 Ok(())
67}