Skip to main content

rebalance/
rebalance.rs

1//! Two-date mean-variance rebalance through the high-level Rust API.
2
3use 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}