Skip to main content

rust_portfolio_opt/
lib.rs

1//! Pure-Rust port of [PyPortfolioOpt][pyo].
2//!
3//! The crate mirrors PyPortfolioOpt's module structure:
4//!
5//! - [`expected_returns`] — historical mean / EMA / CAPM return estimators.
6//! - [`risk_models`] — sample / EWMA / semi-covariance + Ledoit-Wolf and
7//!   oracle-approximating shrinkage; cov ↔ corr helpers.
8//! - [`efficient_frontier`] — mean-variance optimisation: minimum variance,
9//!   tangency (max Sharpe), efficient risk / return, with weight bounds.
10//! - [`black_litterman`] — equilibrium prior + view-blended posterior.
11//! - [`hrp`] — hierarchical risk parity (correlation distance, single
12//!   linkage, recursive bisection).
13//! - [`cla`] — Markowitz's Critical Line Algorithm.
14//! - [`discrete_allocation`] — convert continuous weights to integer share
15//!   counts under a budget.
16//!
17//! All matrix / vector inputs and outputs use [`nalgebra`]'s `DMatrix` /
18//! `DVector` so they slot into broader nalgebra-based pipelines.
19//!
20//! Each estimator and optimiser also has a `_labeled` companion that
21//! accepts ticker names alongside the prices and returns
22//! `BTreeMap<String, f64>` (ordered by ticker), mirroring how
23//! PyPortfolioOpt accepts a `pandas.DataFrame` and returns an
24//! `OrderedDict`. See [`LabeledVector`] / [`LabeledMatrix`] for the
25//! intermediate types.
26//!
27//! Every estimator and optimiser keeps the same default annualisation
28//! convention as PyPortfolioOpt: 252 trading days unless explicitly
29//! overridden by the caller.
30//!
31//! [pyo]: https://github.com/PyPortfolio/PyPortfolioOpt
32
33pub mod black_litterman;
34pub mod cla;
35pub mod discrete_allocation;
36pub mod efficient_frontier;
37pub mod expected_returns;
38pub mod hrp;
39pub mod risk_models;
40
41mod prelude;
42mod qp;
43
44pub use prelude::*;
45
46/// Crate-wide error type. Most fallible operations return [`PortfolioError`]
47/// rather than panicking so callers can decide how to recover from
48/// misconfigured inputs (mismatched shapes, infeasible optimisation, etc.).
49#[derive(Debug, thiserror::Error)]
50pub enum PortfolioError {
51    #[error("dimension mismatch: {0}")]
52    DimensionMismatch(String),
53
54    #[error("invalid argument: {0}")]
55    InvalidArgument(String),
56
57    #[error("matrix is not positive (semi-)definite: {0}")]
58    NotPositiveDefinite(String),
59
60    #[error("optimisation failed: {0}")]
61    OptimisationFailed(String),
62
63    #[error("infeasible problem: {0}")]
64    Infeasible(String),
65
66    #[error("singular system: {0}")]
67    Singular(String),
68}
69
70/// Convenience alias used by every module in the crate.
71pub type Result<T> = std::result::Result<T, PortfolioError>;
72
73/// Default annualisation factor — 252 trading days per year, matching
74/// PyPortfolioOpt's defaults.
75pub const TRADING_DAYS_PER_YEAR: usize = 252;