Expected Risk
PortfolioOptimisers.expected_risk — Function
expected_risk(r, args...; kwargs...)Compute the expected value of a risk measure.
Generic function extended by concrete risk measure types. Each method computes the risk value associated with its risk measure type, given a portfolio (or its return distribution).
Related
PortfolioOptimisers.expected_risk_from_returns — Function
expected_risk_from_returns(r, X; kwargs...)Compute the expected risk of a measure from a precomputed net-return series.
Generic function extended by concrete risk measure types that support the precomputed-returns contract. Only measures with supports_precomputed_returns(r) == true should implement this method.
Related
PortfolioOptimisers.expected_risk_from_returns — Method
expected_risk_from_returns(r::AbstractBaseRiskMeasure, X::VecNum; kwargs...) -> NumberContract entry for evaluating a risk measure on an already-reduced net-return series X. Consults supports_precomputed_returns: for an eligible measure it returns r(X); for an ineligible one it throws an explanatory ArgumentError instead of silently consuming X as weights (a WeightsInput measure) or hitting an opaque MethodError (a moment measure with a per-asset mu).
Internal call sites that hold a precomputed series — cross-validation prediction scoring — route through here rather than calling the functor directly.
The Precomputed-returns contract: the series X must be finite. This entry takes no finiteness check, because every internal caller hands it a finite series: the three ratio kernels call it for each half at every evaluation, and a scan on a long series would be paid by all of them. A tail measure on a gapped series answers a finite wrong number rather than a NaN: partialsort orders a NaN after every real, so a CVaR reads its order statistic off the finite prefix and divides by the poisoned length. A caller who holds a gapped series drops the gaps first with x[isfinite.(x)]. A caller who holds a gapped panel scores it through predict(res::NonFiniteAllocationOptimisationResult, rd::ReturnsResult) instead, which filters the Held Gaps once.
Related
PortfolioOptimisers.expected_risk_from_returns — Method
expected_risk_from_returns(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
X::AbstractVector{<:AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}};
kwargs...
) -> Any
Evaluate a risk measure on each element of a vector of precomputed return series.
Maps expected_risk_from_returns over each Xi in X.
Related
PortfolioOptimisers.number_effective_assets — Function
number_effective_assets(w::VecNum)Compute the effective number of assets (Herfindahl-Hirschman inverse index).
Mathematical definition
\[\begin{align} N_{\mathrm{eff}} &= \frac{1}{\sum_i w_i^2}\,. \end{align}\]
Where:
- $N_{\mathrm{eff}}$: Effective number of assets.
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
Returns the number of equally-weighted assets that would produce the same level of concentration as the given weight vector w.
Arguments
w::VecNum: Portfolio weight vector.
Returns
Number: Effective number of assets.
Related
PortfolioOptimisers.risk_contribution — Function
risk_contribution(
r::BaseRM_VecBaseRM,
w::VecNum,
X::MatNum_Pr,
fees::Option{<:Fees} = nothing;
delta::Number = 1e-6,
marginal::Bool = false,
sca::Scalariser = SumScalariser(),
strict::Bool = false,
kwargs...
) -> VectorCompute the risk contribution of each asset to the total portfolio risk using numerical differentiation.
Mathematical definition
The risk contribution of asset $i$ is defined as:
\[\begin{align} \mathrm{RC}_i &= w_i \cdot \frac{\partial \rho(\boldsymbol{w})}{\partial w_i}\,. \end{align}\]
Where:
- $\mathrm{RC}_i$: Risk contribution of asset $i$.
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $\rho$: Portfolio risk measure.
- $w_i$: Weight of asset $i$.
The partial derivative is approximated using a two-sided finite difference with step size delta. When marginal = true, the function omits the weighting by $w_i$ (i.e., only the marginal risk $\partial \rho / \partial w_i$ is returned).
Arguments
r::BaseRM_VecBaseRM: Risk measure to differentiate, or a vector of them.w::VecNum: Portfolio weights vector.X::MatNum_Pr: Asset returns matrix or prior result.fees::Option{<:Fees}: Optional fee structure.
Keyword Arguments
delta::Number = 1e-6: Finite difference step size.marginal::Bool = false: Iftrue, returns marginal risk contributions (without $w_i$ weighting).sca::Scalariser = SumScalariser(): Scalariser combining a vectorr. Inert on a single measure.strict::Bool = false: Whether a held non-investable asset raises rather than warns. Read only whenXis a prior result.
Returns
Vector: Risk contributions (or marginal risks) for each asset, one entry per asset of the full universe.
Details
- A prior result reduces to the Investable Mask once, before the loop (
investable_reduction), so no perturbed weight ever meets aNaNmoment. The answer expands back into a zero vector of the full length, so a non-investable asset reports exactly0. - A prior result resolves the measure once, before the loop (
resolve_risk_inputs), so a Deferred Quantity is fitted once rather than once per finite difference. - A vector of measures differentiates the aggregate, which is the figure
expected_riskreports. The homogeneity correction is applied per element inside the loop (adjusted_risk), soΣᵢ wᵢ·rcᵢrecovers the aggregate exactly even when the elements have different homogeneity degrees. - Under a Weight Drift the figures are exact to first order in the drift only. The function differentiates one weight vector, and a drifted fold's return series is not linear in that vector, so the contributions sum to the fold's realised risk approximately rather than exactly. The target weights are what the figures are reported against, because they are the decision the finite difference perturbs — a weight path holds no single vector for the difference to move.
All four scalarisers are admitted, and MaxScalariser and MinScalariser are exact almost everywhere. Wherever the argmax is unique the aggregate's decomposition is the winning element's own, scaled. The figure degrades only at a near-exact tie between two scaled measures, where the argmax can flip between the w+δ and w−δ points and an asset's figure becomes a chord across the kink. At such a point the subgradient is genuinely a set, so no answer is uniquely correct.
Related
risk_contribution(r::BaseRM_VecBaseRM, pred::PredictionResult{<:Any, <:Any, <:HeldWeightsResult}, fees = nothing; kwargs...)
risk_contribution(r::BaseRM_VecBaseRM, pred::PredictionResult{<:Any, <:Any, Nothing}, args...; kwargs...)Decompose a fold's risk over its assets.
The fold-taking method of risk_contribution. It resolves the fold's target weights, its asset returns and its fee, and hands them to the free function, so the figures are the free function's own.
The three do not sit on one universe. The Held Weights record came back on the caller's universe through expand_held_weights, and so did the target weights, while the result's fee stayed on the universe it solved on. The method therefore views the weights and the asset returns at the result's Investable Mask before the finite difference, exactly as investable_reduction does at the value-level door, and expands the per asset answer back with a zero at every non-investable asset. A result whose mask is nothing views nothing.
Under a Weight Drift the figures are exact to first order in the drift only, for the reason the free function states: the drifted series is not linear in the target weights, so the contributions sum to the fold's realised risk approximately rather than exactly. The target weights are still what a contribution is reported against, because they are the decision the finite difference perturbs.
A fold that carries no Held Weights record raises, because pred.rd.X is the portfolio series and no asset matrix survives on the fold.
Arguments
r::BaseRM_VecBaseRM: Risk measure to differentiate, or a vector of them.pred: Single-fold prediction result.fees: A caller'sFeeson the caller's universe, which takes precedence over the result's own and is viewed at the result's Investable Mask throughfold_fees.args...: Additional arguments (ignored by the refusing method).
Validation
- The fold carries a
HeldWeightsResult, else anArgumentErrornaming the two switches.
Returns
Vector: Risk contributions (or marginal risks) for each asset of the caller's universe, exactly0at a non-investable one.
Related
PortfolioOptimisers.factor_risk_contribution — Function
factor_risk_contribution(
r::BaseRM_VecBaseRM,
w::VecNum,
X::MatNum_Pr,
fees::Option{<:Fees} = nothing;
re::RegE_Reg = StepwiseRegression(),
rd::ReturnsResult = ReturnsResult(),
delta::Number = 1e-6,
strict::Bool = false,
kwargs...
) -> VectorCompute the risk contribution of each factor (and the idiosyncratic component) to the total portfolio risk using a factor regression.
A prior result reduces to the Investable Mask once at the entry, through investable_reduction, and rd is reduced with it, so the loadings are fitted over the live assets alone. A held non-investable asset warns and its weight is dropped, or raises under strict. The answer is one figure per factor rather than one per asset, so nothing expands.
Mathematical definition
The factor risk contributions partition total portfolio risk into factor-specific components using the Brinson attribution framework:
\[\begin{align} \mathrm{FRC}_k &= (\mathbf{B}^\intercal \boldsymbol{w})_k \cdot (\mathbf{B}^{-\intercal} \nabla \rho)_k\,. \end{align}\]
Where:
- $\mathrm{FRC}_k$: Risk contribution of factor $k$.
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $\mathbf{B}$: Factor loading matrix $N \times K$, estimated by regression.
- $\nabla \rho$: Gradient of the risk measure with respect to the weights.
Arguments
r::BaseRM_VecBaseRM: Risk measure to decompose, or a vector of them.w::VecNum: Portfolio weights vector.X::MatNum_Pr: Asset returns matrix or prior result.fees::Option{<:Fees}: Optional fee structure.
Keyword Arguments
re::RegE_Reg = StepwiseRegression(): Regression estimator for factor loadings.rd::ReturnsResult = ReturnsResult(): Returns result providing factor data.delta::Number = 1e-6: Finite difference step size.
Returns
Vector: Risk contributions for each factor, with the last element being the idiosyncratic (off-factor) contribution.
Details
- The gradient is taken on
original_returns— the returns the caller supplied — and not onpr.X(resolve_factor_risk_inputs). The two differ under a factor prior, wherepr.Xis the reconstructionF * transpose(M) .+ transpose(b). That matrix has ranksize(F, 2)and carries no residual, so the off-factor term of a series-reducing measure reports the intercept's share rather than idiosyncratic risk, and can come out negative. - A consequence: under a factor prior the parts sum to the risk on the caller's returns, and not to
expected_risk(r, w, pr). A measure whose kernel reads a moment rather than the series —Variance,StandardDeviation,DistributionValueatRisk— is unaffected either way, because it never reduces the returns matrix. - The loadings come from
resolve_factor_regression, which prefers the prior's ownrrover a refit, so the loadings and the returns are the pair the prior was fitted on. A stated regression estimator therefore loses to a prior that carries a factor block. - A prior result resolves the measure once, before the loop (
resolve_factor_risk_inputs), so a Deferred Quantity is fitted once rather than once per finite difference. - Under a Weight Drift the figures are exact to first order in the drift only. The function differentiates one weight vector, and a drifted fold's return series is not linear in that vector, so the contributions sum to the fold's realised risk approximately rather than exactly. The target weights are what the figures are reported against, because they are the decision the finite difference perturbs — a weight path holds no single vector for the difference to move.
Related
factor_risk_contribution(r::BaseRM_VecBaseRM, pred::PredictionResult{<:Any, <:Any, <:HeldWeightsResult}, fees = nothing; rd, kwargs...)
factor_risk_contribution(r::BaseRM_VecBaseRM, pred::PredictionResult{<:Any, <:Any, Nothing}, args...; kwargs...)Decompose a fold's risk over its factors.
The fold-taking method of factor_risk_contribution, and the twin of the risk_contribution method above. It resolves the fold's target weights, its asset returns and its fee the same way, views the weights and the returns at the result's Investable Mask the same way, and it builds the rd the loadings are fitted from out of the fold itself: the fold's asset returns beside the factor block reconstruct_rd carried through. A caller who wants other loadings passes its own rd, or a precomputed Regression as re.
A caller's rd is stated on the caller's universe, and fold_factor_returns views it at the mask beside the weights and the returns, so the regression is fitted over the live assets alone. With no rd the same verb builds one from the fold, already on the live assets, because the fold's own nx is the reduced axis.
The first-order caveat of the risk_contribution method above holds here unchanged, and a fold that carries no Held Weights record raises for the same reason.
Arguments
r::BaseRM_VecBaseRM: Risk measure to decompose, or a vector of them.pred: Single-fold prediction result.fees: A caller'sFeeson the caller's universe, which takes precedence over the result's own and is viewed at the result's Investable Mask throughfold_fees.args...: Additional arguments (ignored by the refusing method).
Keyword Arguments
rd::Option{<:ReturnsResult} = nothing: Returns result the loadings are fitted from, on the caller's universe, ornothingfor the fold's own asset returns and factor block.
Validation
- The fold carries a
HeldWeightsResult, else anArgumentErrornaming the two switches.
Returns
Vector: Risk contributions for each factor, with the last element being the idiosyncratic (off-factor) contribution.
Related
PortfolioOptimisers.rolling_window_measure — Function
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
w::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
fees::Union{Nothing, Fees},
window::Integer;
sca,
kwargs...
) -> Any
Compute the expected risk of a risk measure over rolling windows of the returns data.
This is the constant-weight reading: the one vector w is re-scored on every window, so each number is a property of that weight vector rather than of a history. The realised-history reading is the (r, ret::VecNum, window) method below, which rolls an already-formed net return series instead. The two answer different questions, so they are two methods rather than two settings of one.
The (r, w::MatNum, X, fees, window) method below is the constant-weight reading of a weight path: it re-scores the window against the weights held at the window's ending row rather than against one vector for the whole sample. The weight argument's type is the picker, so a vector reads one target and a matrix reads a path.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.w::VecNum: Portfolio weights vector.X::MatNum: Asset returns matrix.fees::Option{<:Fees}: Optional fee structure.window::Integer: Size of the rolling window (number of periods).
Keyword Arguments
sca::Scalariser = SumScalariser(): Scalariser combining a vectorr. Inert on a single measure.
Validation
1 <= window <= size(X, 1), else aDomainErrornamingwindow.
The window is checked here rather than left to the risk kernel. A non-positive window indexes X out of bounds and surfaces as a bare BoundsError from inside whichever measure r names, and a window longer than the sample produces an empty vector of risks that reads as a legitimate result. Both are caller errors, so both are refused at the boundary — the same discipline plot_rolling_measure applies to its own rolling keyword.
Returns
risks::VecNum: Expected risk values for each rolling window.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
w::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
fees::Union{Nothing, Fees},
window::Integer;
sca,
kwargs...
) -> Any
Compute the expected risk of a risk measure over rolling windows, against a weight path.
The ending-weights reading. w is a T × N weight path, and the window closing at row t is scored against row t of the path — the weights the portfolio held by the time that window closed. That is the same reading of ending weights the rest of the library takes, so a window's number is a property of the weights a fund carried into the window's last observation.
It is one snapshot per window and not an exact decomposition of the window's realised return: the weights moved inside the window, and this method scores the window as though they had not. A caller who wants the window's own history is served by the (r, ret::VecNum, window) method, which rolls the drifted series itself.
At constant weights every row of the path is the same vector, so this method reproduces the (r, w::VecNum, X, fees, window) method above.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.w::MatNum: Weight path (observations × assets), asweight_pathmakes one.X::MatNum: Asset returns matrix.fees::Option{<:Fees}: Optional fee structure.window::Integer: Size of the rolling window (number of periods).
Keyword Arguments
sca::Scalariser = SumScalariser(): Scalariser combining a vectorr. Inert on a single measure.
Validation
1 <= window <= size(X, 1), else aDomainErrornamingwindow.size(w, 1) == size(X, 1), else aDimensionMismatchnaming both. A path shorter than the sample indexes out of bounds inside whichever measurernames, which is the caller error the window check already refuses at the boundary.
Returns
risks::VecNum: Expected risk values for each rolling window.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
ret::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
window::Integer;
sca,
kwargs...
) -> Any
Compute the expected risk of a risk measure over rolling windows of an already-formed net return series.
This is the realised-history reading: a window is a sub-series of whatever formed ret, so under a weight drift the drift does not restart at a window's first row — the weights that a row reads are the ones held on the way to that row. It takes no fees argument, because the series is net already. The constant-weight reading is the (r, w::VecNum, X, fees, window) method above.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.ret::VecNum: Net portfolio return series.window::Integer: Size of the rolling window (number of periods).
Keyword Arguments
sca::Scalariser = SumScalariser(): Scalariser combining a vectorr. Inert on a single measure.
Validation
1 <= window <= length(ret), else aDomainErrornamingwindow.- Each window is scored through
expected_risk_from_returns, so a measure whosesupports_precomputed_returnsisfalseraises that entry's own namedArgumentError.
The window is checked here for the reason the constant-weight method states, and the refusal of a weights-consuming measure is the existing one rather than a new error type.
Returns
risks::VecNum: Expected risk values for each rolling window.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
ret::AbstractVector{<:AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
window::Integer;
kwargs...
) -> Any
Roll the realised-history measure over each series of a population of net return series.
The twin of the singular series method, and it mirrors expected_risk_from_returns on a VecVecNum: a fold whose optimisation result carries a population of weight vectors forms one series per member, and each member is rolled on its own.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.ret::VecVecNum: Net portfolio return series, one per population member.window::Integer: Size of the rolling window (number of periods).
Returns
risks::Vector{<:VecNum}: Rolling risk values, one vector per population member.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
pred::PredictionResult,
window::Integer;
kwargs...
) -> Any
Roll a risk measure over the return series a single fold formed.
The realised-history reading of rolling_window_measure on a fold: pred.rd.X is the series predict stored, so no weights are read and none are needed. Under a weight drift that series is the drifted one, and a window of it is a sub-series of the fold's own drift.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.pred::PredictionResult: Single-fold prediction result.window::Integer: Size of the rolling window (number of periods).
Returns
risks::VecNum: Expected risk values for each rolling window.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
mpred::MultiPeriodPredictionResult,
window::Integer;
kwargs...
) -> Any
Roll a risk measure over the return series a whole path formed.
mpred.mrd.X concatenates the folds of the path into one series, so a window can straddle a rebalance and read observations from two folds. That is the realised history: the fund held one set of weights before the rebalance and another after it, and the window sees both.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.mpred::MultiPeriodPredictionResult: Multi-period prediction result.window::Integer: Size of the rolling window (number of periods).
Returns
risks::VecNum: Expected risk values for each rolling window.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
preds::AbstractVector{<:MultiPeriodPredictionResult},
window::Integer;
kwargs...
) -> Any
Roll a risk measure over each path of a vector of multi-period prediction results.
Maps the multi-period method over preds, so each path is rolled on its own series. The paths of a combinatorial scheme cover the same calendar, so their windows are comparable across the vector.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.preds::VecMPredRes: Vector of multi-period prediction results.window::Integer: Size of the rolling window (number of periods).
Returns
risks::Vector{<:VecNum}: Rolling risk values, one vector per path.
Related
rolling_window_measure(
r::Union{AbstractBaseRiskMeasure, AbstractVector{<:AbstractBaseRiskMeasure}},
ppred::PopulationPredictionResult,
window::Integer;
kwargs...
) -> Any
Roll a risk measure over every path of a population prediction result.
Delegates to the vector method on ppred.pred, which is the route expected_risk takes on the same type.
Arguments
r::BaseRM_VecBaseRM: Risk measure to evaluate, or a vector of them.ppred::PopulationPredictionResult: Population prediction result.window::Integer: Size of the rolling window (number of periods).
Returns
risks::Vector{<:VecNum}: Rolling risk values, one vector per path.
Related