The exact projection weight finalisers: private API

PortfolioOptimisers.weights_break_bounds — Function
weights_break_bounds(wb::WeightBounds, w::VecNum) -> Bool

Tests whether a weight in w lies outside the bounds wb.

An absent bound breaks nothing. A NaN weight lies outside no bound, so this test passes it, and weights_meet_bounds rejects it later.

Mathematical definition

\[\exists\, i : w_{i} < l_{i} \quad \textrm{or} \quad w_{i} > u_{i}\,.\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.

Arguments

  • wb: The weight bounds. Either bound can be nothing.
  • w: The weights to test.

Returns

  • flag::Bool: true if a weight lies below its lower bound or above its upper bound.

Related

source
PortfolioOptimisers.euclidean_weight_projection — Function
euclidean_weight_projection(w::VecNum, wb::WeightBounds) -> VecNum

Projects w in the Euclidean norm onto the weights that lie in the bounds wb and sum to sum(w).

EuclideanWeightFinaliser states the programme and its minimiser. This function finds the shift of that minimiser exactly from the kinks of the budget, with no solver and no tolerance.

Algorithm

  1. Read the budget s as sum(w).
  2. Read the bounds lb and ub. An absent bound is the infinity of the type that a division of two weights returns, so it adds no kink.
  3. Collect the kinks w .- ub and w .- lb. At a kink, one weight reaches one of its bounds. Between two neighbouring kinks, the sum of clamp.(w .- theta, lb, ub) is linear in theta and does not increase.
  4. Find the shift theta at which that sum equals s, with breakpoint_root over the kinks.
  5. Return clamp.(w .- theta, lb, ub).

Arguments

  • w: The weights to project.
  • wb: The weight bounds. Either bound can be nothing.

Returns

  • w::VecNum: The projected weights, a new vector. If Σ lb > sum(w) or Σ ub < sum(w), no projection exists. breakpoint_root then finds the root on the flat tail past the last kink, and the returned weights lie on their bounds with a different sum.

Related

source
PortfolioOptimisers.entropic_weight_projection — Function
entropic_weight_projection(w::VecNum, wb::WeightBounds) -> VecNum

Projects w in relative entropy onto the weights that lie in the bounds wb and sum to sum(w), or returns the Euclidean projection where the entropic one is not defined.

EntropicWeightFinaliser states the programme, its minimiser and its domain. This function finds the scale of that minimiser exactly from the kinks of the budget, with no solver and no tolerance.

Algorithm

  1. Read the budget s as sum(w).
  2. Spread the bounds over the assets as lb and ub. An absent lower bound is zero, which changes no clip of a non-negative weight. An absent upper bound is the infinity of the type that a division of two weights returns.
  3. When the entropic projection is not defined, return the projection from euclidean_weight_projection. The projection is not defined when a weight or a lower bound is negative, or when s is not positive. It is also not defined when s is at most sum(lb), or above the sum of ub over the positive weights and lb over the zero weights. At s = sum(lb), the only feasible vector is lb, and the Euclidean projection returns it.
  4. Collect the kinks lb ./ w and ub ./ w. At a kink, one weight reaches one of its bounds. Between two neighbouring kinks, the sum of clamp.(t .* w, lb, ub) is linear in the scale t and does not decrease.
  5. Find the scale t at which that sum equals s, with breakpoint_root over the negated kinks. breakpoint_root needs a sum that does not increase, so it searches in -t.
  6. Return clamp.(t .* w, lb, ub).

Arguments

  • w: The weights to project.
  • wb: The weight bounds. Either bound can be nothing.

Returns

  • w::VecNum: The projected weights, a new vector.

Related

source
PortfolioOptimisers.weights_bound_tolerance — Function
weights_bound_tolerance(::Type{T}) -> T

Return the tolerance to which weights_meet_bounds holds the finalised weights of element type T to their bounds and to their budget.

A floating-point type rounds, so a finaliser that lands on a bound or on the budget can miss it by a few units in the last place. The tolerance is then $\sqrt{\epsilon}$, with $\epsilon$ the machine epsilon of T. A Rational type does not round, so a finaliser lands on a bound exactly, and the tolerance is zero. Rational has no eps method, so the choice cannot come from eps itself.

The choice is made by dispatch on Type{T}. A package that defines another exact number type adds a method for it.

Algorithm

  1. T is a subtype of Rational: return zero(T).
  2. T is any other type: return sqrt(eps(T)). This covers every floating-point type, and every number type that defines eps, such as an automatic-differentiation dual.

Arguments

  • T: The element type of the weights. An Integer type has no fractional weight to compare, so the caller repairs it with float_if_integer first.

Returns

  • tol::T: The tolerance, a non-negative value of type T.

Examples

julia> PortfolioOptimisers.weights_bound_tolerance(Float64)1.4901161193847656e-8julia> PortfolioOptimisers.weights_bound_tolerance(Rational{Int})0//1

Related

source
PortfolioOptimisers.weights_meet_bounds — Function
weights_meet_bounds(wb::WeightBounds, w::VecNum, s::Number) -> Bool

Tests whether the finalised weights w are finite, lie in the bounds wb, and sum to the budget s. The last two conditions hold to a tolerance.

finalise_weight_bounds reads it to choose its return code.

Mathematical definition

Every $w_{i}$ is finite, and:

\[\begin{align} \boldsymbol{l} - \tau &\leq \boldsymbol{w} \leq \boldsymbol{u} + \tau\,, \\ \left\lvert \boldsymbol{1}^\intercal \boldsymbol{w} - s \right\rvert &\leq \tau \max\left(1, \lvert s \rvert\right)\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.
  • $s$: The budget of the weights before the finaliser moved them.
  • $\tau$: The tolerance that weights_bound_tolerance gives for the element type of $\boldsymbol{w}$, which is $\sqrt{\epsilon}$ for a floating-point type and zero for an exact type such as Rational.

The tolerance on the bounds is absolute. The tolerance on the budget is relative when $\lvert s \rvert > 1$.

Arguments

  • wb: The weight bounds. Either bound can be nothing.
  • w: The finalised weights.
  • s: The budget of the weights before the finaliser moved them.

Returns

  • flag::Bool: true if the weights meet all three conditions.

Related

source