Asset selection: private API

Functions

PortfolioOptimisers.tail_maskFunction
tail_mask(
    scores::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    k::Integer,
    bib::Bool,
    tail::Symbol
) -> BitVector

Return the mask of the k assets furthest into the tail end of the score ordering.

tail is :best or :worst; which raw direction that is comes from bib, the bigger_is_better flag of the score. The count saturates at length(scores).

Assets tied with the k-th score are included only when the whole tied block fits within k. Otherwise the block straddles the cut and is excluded — the "trust neither" tie policy, which is why the returned mask may hold fewer than k assets. RankRule states the admitted set in closed form.

Algorithm

  1. Read n, the number of scores. Return n falses when k is not positive, and n trues when k is at least n. The second case is the saturation of the count.
  2. Choose the comparison ahead. It is > when tail and bib agree that a larger score lies further into the tail, and < otherwise.
  3. Sort the scores under ahead with sortperm, giving perm, and read cut, the score at position k of that order.
  4. Count the scores strictly ahead of cut, giving n_ahead, and the scores equal to cut, giving n_eq.
  5. Set keep_ties to n_ahead + n_eq == k. The tied block at the cut fits inside k only then, because cut sits at position k and so n_ahead + n_eq >= k always holds.
  6. Return the mask that is true for every score strictly ahead of cut, and for a score equal to cut when keep_ties is true.

Arguments

  • scores: Per-asset score vector assets × 1.
  • k: Number of assets to take from the tail end.
  • bib: bigger_is_better flag of the score.
  • tail: :best or :worst, the end to take from.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every asset in the tail. It holds at most k assets, and fewer when a tied block straddles the cut.

Related

source
PortfolioOptimisers.tail_action_maskFunction
tail_action_mask(
    best::Union{Nothing, Integer},
    worst::Union{Nothing, Integer},
    action::Symbol,
    scores::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    bib::Bool
) -> BitVector

Union the two tail masks and apply the rule's action.

Algorithm

  1. Start from a mask of length(scores) falses.
  2. When best is not nothing, take the :best tail of size best with tail_mask and union it into the mask.
  3. When worst is not nothing, take the :worst tail of size worst with tail_mask and union it into the mask.
  4. Return the mask when action is :keep, and its complement when action is :drop.

Arguments

  • best: Number of assets to take from the best end, or nothing.
  • worst: Number of assets to take from the worst end, or nothing.
  • action: :keep returns the union, :drop returns its complement.
  • scores: Per-asset score vector assets × 1.
  • bib: bigger_is_better flag of the score.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every asset the rule admits.

Related

source
PortfolioOptimisers.groups_argbestFunction
groups_argbest(
    groups,
    scores::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    bib::Bool
) -> BitVector

Keep the single best-scoring member of each redundancy group.

A group whose best score is tied keeps nobody: under the library's "if we cannot tell them apart, trust neither" policy, two indistinguishable assets are both discarded — the same stance find_uncorrelated_indices takes on an exactly-tied correlated pair. A singleton group is trivially unambiguous and always survives.

Algorithm

  1. Start from a keep-mask of length(scores) falses.
  2. Choose the comparison better. It is > when bib is true, and < otherwise.
  3. Take the next group g of groups, and skip it when it is empty.
  4. Walk the members of g from the first, holding in best the index whose score no later member beats under better.
  5. Count the members of g whose score equals scores[best]. Set the mask at best only when that count is one, so a group with a tied best keeps nobody.
  6. Repeat steps 3 to 5 over the remaining groups, then return the mask.

Arguments

  • groups: Vector of index vectors partitioning the assets.
  • scores: Per-asset scores.
  • bib: Whether a larger score is better.

Returns

  • keep::BitVector: One survivor per unambiguous group.

Related

source
PortfolioOptimisers.correlation_componentsFunction
correlation_components(
    rho::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    t::Number
) -> Vector{Vector{Int64}}

Return the connected components of the graph whose edges are the pairs of rho at or above t.

A union-find pass over the strict lower triangle, so components are transitive and every asset lands in exactly one of them (a singleton when it has no over-threshold partner).

Algorithm

  1. Read n, the number of rows of rho, and set parent[i] to i for every asset. Each asset starts as its own component.
  2. Take the next entry of the strict lower triangle of rho, which holds each pair once.
  3. When that entry is at least t, find the root of each member of the pair with find, and point the larger root at the smaller one. find compresses the path it walks, so a later lookup on that chain is one step.
  4. Repeat steps 2 and 3 over the remaining pairs.
  5. Group the assets by their final root into groups, keyed by that root.
  6. Return the values of groups.

The threshold is compared against rho as it is given, so a caller that wants the absolute value takes it before this call.

Arguments

  • rho: Correlation matrix assets × assets.
  • t: Threshold value.

Returns

  • groups::Vector{Vector{Int}}: The components, each a vector of asset indices in ascending order. The components themselves come out of a Dict, so their order is not defined. groups_argbest reads them as a set and does not depend on it.

Related

source
PortfolioOptimisers.drop_scoresFunction
drop_scores(
    scores::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    bib::Bool
) -> Any

Convert per-asset scores into drop scores, where higher means "discard me first".

A risk measure with bigger_is_better == false (lower risk is better) already reads as a drop score; one with bigger_is_better == true is negated. Downstream of this call a lower number is always better, whatever the measure's own orientation was.

Mathematical definition

\[\begin{align} d_{i} &= \begin{cases} -s_{i} & \text{a larger score is better} \\ s_{i} & \text{otherwise} \end{cases}\,. \end{align}\]

Where:

  • $d_{i}$: Drop score of asset $i$. The asset with the higher drop score is discarded first.
  • $s_{i}$: Score of asset $i$, the risk measure evaluated on that asset's own return series.

The map is order-reversing in the first case and order-preserving in the second, so it never changes which of two assets is preferred. It changes only the direction that carries that preference.

Arguments

  • scores: Per-asset score vector assets × 1.
  • bib: bigger_is_better flag of the score that produced scores.

Returns

  • d::VecNum: Drop score vector assets × 1, in which a higher number means "discard me first".

Related

source
PortfolioOptimisers.assert_scoreableFunction
assert_scoreable(score::AbstractBaseRiskMeasure)

Validate that score can be evaluated on a single asset's return series.

Scoring asset i is score(X[:, i]), which is exactly the precomputed-returns path supports_precomputed_returns governs. A WeightsInput measure — Variance and StandardDeviation among them — consumes portfolio weights instead, and cannot score an asset.

Arguments

  • score: The risk measure a selector scores its assets with.

Validation

  • supports_precomputed_returns(score), else an ArgumentError is thrown. The message names the measure, and adds a pointer to SCM() when the measure is a Variance or a StandardDeviation.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_selection_actionFunction
assert_selection_action(action::Symbol)

Validate the action field shared by the ordinal selection rules.

Arguments

  • action: The action field of an ordinal selection rule.

Validation

  • action in (:keep, :drop), else an ArgumentError is thrown naming the value it got.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_tail_countsFunction
assert_tail_counts(
    best::Union{Nothing, Integer},
    worst::Union{Nothing, Integer},
    name::Symbol
)

Validate the best/worst tail sizes shared by the ordinal selection rules.

Arguments

  • best: Number of assets to take from the best end, or nothing.
  • worst: Number of assets to take from the worst end, or nothing.
  • name: Name of the rule being constructed, written into every exception message.

Validation

  • At least one of best, worst is not nothing, else an IsNothingError is thrown.
  • Every count that is given is >= 0, else a DomainError is thrown.
  • The larger of the two counts is > 0, else a DomainError is thrown. A rule that takes no asset is rejected.

Returns

  • nothing.

Related

source