Mean Risk

PortfolioOptimisers.MeanRiskResultType
struct MeanRiskResult{__T_jr, __T_r, __T_fb} <: RiskJuMPOptimisationResult

Result type for Mean-Risk portfolio optimisation.

Fields

  • r: The risk measure the optimisation ran under, or a vector of them, stored resolved — a Deferred Quantity has already been fitted and an unstated slot has already taken the prior's field. A resolved measure is fitted state, not configuration, so it belongs on the Result. Pass it back as expected_risk(res.r, res.w, res.pr; sca = res.sca).
  • fb: The fallback chain that answered this result: the (estimator, result) pair of every attempt optimise made before this one, in the order they ran, or nothing when the estimator it was asked of answered (see FbChain).

Property access delegates to the embedded JuMPOptimisationResult: the virtual :w property and unknown properties resolve through jr.

Constructors

MeanRiskResult(;    jr::JuMPOptimisationResult, r::BaseRM_VecBaseRM, fb::Option{<:OptE_Opt_FbChain}) -> MeanRiskResult

Keywords correspond to the struct's fields.

Related

source
PortfolioOptimisers.MeanRiskType
struct MeanRisk{__T_opt, __T_r, __T_obj, __T_wi, __T_fb} <: RiskJuMPOptimisationEstimator

Mean-Risk portfolio optimiser.

MeanRisk formulates and solves a mean-risk portfolio optimisation problem using JuMP. It can optimise a wide variety of objective functions (minimum risk, maximum return, maximum Sharpe ratio, maximum utility) subject to risk, weight, cardinality, and custom constraints.

Fields

  • opt: JuMP optimiser configuration.
  • r: Risk measure or vector of risk measures.
  • obj: Portfolio objective function.
  • wi: Initial portfolio weights for warm-starting the solver.
  • fb: Fallback result or estimator.

Constructors

MeanRisk(;    opt::JuMPOptimiser,    r::TD{<:RM_VecRM} = Variance(),    obj::TD{<:ObjectiveFunction} = MinimumRisk(),    wi::TD_Option{<:VecNum} = nothing,    fb::TDO_Option{<:OptE_Opt} = nothing) -> MeanRisk

Keywords correspond to the struct's fields. Fields typed TD, TD_Option or TDO_Option may hold a TimeDependent per-fold schedule instead of a static value: the risk measure, objective, warm start and fallback are problem definition, so a cross-validation fold loop resolves them per fold, and a fold-less optimise runs with each at its static default (nothing for wi and fb, so a scheduled fallback is disabled outside fold loops unless the schedule carries a default).

Validation

  • If r is a vector: !isempty(r).
  • If wi is provided: !isempty(wi).
  • fb schedules: bind !== :nearest.
  • A risk expression that is identically zero is refused with MinimumRisk and with MaximumRatio. Every feasible portfolio is optimal under the first, and the second is unbounded. A NoRisk measure and settings.rke = false on every measure are the two routes to it.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • opt: Recursively updated via factory.
  • r: Recursively updated via factory.
  • fb: Recursively updated via factory.

View parameters

MeanRisk defines its own port_opt_view method rather than deriving one from field tags.

  • The method reads the returns matrix X as its third argument. When opt.pe already holds a prior result, the method replaces X with opt.pe.X, so the children are viewed against the prior's own observations rather than the caller's matrix.
  • opt and r recurse through port_opt_view with that matrix.
  • wi is sliced to the selected assets.
  • obj and fb are carried through unchanged.

Examples

julia> MeanRisk(; opt = JuMPOptimiser(; slv = Solver(; solver = nothing)))MeanRisk  opt ┼ JuMPOptimiser      │        pe ┼ EmpiricalPrior      │           │           ce ┼ PortfolioOptimisersCovariance      │           │              │   ce ┼ Covariance      │           │              │      │    me ┼ SimpleExpectedReturns      │           │              │      │       │   w ┴ nothing      │           │              │      │    ce ┼ GeneralCovariance      │           │              │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)      │           │              │      │       │    w ┴ nothing      │           │              │      │   alg ┼ FullMoment()      │           │              │      │     w ┴ nothing      │           │              │   mp ┼ MatrixProcessing      │           │              │      │     pdm ┼ Posdef      │           │              │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton      │           │              │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()      │           │              │      │      dn ┼ nothing      │           │              │      │      dt ┼ nothing      │           │              │      │     alg ┼ nothing      │           │              │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)      │           │           me ┼ SimpleExpectedReturns      │           │              │   w ┴ nothing      │           │      horizon ┼ nothing      │           │   fill_limit ┴ nothing      │       slv ┼ Solver      │           │          name ┼ String: ""      │           │        solver ┼ nothing      │           │      settings ┼ nothing      │           │     check_sol ┼ @NamedTuple{}: NamedTuple()      │           │   add_bridges ┴ Bool: true      │        wb ┼ WeightBounds      │           │   lb ┼ Float64: 0.0      │           │   ub ┴ Float64: 1.0      │       bgt ┼ Float64: 1.0      │      sbgt ┼ nothing      │      gbgt ┼ nothing      │      xbgt ┼ Bool: false      │        lt ┼ nothing      │        st ┼ nothing      │      lcse ┼ nothing      │       cte ┼ nothing      │    gcarde ┼ nothing      │   sgcarde ┼ nothing      │      smtx ┼ nothing      │     sgmtx ┼ nothing      │       slt ┼ nothing      │       sst ┼ nothing      │      sglt ┼ nothing      │      sgst ┼ nothing      │        tn ┼ nothing      │      fees ┼ nothing      │      sets ┼ nothing      │        tr ┼ nothing      │       ple ┼ nothing      │       ret ┼ ArithmeticReturn      │           │   settings ┼ JuMPReturnsSettings      │           │            │   scale ┼ Float64: 1.0      │           │            │      lb ┼ nothing      │           │            │     rte ┼ Bool: true      │           │            │     fee ┼ Bool: true      │           │            │     mic ┴ Bool: true      │           │        ucs ┼ nothing      │           │         mu ┴ nothing      │       sca ┼ SumScalariser()      │      ccnt ┼ nothing      │      cobj ┼ nothing      │        sc ┼ Int64: 1      │        so ┼ Int64: 1      │        ss ┼ nothing      │      card ┼ nothing      │     scard ┼ nothing      │       l2c ┼ nothing      │       lpc ┼ nothing      │     linfc ┼ nothing      │        l1 ┼ nothing      │        l2 ┼ nothing      │        lp ┼ nothing      │      linf ┼ nothing      │       brt ┼ Bool: false      │     x_src ┼ Symbol: :prior      │    strict ┴ Bool: false    r ┼ Variance      │   settings ┼ RiskMeasureSettings      │            │   scale ┼ Float64: 1.0      │            │      ub ┼ nothing      │            │     rke ┴ Bool: true      │      sigma ┼ nothing      │       chol ┼ nothing      │         rc ┼ nothing      │        alg ┴ SquaredSOCRiskExpr()  obj ┼ MinimumRisk()   wi ┼ nothing   fb ┴ nothing

Mathematical definition

The general mean-risk optimisation problem is:

\[\begin{align} \underset{\boldsymbol{w}}{\min} \; f(\boldsymbol{w}) \quad \text{s.t.} \quad \boldsymbol{w} \in \mathcal{W}\,. \end{align}\]

Objective $f$ depends on ObjectiveFunction:

  • MinimumRisk: $f(\boldsymbol{w}) = \rho(\boldsymbol{w})$
  • MaximumReturn: $f(\boldsymbol{w}) = -\hat{\boldsymbol{\mu}}^\intercal \boldsymbol{w}$
  • MaximumUtility: $f(\boldsymbol{w}) = -\hat{\boldsymbol{\mu}}^\intercal \boldsymbol{w} + \lambda \rho(\boldsymbol{w})$
  • MaximumRatio (Sharpe): $f(\boldsymbol{w}) = -(\hat{\boldsymbol{\mu}}^\intercal \boldsymbol{w} - r_f) / \rho(\boldsymbol{w})$

The ratio is not solved in that form. The model homogenises it into a linear problem with an auxiliary scalar k, and de-homogenises the weights afterwards. See MaximumRatio.

Where:

  • $\boldsymbol{w}$: Portfolio weight vector.
  • $\mathcal{W}$: Feasible weight set defined by portfolio constraints.
  • $f(\boldsymbol{w})$: Objective function (depends on ObjectiveFunction).
  • $\rho(\boldsymbol{w})$: Portfolio risk measure.
  • $\hat{\boldsymbol{\mu}}$: Estimated expected return vector.
  • $\lambda$: Risk aversion parameter.
  • $r_f$: Risk-free rate.

Related

References

  • [11] H. Markowitz. Modern portfolio theory. Journal of Finance 7, 77–91 (1952).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Chapter 8.
source
PortfolioOptimisers.factoryMethod
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a
factory(a::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                                  <:AbstractResult}}, args...; kwargs...) -> Vector

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

The vector method is the one forwarding contract for every vector-valued propagation field: it applies factory to each element and forwards args... and kwargs... unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract), defines its own more specific method.

Algorithm

The scalar method:

  1. Return a unchanged, and drop args... and kwargs.... This method is the leaf of the recursion, and it is what makes an untagged type safe to call the verb on.

The vector method:

  1. For each element ai of a, call factory on ai, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of a, and return it.

A @propagatable struct with at least one @fprop- or @wprop-tagged field carries a generated method that dominates the scalar method. That method rebuilds the struct with its keyword constructor, sending each @fprop field through factory_child and each @wprop field through _wprop.

Arguments

  • a: Indicates no object should be constructed, or a vector whose elements are rebuilt one by one.
  • args...: Arbitrary positional arguments (ignored by the scalar method, forwarded by the vector method).
  • kwargs...: Arbitrary keyword arguments (ignored by the scalar method, forwarded by the vector method).

Returns

  • a: The input unchanged.
  • v::Vector: The element-wise rebuilds, for the vector method.

Examples

julia> factory(nothing, 1, 2; x = 3)julia> factory(MeanValue())MeanValue  w ┴ nothing

Related

source
factory(res::NonFiniteAllocationOptimisationResult, fb::Option{<:OptE_Opt_FbChain})

Rebuild a continuous optimisation result with an updated fallback record fb.

Every optimisation result carries fb as its last field, so the generic rebuild copies all fields unchanged except the trailing fb. Concrete result types may override this method when rebuilding requires more than swapping fb. optimise is the one caller, and it hands in the FbChain it walked.

Related

source
factory(
    opt::Union{NonFiniteAllocationOptimisationEstimator, NonFiniteAllocationOptimisationResult},
    _
) -> RandomWeighted{_A, var"#s185", _B, _C, _D, _E, _F, Bool} where {_A, var"#s185"<:AbstractRNG, _B, _C, _D, _E, _F}

Return opt unchanged.

Default pass-through factory for optimisation estimators and results. Overridden for estimators that carry parameters requiring update at each optimisation step.

Related

source
PortfolioOptimisers.factoryMethod
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a
factory(a::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                                  <:AbstractResult}}, args...; kwargs...) -> Vector

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

The vector method is the one forwarding contract for every vector-valued propagation field: it applies factory to each element and forwards args... and kwargs... unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract), defines its own more specific method.

Algorithm

The scalar method:

  1. Return a unchanged, and drop args... and kwargs.... This method is the leaf of the recursion, and it is what makes an untagged type safe to call the verb on.

The vector method:

  1. For each element ai of a, call factory on ai, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of a, and return it.

A @propagatable struct with at least one @fprop- or @wprop-tagged field carries a generated method that dominates the scalar method. That method rebuilds the struct with its keyword constructor, sending each @fprop field through factory_child and each @wprop field through _wprop.

Arguments

  • a: Indicates no object should be constructed, or a vector whose elements are rebuilt one by one.
  • args...: Arbitrary positional arguments (ignored by the scalar method, forwarded by the vector method).
  • kwargs...: Arbitrary keyword arguments (ignored by the scalar method, forwarded by the vector method).

Returns

  • a: The input unchanged.
  • v::Vector: The element-wise rebuilds, for the vector method.

Examples

julia> factory(nothing, 1, 2; x = 3)julia> factory(MeanValue())MeanValue  w ┴ nothing

Related

source
factory(
    opt::Union{NonFiniteAllocationOptimisationEstimator, NonFiniteAllocationOptimisationResult},
    _
) -> RandomWeighted{_A, var"#s185", _B, _C, _D, _E, _F, Bool} where {_A, var"#s185"<:AbstractRNG, _B, _C, _D, _E, _F}

Return opt unchanged.

Default pass-through factory for optimisation estimators and results. Overridden for estimators that carry parameters requiring update at each optimisation step.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    mr::MeanRisk,
    i,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    args...
) -> MeanRisk{JuMPOptimiser{__T_pe, __T_slv, __T_wb, __T_bgt, __T_sbgt, __T_gbgt, __T_xbgt, __T_lt, __T_st, __T_lcse, __T_cte, __T_gcarde, __T_sgcarde, __T_smtx, __T_sgmtx, __T_slt, __T_sst, __T_sglt, __T_sgst, __T_tn, __T_fees, __T_sets, __T_tr, __T_ple, __T_ret, __T_sca, __T_ccnt, __T_cobj, __T_sc, __T_so, __T_ss, __T_card, __T_scard, __T_l2c, __T_lpc, __T_linfc, __T_l1, __T_l2, __T_lp, __T_linf, __T_brt, __T_x_src, __T_strict, __T_cache}} where {__T_pe, __T_slv, __T_wb, __T_bgt, __T_sbgt, __T_gbgt, __T_xbgt, __T_lt, __T_st, __T_lcse, __T_cte, __T_gcarde, __T_sgcarde, __T_smtx, __T_sgmtx, __T_slt, __T_sst, __T_sglt, __T_sgst, __T_tn, __T_fees, __T_sets, __T_tr, __T_ple, __T_ret, __T_sca, __T_ccnt, __T_cobj, __T_sc, __T_so, __T_ss, __T_card, __T_scard, __T_l2c, __T_lpc, __T_linfc, __T_l1, __T_l2, __T_lp, __T_linf, __T_brt, __T_x_src, __T_strict, __T_cache}

Return a cluster-sliced copy of MeanRisk for asset index set i and returns matrix X.

source
PortfolioOptimisers.optimiseMethod
optimise(mr::MeanRisk{<:Any, <:Any, <:Any, <:Any, Nothing},
         rd::ReturnsResult; dims::Int = 1,
         str_names::Bool = false, save::Bool = true, kwargs...) -> MeanRiskResult

Run the Mean-Risk portfolio optimisation.

Arguments

  • mr: The mean risk optimiser to use.
  • rd: The returns result to use. If isa(mr.opt.pe, AbstractPriorResult), rd is not necessary if doing a standalone optimisation, but may be required/desired by fallbacks and/or clusterisation.
  • dims: The dimension along which observations advance in time.
  • str_names: Whether to use string names for the assets in the optimisation.
  • save: Whether to save the JuMP model in the optimisation result.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Validation

  • No field in the tree of mr holds an Online. An ArgumentError naming the field is thrown otherwise, through assert_batch_entry: a plain optimise is a batch fit, and a wrapper resolves only at the warm-up of the fold loop's online arm.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[11]
H. Markowitz. Modern portfolio theory. Journal of Finance 7, 77–91 (1952).