pub struct PortfolioProblem { /* private fields */ }Expand description
A mean-variance portfolio problem backed by a factor covariance.
The objective is
risk_aversion / 2 * (w - b)' Σ (w - b) - expected_returns' w
+ turnover_penalty / 2 * ||w - previous_weights||²
+ l1_turnover_costs' |w - previous_weights|where the benchmark b defaults to zero (absolute risk) and is set by
PortfolioProblem::with_tracking_benchmark for tracking-error
problems. The benchmark only shifts the linear term — the QP structure
is unchanged — and the constant risk_aversion / 2 * b' Σ b is dropped
from reported objective values, as is conventional for QP solvers.
The quadratic turnover term is an L2 approximation useful when a smooth
preference for stable weights is acceptable
(PortfolioProblem::with_quadratic_turnover). The L1 term models
proportional transaction costs exactly
(PortfolioProblem::with_l1_turnover); it is handled by a dedicated
proximal block inside the solver, so it never grows the reduced system.
Both may be combined; they share one previous portfolio.
Implementations§
Source§impl PortfolioProblem
impl PortfolioProblem
Sourcepub fn new(
factors: Matrix,
omega: FactorCovariance,
specific_variance: Vec<f64>,
expected_returns: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn new( factors: Matrix, omega: FactorCovariance, specific_variance: Vec<f64>, expected_returns: Vec<f64>, ) -> Result<Self, PortfolioError>
Creates a long-only, fully invested mean-variance problem.
Defaults are risk aversion 1, budget 1, and bounds [0, 1].
§Errors
Returns an error when covariance data or expected-return dimensions are invalid.
Sourcepub fn with_risk_aversion(
self,
risk_aversion: f64,
) -> Result<Self, PortfolioError>
pub fn with_risk_aversion( self, risk_aversion: f64, ) -> Result<Self, PortfolioError>
Sets the positive multiplier on portfolio variance.
§Errors
Returns an error unless risk_aversion is finite and positive.
Sourcepub fn with_budget(self, budget: Option<f64>) -> Result<Self, PortfolioError>
pub fn with_budget(self, budget: Option<f64>) -> Result<Self, PortfolioError>
Sets the budget equality, or removes it with None.
§Errors
Returns an error when a supplied budget is not finite.
Sourcepub fn with_bounds(
self,
lower_bounds: Vec<f64>,
upper_bounds: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn with_bounds( self, lower_bounds: Vec<f64>, upper_bounds: Vec<f64>, ) -> Result<Self, PortfolioError>
Replaces the per-asset box constraints.
§Errors
Returns an error for wrong dimensions, NaNs, or lower bounds above upper bounds.
Sourcepub fn with_equalities(
self,
matrix: Matrix,
rhs: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn with_equalities( self, matrix: Matrix, rhs: Vec<f64>, ) -> Result<Self, PortfolioError>
Adds user-supplied equality constraints alongside the optional budget.
§Errors
Returns an error when dimensions or coefficients are invalid.
Sourcepub fn with_inequalities(
self,
matrix: Matrix,
rhs: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn with_inequalities( self, matrix: Matrix, rhs: Vec<f64>, ) -> Result<Self, PortfolioError>
Adds upper-form linear constraints matrix * weights <= rhs.
§Errors
Returns an error when dimensions or coefficients are invalid.
Sourcepub fn with_quadratic_turnover(
self,
previous_weights: Vec<f64>,
turnover_penalty: f64,
) -> Result<Self, PortfolioError>
pub fn with_quadratic_turnover( self, previous_weights: Vec<f64>, turnover_penalty: f64, ) -> Result<Self, PortfolioError>
Adds an L2 penalty around the previous portfolio.
This is not proportional transaction cost — use
PortfolioProblem::with_l1_turnover for that. The problem models a
single previous portfolio: calling this after with_l1_turnover
replaces the shared anchor.
§Errors
Returns an error for a wrong-length/non-finite previous portfolio or a negative/non-finite penalty.
Sourcepub fn with_l1_turnover(
self,
previous_weights: Vec<f64>,
costs: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn with_l1_turnover( self, previous_weights: Vec<f64>, costs: Vec<f64>, ) -> Result<Self, PortfolioError>
Adds exact proportional transaction costs
costs' |w - previous_weights| around the previous portfolio
(roadmap 2.1).
Unlike the L2 approximation, this is the real trading-cost model: a
per-asset charge proportional to the traded amount, with a genuine
no-trade region. The term is handled by a dedicated soft-threshold
proximal block, so the reduced factorization keeps its
factors + constraints dimension — an epigraph reformulation would
add 2n constraint rows instead.
May be combined with PortfolioProblem::with_quadratic_turnover;
the two terms share a single previous portfolio, and calling either
method replaces that shared anchor.
§Errors
Returns an error for wrong-length or non-finite previous weights, or costs that are not finite and non-negative.
Sourcepub fn with_tracking_benchmark(
self,
benchmark_weights: Vec<f64>,
) -> Result<Self, PortfolioError>
pub fn with_tracking_benchmark( self, benchmark_weights: Vec<f64>, ) -> Result<Self, PortfolioError>
Sets the benchmark for a tracking-error objective (roadmap 2.6).
The risk term becomes risk_aversion / 2 * (w - b)' Σ (w - b):
active risk against the benchmark instead of absolute risk. It is the
same QP underneath — expanding the square only shifts the linear
objective by -risk_aversion * Σ b — so no solver machinery changes
and rolling sequences keep their cached factorizations when the
benchmark moves.
The constant risk_aversion / 2 * b' Σ b is dropped from reported
objective values, as is conventional for QP solvers; add it back if
an absolute tracking-error number is needed.
The benchmark does not need to satisfy the portfolio’s constraints.
§Errors
Returns an error for wrong-length or non-finite benchmark weights.
Sourcepub fn with_industry_neutrality(
self,
industries: &[usize],
) -> Result<Self, PortfolioError>
pub fn with_industry_neutrality( self, industries: &[usize], ) -> Result<Self, PortfolioError>
Adds industry-neutrality equalities derived from the tracking benchmark (roadmap 3.1).
industries[i] is the zero-based industry id of asset i; industry
ids run from 0 to max(industries). One equality row is appended
per industry, pinning the portfolio’s industry weight to the
benchmark’s:
sum_{i in industry g} w_i = sum_{i in industry g} b_iwhich is exactly “zero net active industry exposure”. Requires
PortfolioProblem::with_tracking_benchmark to have been called
first; for explicit targets (or without a benchmark) use
PortfolioProblem::with_group_targets.
The rows are appended after any existing user equality rows, one per
industry in id order, and count as user equality rows from then on:
rolling sequences move the targets through
RebalanceStep::equality_rhs like any other
equality target. Call
PortfolioProblem::with_equalities before this method — it
replaces the user equality block.
§Errors
Returns an error when no tracking benchmark is set, industries has
the wrong length, or an industry has no member assets.
Sourcepub fn with_group_targets(
self,
groups: &[usize],
targets: &[f64],
) -> Result<Self, PortfolioError>
pub fn with_group_targets( self, groups: &[usize], targets: &[f64], ) -> Result<Self, PortfolioError>
Adds group-weight equalities: one row per group pinning the summed member weights to an explicit target (roadmap 3.1).
groups[i] is the zero-based group id of asset i and must be
smaller than targets.len(); group g contributes the row
sum_{i in group g} w_i = targets[g]This is the explicit-target form of
PortfolioProblem::with_industry_neutrality and also covers
sector/country sleeves or a zero-net-exposure book
(targets = [0.0, ...]).
The rows are appended after any existing user equality rows, one per
group in id order, and count as user equality rows from then on:
rolling sequences move the targets through
RebalanceStep::equality_rhs. Call
PortfolioProblem::with_equalities before this method — it
replaces the user equality block.
§Errors
Returns an error when groups has the wrong length or an id out of
range, a target is non-finite, or a group has no member assets.
Sourcepub fn with_style_bounds(
self,
exposures: &Matrix,
lower: &[f64],
upper: &[f64],
) -> Result<Self, PortfolioError>
pub fn with_style_bounds( self, exposures: &Matrix, lower: &[f64], upper: &[f64], ) -> Result<Self, PortfolioError>
Adds style-exposure bands lower[s] <= exposures[s] · w <= upper[s]
as linear constraint rows (roadmap 3.1).
exposures is styles-by-assets: row s holds the per-asset loadings
of style s (momentum, value, size, …). Use
f64::NEG_INFINITY / f64::INFINITY for one-sided bands. A band
with lower[s] == upper[s] becomes a single equality row (style
neutrality); other bands append their finite sides as inequality
rows.
Appended row order, per style in index order: the equality row (when
the band is exact), otherwise the upper row
exposures[s] · w <= upper[s] followed by the lower row
-exposures[s] · w <= -lower[s], skipping infinite sides. The rows
count as user constraint rows from then on: rolling sequences move
the bands through
RebalanceStep::equality_rhs /
RebalanceStep::inequality_rhs (mind the
sign convention on lower rows). Call
PortfolioProblem::with_equalities /
PortfolioProblem::with_inequalities before this method — they
replace their constraint blocks.
§Errors
Returns an error for dimension mismatches, non-finite loadings, NaN or crossing bounds, or a style with neither side finite.
Sourcepub fn with_concentration_limit(
self,
max_weight: f64,
) -> Result<Self, PortfolioError>
pub fn with_concentration_limit( self, max_weight: f64, ) -> Result<Self, PortfolioError>
Caps every asset’s absolute weight: |w_i| <= max_weight
(roadmap 3.1).
Concentration limits map onto the existing box constraints — upper
bounds are tightened to min(upper_i, max_weight) and lower bounds
raised to max(lower_i, -max_weight) — so no constraint rows are
added and the reduced factorization keeps its dimension. Long-only
problems (default bounds [0, 1]) end up with w_i <= max_weight.
Apply after PortfolioProblem::with_bounds; the tightening keeps
whichever side is already stricter.
§Errors
Returns an error unless max_weight is finite and positive, or when
the cap contradicts an existing bound (for example a forced minimum
position above the cap).
Sourcepub fn with_short_limit(self, max_short: f64) -> Result<Self, PortfolioError>
pub fn with_short_limit(self, max_short: f64) -> Result<Self, PortfolioError>
Caps every asset’s short position: w_i >= -max_short
(roadmap 3.1).
Short limits map onto the existing box constraints — lower bounds are
raised to max(lower_i, -max_short) — so no constraint rows are
added. max_short = 0 forces a long-only portfolio. This is a
per-asset limit; a total short-budget cap
(sum_i max(-w_i, 0) <= S) needs a long/short variable split that
the QP form deliberately does not model.
Apply after PortfolioProblem::with_bounds; the tightening keeps
whichever lower bound is already stricter.
§Errors
Returns an error unless max_short is finite and non-negative, or
when the limit contradicts an existing bound (an upper bound forcing
a deeper short).
Sourcepub fn to_qp(&self) -> Result<QpProblem, PortfolioError>
pub fn to_qp(&self) -> Result<QpProblem, PortfolioError>
Builds the standard-form QP consumed by the numerical kernel.
§Errors
Returns an error when the budget is already impossible under the box constraints or if final QP validation fails.
Sourcepub fn solve(
&self,
warm_start: Option<&WarmStart>,
) -> Result<Solution, PortfolioError>
pub fn solve( &self, warm_start: Option<&WarmStart>, ) -> Result<Solution, PortfolioError>
Solves with default settings.
§Errors
Returns setup and validation errors. A completed solve reports
convergence through crate::SolveStatus on the returned solution.
Sourcepub fn sequence(&self) -> Result<PortfolioSequence, PortfolioError>
pub fn sequence(&self) -> Result<PortfolioSequence, PortfolioError>
Builds a rolling PortfolioSequence with default settings
(roadmap 2.5).
The sequence reuses the equilibration and the reduced factorizations
across dates and chains warm starts automatically; per-date data
changes are described by crate::RebalanceStep.
§Errors
Returns setup and validation errors for the base problem.
Sourcepub fn sequence_with(
&self,
solver: &Solver,
) -> Result<PortfolioSequence, PortfolioError>
pub fn sequence_with( &self, solver: &Solver, ) -> Result<PortfolioSequence, PortfolioError>
Builds a rolling PortfolioSequence iterating with an explicitly
configured solver.
§Errors
Returns setup and validation errors for the base problem.
Sourcepub fn solve_with(
&self,
solver: &Solver,
warm_start: Option<&WarmStart>,
) -> Result<Solution, PortfolioError>
pub fn solve_with( &self, solver: &Solver, warm_start: Option<&WarmStart>, ) -> Result<Solution, PortfolioError>
Solves with an explicitly configured solver.
§Errors
Returns setup and validation errors. A completed solve reports
convergence through crate::SolveStatus on the returned solution.
Trait Implementations§
Source§impl Clone for PortfolioProblem
impl Clone for PortfolioProblem
Source§fn clone(&self) -> PortfolioProblem
fn clone(&self) -> PortfolioProblem
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more