ℓ1 Uncertainty Sets: private API
PortfolioOptimisers.l1_activation_ladder — Function
l1_activation_ladder(mu::VecNum, sd::Option{<:VecNum})Return the vector g whose k-th entry is the radius at which the k-th asset becomes active in the long-only problem.
mu must be sorted in non-increasing order, with sd (when given) under the same permutation.
Mathematical definition
\[\begin{align} L_k &= \sum_{i=1}^{k} \frac{\hat{\mu}_i - \hat{\mu}_k}{\sigma_i}\,, \quad k = 1,\, \ldots,\, N\,. \end{align}\]
Where:
- $L_k$: Activation threshold of the $k$-th asset of the ranking.
- $\hat{\mu}_i$: $i$-th entry of the characteristic vector, sorted non-increasing.
- $\sigma_i$: Per-asset scaling of the $i$-th entry of the characteristic vector; $1$ when the set is unscaled.
- $N$: Number of assets.
This is the threshold of Lemma 2 ($\sigma_i = 1$) and of Lemma 9 (scaled) of [4]. Both lemmas state that the number of active assets is the largest $k$ with $L_k < \epsilon$, so a radius in the open interval $(L_q,\, L_{q+1})$ activates exactly $q$ assets, and the active weights are equal (Lemma 2) or inverse-volatility (Lemma 9). Corollaries 4 and 11 read this off at the quintile.
Two consequences bound the sequence. Every summand of $L_1$ is zero, so $L_1 = 0$: a strictly positive radius always activates at least one asset. And $L_{k+1} - L_k = \sum_{i=1}^{k+1} (\hat{\mu}_k - \hat{\mu}_{k+1}) / \sigma_i \geq 0$ because the ranking is non-increasing, so the sequence is non-decreasing and its rungs bracket. Equality holds exactly when $\hat{\mu}_k = \hat{\mu}_{k+1}$, which is the tie Assumption 1 of the paper excludes.
Algorithm
- Read
N = length(mu), the number of rungs to build. - For each
kin1:N, sum $(\hat{\mu}_i - \hat{\mu}_k) / \sigma_i$ overiin1:k, taking $\sigma_i = 1$ whensdisnothing, giving thek-th rung.
Arguments
mu: Characteristic vector, sorted in non-increasing order.sd: Per-asset scaling under the same permutation, ornothingfor an unscaled ladder.
Returns
g::Vector{<:Number}: The activation ladder, one rung per asset.
Examples
julia> PortfolioOptimisers.l1_activation_ladder([0.125, 0.0625, 0.03125], nothing)3-element Vector{Float64}: 0.0 0.0625 0.125julia> PortfolioOptimisers.l1_activation_ladder([0.125, 0.0625, 0.03125], [0.5, 0.25, 0.125])3-element Vector{Float64}: 0.0 0.125 0.3125Related
References
- [4] R. Zhou and D. P. Palomar. Understanding the Quintile Portfolio. IEEE Transactions on Signal Processing 68, 4030–4040 (2020).
PortfolioOptimisers.l1_active_count — Function
l1_active_count(active::Union{<:Integer, <:AbstractFloat}, N::Int)Convert an active target — a count or a fraction of the universe — into an asset count in 1:N.
The type of active selects the rule, and the clamp makes a target outside the universe usable rather than an error: a count above N becomes N, and a fraction that rounds to zero becomes 1.
Algorithm
- When
activeis anInteger, take it as the count itself, givingq. - Otherwise take
round(Int, active * N), the fraction of the universe rounded to the nearest asset, givingq. - Return
clamp(q, 1, N), so the count names an asset of the ranking.
Arguments
active: Target number of active assets, as a count (Integer) or a fraction of the universe (AbstractFloat).N: Number of assets in the universe.
Returns
q::Int: The target count, in1:N.
Examples
julia> PortfolioOptimisers.l1_active_count(3, 10)3julia> PortfolioOptimisers.l1_active_count(0.2, 10)2julia> PortfolioOptimisers.l1_active_count(20, 10)10Related
References
- [4] R. Zhou and D. P. Palomar. Understanding the Quintile Portfolio. IEEE Transactions on Signal Processing 68, 4030–4040 (2020).
PortfolioOptimisers.l1_eps_from_ladder — Function
l1_eps_from_ladder(method, ladder::VecNum, N::Int)Resolve a radius from a Num_UcSEps against an activation ladder.
The method that takes a Number is a passthrough: it returns the radius the caller gave, runs no procedure, and carries neither an # Algorithm nor a # Validation section. The method that takes an ActiveAssetsUncertaintyAlgorithm returns the midpoint of the open interval $(L_q,\, L_{q+1})$ that the closed forms of [4] require for exactly q active entries.
Above the last rung any radius activates every entry, so the top of the ladder has no interval to bisect and the method continues the final increment by a half step instead. A ladder of one rung has no increment either, and the method returns one(eltype(ladder)). That radius is inert rather than calibrated: a one-rung ladder is a one-asset universe, where the budget pins the single weight and every radius gives the same portfolio.
Algorithm
- Read
L = length(ladder), the number of rungs. - Clamp
qto1:L, so the target names a rung. - When
q < L, read the bracketing rungslo, hi = ladder[q], ladder[q+1]and return their midpoint, a radius strictly inside the open interval. - Otherwise
q == L: returnladder[L] + (ladder[L] - ladder[L-1]) / 2, half a final increment above the last rung, orone(eltype(ladder))whenL == 1and there is no increment to continue.
Arguments
method: A radius (Number) or anActiveAssetsUncertaintyAlgorithm.ladder: Activation ladder, asl1_activation_ladderreturns it.q: Target number of active entries.args...: Additional positional arguments (ignored by theNumbermethod).
Validation
length(ladder) >= 1, otherwise anIsEmptyErroris thrown.ladder[q+1] > ladder[q]whenq < L, otherwise aDomainErroris thrown. The rungs coincide when the characteristic has ties across the cut, the interval the closed form needs is empty, and no radius delivers the requested count. Assumption 1 of [4] excludes the case, so the message names the two fixes: break the tie, or pass an explicit radius.
Returns
eps::Number: The resolved radius.
Examples
julia> PortfolioOptimisers.l1_eps_from_ladder(0.5)0.5julia> alg = ActiveAssetsUncertaintyAlgorithm(; active = 2);julia> PortfolioOptimisers.l1_eps_from_ladder(alg, [0.0, 0.0625, 0.125], 2)0.09375julia> PortfolioOptimisers.l1_eps_from_ladder(alg, [0.0, 0.0625, 0.125], 3)0.15625julia> PortfolioOptimisers.l1_eps_from_ladder(alg, [0.0], 1)1.0Related
References
- [4] R. Zhou and D. P. Palomar. Understanding the Quintile Portfolio. IEEE Transactions on Signal Processing 68, 4030–4040 (2020).
References
- [4]
- R. Zhou and D. P. Palomar. Understanding the Quintile Portfolio. IEEE Transactions on Signal Processing 68, 4030–4040 (2020).