Skip to main content

PortfolioProblem

Struct PortfolioProblem 

Source
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

Source

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.

Source

pub fn dimension(&self) -> usize

Number of portfolio weights.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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_i

which 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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source§

fn clone(&self) -> PortfolioProblem

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PortfolioProblem

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for PortfolioProblem

Source§

fn eq(&self, other: &PortfolioProblem) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for PortfolioProblem

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.