Normal Uncertainty Sets: private API

PortfolioOptimisers.commutation_matrixFunction
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

  1. Read m, n = size(X) and form mn = m * n, the side of the result.
  2. Number the entries of an m by n matrix in column-major order, giving row = 1:mn.
  3. Send each number through the transpose with col = vec(transpose(reshape(row, m, n))), the position that entry takes in the vectorised transpose.
  4. Write a one at every (row, col) pair, giving the sparse mn by mn matrix cX.

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) where m, 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.0

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Equation 11.17.
  • [25] A. Meucci. Risk and Asset Allocation (Springer Berlin Heidelberg, 2005).
source
PortfolioOptimisers.choose_scaling_parameterMethod
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

  1. Return ue.ens when it is not nothing, the effective count the estimator states.
  2. Otherwise return pr.ens when it is not nothing, the effective count the prior carries.
  3. Otherwise return size(pr.X, 1), the row count of the prior's returns matrix.

Arguments

Returns

  • T::Number: Effective number of observations.

Related

source
PortfolioOptimisers.normal_mu_error_sampleFunction
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

  1. Draw ue.n_sim vectors from Distributions.MvNormal(mu, sigma_mu), giving one column per simulation.
  2. Subtract mu from every column, and transpose, giving one estimation error per row.

Arguments

  • ue: Normal uncertainty set estimator.
  • rng: Random number generator, resolved by the caller with resolve_rng.
  • mu: Point estimate of the expected returns vector, the centre the draws are taken around.
  • sigma_mu: Mean asymptotic covariance from mu_asymptotic_cov.

Returns

  • X_mu::MatNum: Sampled estimation errors, ue.n_sim by N.

Related

source
PortfolioOptimisers.normal_sigma_error_sampleFunction
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

  1. Draw ue.n_sim matrices from Distributions.Wishart(T, sigma_mu), giving sigmas.
  2. Subtract sigma from each draw into an N by N by ue.n_sim array.
  3. Reshape to N^2 columns and transpose, giving one vectorised estimation error per row.

Arguments

  • ue: Normal uncertainty set estimator.
  • rng: Random number generator, resolved by the caller with resolve_rng.
  • sigma: Point estimate of the covariance matrix, the centre the draws are taken around.
  • sigma_mu: Mean asymptotic covariance from mu_asymptotic_cov, the Wishart scale.
  • T: Scaling parameter from choose_scaling_parameter, the Wishart degrees of freedom.
  • N: Asset count, read by the caller from size(pr.X, 2).

Returns

  • X_sigma::MatNum: Sampled estimation errors, ue.n_sim by N^2.

Related

source
PortfolioOptimisers.mu_asymptotic_covFunction
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

  1. Divide sigma by T, giving sigma_mu.
  2. Repair sigma_mu in place with posdef! under pdm.
  3. Return sigma_mu.

Arguments

  • pdm: Positive definite matrix estimator. nothing skips the repair.
  • sigma: Point estimate of the covariance matrix.
  • T: Scaling parameter from choose_scaling_parameter.

Returns

  • sigma_mu::MatNum: Asymptotic covariance of the mean estimator.

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Equation 11.16.
  • [25] A. Meucci. Risk and Asset Allocation (Springer Berlin Heidelberg, 2005).
source
PortfolioOptimisers.sigma_asymptotic_covFunction
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

  1. Build K = commutation_matrix(sigma). sigma is square, so K is $N^{2} \times N^{2}$.
  2. Form sigma_sigma = T * (LinearAlgebra.I + K) * kron(sigma_mu, sigma_mu).
  3. Repair sigma_sigma in place with posdef! under pdm.
  4. Return sigma_sigma.

Arguments

Returns

  • sigma_sigma::MatNum: Asymptotic covariance of the vectorised covariance estimator, $N^{2} \times N^{2}$.

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Equations 11.17 and 11.24.
  • [25] A. Meucci. Risk and Asset Allocation (Springer Berlin Heidelberg, 2005).
source
PortfolioOptimisers.mu_normal_box_setFunction
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

  1. Take the $1 - q$ standard normal quantile with Distributions.cquantile, giving the multiplier $z_{q}$.
  2. Multiply it by the square roots of LinearAlgebra.diag(sigma_mu) and by two, giving mu_u, the width of the box on each asset.
  3. Build mu_l, a vector of zeros of the same length and element type.
  4. Return a BoxUncertaintySet carrying mu_l, mu_u, and mu, 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 from mu_asymptotic_cov.
  • q: Half significance level, already halved by normal_box_preamble.

Returns

  • mu_ucs::BoxUncertaintySet: Expected returns uncertainty set.

Related

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).
source
PortfolioOptimisers.sigma_normal_box_setFunction
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

  1. Read the point estimate sigma = pr.sigma, the centre the bounds bracket.
  2. Resolve the random number generator from ue.rng and ue.seed with resolve_rng.
  3. Draw ue.n_sim matrices sigmas from Distributions.Wishart(T, sigma_mu).
  4. Take the element-wise q and 1 - q quantiles of sigmas with box_quantile_bounds, giving sigma_l and sigma_u.
  5. Repair sigma_l and sigma_u in place with posdef!. 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.
  6. Return a BoxUncertaintySet carrying sigma_l, sigma_u and sigma.

Arguments

  • ue: Normal uncertainty set estimator.
  • pr: Prior result carrying the point estimate pr.sigma.
  • T: Scaling parameter from choose_scaling_parameter, used as the Wishart degrees of freedom.
  • sigma_mu: Mean asymptotic covariance from mu_asymptotic_cov, used as the Wishart scale.
  • q: Half significance level.

Returns

  • sigma_ucs::BoxUncertaintySet: Covariance uncertainty set.

Related

source
PortfolioOptimisers.normal_box_preambleFunction
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

  1. Resolve the scaling parameter with choose_scaling_parameter, giving T.
  2. Build the mean asymptotic covariance with mu_asymptotic_cov, giving sigma_mu.
  3. Halve ue.q, giving q, the significance level of one tail.
  4. Return T, sigma_mu and q as 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

source

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