Normal Uncertainty Sets: private API
PortfolioOptimisers.commutation_matrix — Function
commutation_matrix(X::MatNum)Constructs the commutation matrix for a given matrix.
The commutation matrix is the permutation that carries the column-major vectorisation of a matrix into the vectorisation of its transpose. It is its own inverse, and it holds one entry per row, so it is built as a sparse matrix. Only the shape of X is read, never its entries.
Mathematical definition
\[\mathbf{K}_{m,n} \operatorname{vec}(\mathbf{A}) = \operatorname{vec}\!\left(\mathbf{A}^{\intercal}\right)\,, \qquad \mathbf{A} \in \mathbb{R}^{m \times n}\,.\]
Where:
- $\mathbf{K}_{m,n}$: Commutation matrix of an $m \times n$ matrix, of size $mn \times mn$.
- $\mathbf{A}$: Any $m \times n$ matrix.
- $\operatorname{vec}$: Column-major vectorisation.
A symmetric $\mathbf{A}$ satisfies $\mathbf{K}_{n,n} \operatorname{vec}(\mathbf{A}) = \operatorname{vec}(\mathbf{A})$, so it cannot separate $\mathbf{K}$ from the identity.
Algorithm
- Read
m, n = size(X)and formmn = m * n, the side of the result. - Number the entries of an
mbynmatrix in column-major order, givingrow = 1:mn. - Send each number through the transpose with
col = vec(transpose(reshape(row, m, n))), the position that entry takes in the vectorised transpose. - Write a one at every
(row, col)pair, giving the sparsemnbymnmatrixcX.
Arguments
X: Input matrix. Used to determine the dimensions of the commutation matrix.
Returns
cX::SparseArrays.SparseMatrixCSC{<:Number, <:Integer}: Returns a sparse commutation matrix of size(m*n, m*n)wherem, n = size(X).
Examples
julia> PortfolioOptimisers.commutation_matrix(rand(2, 2))4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 4 stored entries: 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ 1.0julia> PortfolioOptimisers.commutation_matrix(rand(2, 3))6×6 SparseArrays.SparseMatrixCSC{Float64, Int64} with 6 stored entries: 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0julia> PortfolioOptimisers.commutation_matrix(rand(3, 2))6×6 SparseArrays.SparseMatrixCSC{Float64, Int64} with 6 stored entries: 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0Related
References
PortfolioOptimisers.choose_scaling_parameter — Method
choose_scaling_parameter(ue, pr)Return $T$, the effective number of observations that divides the covariance to give the asymptotic covariance of the mean.
Three sources are read in order, and the first that is not nothing wins: the estimator's own ens, the prior's ens, and the row count of the prior's returns matrix. The first two exist because a weighted or a shrunk prior carries fewer effective observations than it has rows, and because a prior under a Scenario Cap carries fewer rows than the observations its moments were fitted over, which it states in ens. Every set the file builds is scaled by $T$, and the width of a set scales as $T^{-1/2}$, so a prior that reports fewer effective scenarios than it has rows widens the set. Quartering $T$ doubles the width of the mean box.
Algorithm
- Return
ue.enswhen it is notnothing, the effective count the estimator states. - Otherwise return
pr.enswhen it is notnothing, the effective count the prior carries. - Otherwise return
size(pr.X, 1), the row count of the prior's returns matrix.
Arguments
ue:NormalUncertaintySetestimator.pr: Prior result.
Returns
T::Number: Effective number of observations.
Related
PortfolioOptimisers.normal_mu_error_sample — Function
normal_mu_error_sample(ue::NormalUncertaintySet, rng::Random.AbstractRNG, mu::VecNum,
sigma_mu::MatNum)Draw ue.n_sim estimation errors of the mean from the sampling law that normal returns imply.
The sample is the estimation error, not the level. The draws come from $\mathcal{N}(\hat{\boldsymbol{\mu}}, \hat{\mathbf{\Sigma}}/T)$ and mu is subtracted from each, so the sample and the shape it is measured against are on one scale. A draw from $\mathcal{N}(\hat{\boldsymbol{\mu}}, \hat{\mathbf{\Sigma}})$ in its place multiplies every deviation, and therefore the radius, by $\sqrt{T}$. The generator is passed in rather than resolved here, so a caller that needs both axes draws them off one stream.
Algorithm
- Draw
ue.n_simvectors fromDistributions.MvNormal(mu, sigma_mu), giving one column per simulation. - Subtract
mufrom every column, and transpose, giving one estimation error per row.
Arguments
ue: Normal uncertainty set estimator.rng: Random number generator, resolved by the caller withresolve_rng.mu: Point estimate of the expected returns vector, the centre the draws are taken around.sigma_mu: Mean asymptotic covariance frommu_asymptotic_cov.
Returns
X_mu::MatNum: Sampled estimation errors,ue.n_simbyN.
Related
PortfolioOptimisers.normal_sigma_error_sample — Function
normal_sigma_error_sample(ue::NormalUncertaintySet, rng::Random.AbstractRNG,
sigma::MatNum, sigma_mu::MatNum, T::Number, N::Integer)Draw ue.n_sim estimation errors of the vectorised covariance from the Wishart law that normal returns imply.
The sample is the estimation error, not the level. Each draw has sigma subtracted from it, and the variance of an entry of a $\mathrm{Wishart}(T, \hat{\mathbf{\Sigma}}/T)$ draw is the matching diagonal entry of the covariance asymptotic covariance, so the sample and the shape it is measured against are on one scale. N is passed in rather than read from sigma, so every caller reads the asset count from one source, size(pr.X, 2).
Algorithm
- Draw
ue.n_simmatrices fromDistributions.Wishart(T, sigma_mu), givingsigmas. - Subtract
sigmafrom each draw into anNbyNbyue.n_simarray. - Reshape to
N^2columns and transpose, giving one vectorised estimation error per row.
Arguments
ue: Normal uncertainty set estimator.rng: Random number generator, resolved by the caller withresolve_rng.sigma: Point estimate of the covariance matrix, the centre the draws are taken around.sigma_mu: Mean asymptotic covariance frommu_asymptotic_cov, the Wishart scale.T: Scaling parameter fromchoose_scaling_parameter, the Wishart degrees of freedom.N: Asset count, read by the caller fromsize(pr.X, 2).
Returns
X_sigma::MatNum: Sampled estimation errors,ue.n_simbyN^2.
Related
PortfolioOptimisers.mu_asymptotic_cov — Function
mu_asymptotic_cov(pdm, sigma, T)Asymptotic covariance of the mean estimator, projected to the nearest positive definite matrix with pdm.
Shared by the box and the ellipsoidal NormalUncertaintySet constructions, so the two routes cannot drift apart. The division is by a positive scalar, so it preserves definiteness: posdef! moves nothing when sigma is already positive definite.
Mathematical definition
\[\mathbf{\Sigma}_{\boldsymbol{\mu}} = \dfrac{\hat{\mathbf{\Sigma}}}{T}\,.\]
Where:
- $\mathbf{\Sigma}_{\boldsymbol{\mu}}$: Asymptotic covariance of the mean estimator.
- $\hat{\mathbf{\Sigma}}$: Estimated covariance matrix.
- $T$: Number of observations.
Algorithm
- Divide
sigmabyT, givingsigma_mu. - Repair
sigma_muin place withposdef!underpdm. - Return
sigma_mu.
Arguments
pdm: Positive definite matrix estimator.nothingskips the repair.sigma: Point estimate of the covariance matrix.T: Scaling parameter fromchoose_scaling_parameter.
Returns
sigma_mu::MatNum: Asymptotic covariance of the mean estimator.
Related
References
PortfolioOptimisers.sigma_asymptotic_cov — Function
sigma_asymptotic_cov(pdm, sigma_mu, sigma, T)Asymptotic covariance of the vectorised covariance estimator, projected to the nearest positive definite matrix with pdm.
sigma_mu is the raw, pre-diagonalisation mean asymptotic covariance from mu_asymptotic_cov, and $\mathbf{K}$ is the commutation_matrix of sigma. The two matrix arguments are not independent: the form below reduces to the sampling law of a normal sample only when sigma_mu was built from the same sigma and the same T.
Mathematical definition
\[\mathbf{\Sigma}_{\mathbf{\Sigma}} = T \left(\mathbf{I} + \mathbf{K}\right) \left(\mathbf{\Sigma}_{\boldsymbol{\mu}} \otimes \mathbf{\Sigma}_{\boldsymbol{\mu}}\right) = \dfrac{\left(\mathbf{I} + \mathbf{K}\right) \left(\hat{\mathbf{\Sigma}} \otimes \hat{\mathbf{\Sigma}}\right)}{T}\,.\]
Where:
- $\mathbf{\Sigma}_{\mathbf{\Sigma}}$: Asymptotic covariance of the vectorised covariance estimator.
- $\mathbf{\Sigma}_{\boldsymbol{\mu}}$: Asymptotic covariance of the mean estimator.
- $\mathbf{I}$: Identity matrix.
- $\mathbf{K}$: Commutation matrix.
- $\otimes$: Kronecker product.
- $\hat{\mathbf{\Sigma}}$: Estimated covariance matrix.
- $T$: Number of observations.
The right-hand form is the asymptotic covariance of $\operatorname{vec}(\hat{\mathbf{\Sigma}})$ for a normal sample. Its $(i,j)$ diagonal entry is $\left(\hat{\Sigma}_{ii} \hat{\Sigma}_{jj} + \hat{\Sigma}_{ij}^{2}\right) / T$, which is the variance of the $(i,j)$ entry of a $\mathrm{Wishart}(T, \hat{\mathbf{\Sigma}}/T)$ draw. The sample that ucs builds from those draws and the shape matrix it measures that sample against are therefore on one scale.
Algorithm
- Build
K = commutation_matrix(sigma).sigmais square, soKis $N^{2} \times N^{2}$. - Form
sigma_sigma = T * (LinearAlgebra.I + K) * kron(sigma_mu, sigma_mu). - Repair
sigma_sigmain place withposdef!underpdm. - Return
sigma_sigma.
Arguments
pdm: Positive definite matrix estimator.nothingskips the repair.sigma_mu: Mean asymptotic covariance frommu_asymptotic_cov.sigma: Point estimate of the covariance matrix. Only its shape is read, throughcommutation_matrix.T: Scaling parameter fromchoose_scaling_parameter.
Returns
sigma_sigma::MatNum: Asymptotic covariance of the vectorised covariance estimator, $N^{2} \times N^{2}$.
Related
References
PortfolioOptimisers.mu_normal_box_set — Function
mu_normal_box_set(mu::VecNum, sigma_mu::MatNum, q::Number)Box uncertainty set for expected returns under normality: a zero lower bound, and an upper bound that carries the whole width of the box.
q is the already-halved significance level. set_ucs_return_constraints! reads the pair only through its half-width $(\boldsymbol{\mu}_{u} - \boldsymbol{\mu}_{l})/2$, which is therefore $z_{q} \sqrt{\operatorname{diag}(\mathbf{\Sigma}_{\boldsymbol{\mu}})}$, and it centres that width on the vector the set carries. Neither bound is a bound on the mean on its own. Shared by the box ucs and mu_ucs constructions for NormalUncertaintySet.
Mathematical definition
\[\begin{align} \boldsymbol{\mu}_{l} &= \boldsymbol{0}\,, \\ \mu_{u,i} &= 2 z_{q} \sqrt{\left(\mathbf{\Sigma}_{\boldsymbol{\mu}}\right)_{ii}}\,, \qquad z_{q} = \Phi^{-1}(1 - q)\,. \end{align}\]
Where:
- $\boldsymbol{\mu}_{l}$, $\mu_{u,i}$: Lower bound, and $i$-th entry of the upper bound.
- $\mathbf{\Sigma}_{\boldsymbol{\mu}}$: Asymptotic covariance of the mean estimator.
- $z_{q}$: Normal complementary quantile at the halved significance level.
- $q$: Half significance level.
Each entry of the resulting interval covers $1 - 2q$ of the sampling law of that entry, because the two tails carry $q$ each. The interval is one entry at a time, so it is not a joint region for the whole vector.
Algorithm
- Take the $1 - q$ standard normal quantile with
Distributions.cquantile, giving the multiplier $z_{q}$. - Multiply it by the square roots of
LinearAlgebra.diag(sigma_mu)and by two, givingmu_u, the width of the box on each asset. - Build
mu_l, a vector of zeros of the same length and element type. - Return a
BoxUncertaintySetcarryingmu_l,mu_u, andmu, the vector the width is centred on.
Arguments
mu: Point estimate of the expected returns vector, carried by the set as its centre.sigma_mu: Mean asymptotic covariance frommu_asymptotic_cov.q: Half significance level, already halved bynormal_box_preamble.
Returns
mu_ucs::BoxUncertaintySet: Expected returns uncertainty set.
Related
NormalUncertaintySetmu_asymptotic_covnormal_box_preambleBoxUncertaintySetset_ucs_return_constraints!
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Equations 11.14 and 11.19.
- [87] F. J. Fabozzi, P. N. Kolm, D. A. Pachamanova and S. M. Focardi. Robust Portfolio Optimization and Management (John Wiley & Sons, Hoboken, NJ, 2007).
PortfolioOptimisers.sigma_normal_box_set — Function
sigma_normal_box_set(ue::NormalUncertaintySet, pr::LowOrderPrior, T::Number,
sigma_mu::MatNum, q::Number)Covariance box uncertainty set of a NormalUncertaintySet.
Draws ue.n_sim Wishart matrices with T degrees of freedom and scale sigma_mu, takes the element-wise q and 1 - q quantiles as the bounds, then projects each bound to the nearest positive definite matrix. This is the covariance half of ucs and the whole of sigma_ucs, so the simulation is written once and the two entry points cannot drift apart. The mean of a $\mathrm{Wishart}(T, \mathbf{\Sigma}_{\boldsymbol{\mu}})$ draw is $T \mathbf{\Sigma}_{\boldsymbol{\mu}}$, which is $\hat{\mathbf{\Sigma}}$, so the draws centre on the point estimate and the bounds bracket it.
Algorithm
- Read the point estimate
sigma = pr.sigma, the centre the bounds bracket. - Resolve the random number generator from
ue.rngandue.seedwithresolve_rng. - Draw
ue.n_simmatricessigmasfromDistributions.Wishart(T, sigma_mu). - Take the element-wise
qand1 - qquantiles ofsigmaswithbox_quantile_bounds, givingsigma_landsigma_u. - Repair
sigma_landsigma_uin place withposdef!. A quantile is taken entry by entry, so neither bound is a Wishart draw and neither is positive definite by construction. A bound that is already positive definite is not moved. - Return a
BoxUncertaintySetcarryingsigma_l,sigma_uandsigma.
Arguments
ue: Normal uncertainty set estimator.pr: Prior result carrying the point estimatepr.sigma.T: Scaling parameter fromchoose_scaling_parameter, used as the Wishart degrees of freedom.sigma_mu: Mean asymptotic covariance frommu_asymptotic_cov, used as the Wishart scale.q: Half significance level.
Returns
sigma_ucs::BoxUncertaintySet: Covariance uncertainty set.
Related
PortfolioOptimisers.normal_box_preamble — Function
normal_box_preamble(ue::NormalUncertaintySet, pr::AbstractPriorResult)Shared preamble of the three box entry points of a NormalUncertaintySet.
Resolves the scaling parameter and derives the two quantities both box sets are built from, off the prior result the set is calibrated on. ucs, mu_ucs and sigma_ucs all start here, so the scaling choice is written once instead of three times. The returned q is halved, and the ellipsoidal route halves nothing. A box bounds each entry on both sides, so half of the significance level goes into each tail. An ellipsoid cuts only the upper tail of a distance that cannot be negative, so one cut at the $1 - q$ quantile already covers $1 - q$.
Algorithm
- Resolve the scaling parameter with
choose_scaling_parameter, givingT. - Build the mean asymptotic covariance with
mu_asymptotic_cov, givingsigma_mu. - Halve
ue.q, givingq, the significance level of one tail. - Return
T,sigma_muandqas a tuple.
Arguments
ue: Normal uncertainty set estimator.pr: Fitted prior result the set is calibrated on.
Returns
(T, sigma_mu, q): Scaling parameter, mean asymptotic covariance, and half significance level.
Related
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
- [25]
- A. Meucci. Risk and Asset Allocation (Springer Berlin Heidelberg, 2005).
- [87]
- F. J. Fabozzi, P. N. Kolm, D. A. Pachamanova and S. M. Focardi. Robust Portfolio Optimization and Management (John Wiley & Sons, Hoboken, NJ, 2007).