Asset selection

Asset selectors narrow the universe from the data: drop constant columns, keep the best or worst assets by a risk measure, prune redundant ones. They are ordinary returns-preprocessing estimators — they know nothing about pipelines, and a Pipeline drives them through fit_preprocessing and apply_preprocessing like any other step.

The universe a selector chooses on the training window is its fitted state. Applying the fitted result to an unseen window replays that universe rather than re-deciding it, which is what makes a selector safe inside cross-validation.

See docs/adr/0029-asset-selection-is-returns-preprocessing.md for the design rationale, and PortfolioOptimisers.AbstractAssetSelector for the seam every selector shares.

Scoring assets with a risk measure

A ScoreSelector scores each asset by evaluating a risk measure on that asset's own return series, then hands the scores to a rule. Any risk measure whose supports_precomputed_returns is true may be used, which covers the quantile and drawdown families, the moment measures, and MeanReturn. bigger_is_better tells the ordinal rules which end of the ordering is "best".

Two measures are notable exceptions. Variance and StandardDeviation are WeightsInput measures: their functors consume portfolio weights, not a return series, so they cannot score a single asset and are rejected at construction. Use SCM(), which computes the same quantity from a return series — ZeroVarianceFilter spells this for you.

PortfolioOptimisers.ScoreSelectorType
struct ScoreSelector{__T_score, __T_rule} <: AbstractAssetSelector

Asset selector that scores every asset with a risk measure and keeps the assets a rule admits.

score is any AbstractBaseRiskMeasure that can be evaluated on a bare return series — asset i's score is score(X[:, i]). That reuses the whole risk-measure family: ConditionalValueatRisk and the drawdown measures score risk, SCM() scores variance, MeanReturn scores mean return. bigger_is_better tells the ordinal rules which end is "best".

rule decides what to do with the scores: an absolute band (ThresholdRule) or a count/fraction taken from the tails (RankRule, QuantileRule).

The selected universe is fitted state, so a ScoreSelector is safe inside cross-validation: assets are chosen on the training window and the same universe is replayed on test windows.

Fields

Constructors

ScoreSelector(;    score::AbstractBaseRiskMeasure,    rule::AbstractSelectionRule,) -> ScoreSelector

Keywords correspond to the struct's fields.

Validation

Examples

julia> rd = ReturnsResult(; nx = ["A", "B", "C"], X = [0.1 0.0 -0.2; -0.1 0.0 0.3; 0.2 0.0 -0.1]);julia> sel = ScoreSelector(; score = SCM(), rule = ThresholdRule(; lo = 1e-12));julia> PortfolioOptimisers.fit_preprocessing(sel, rd).nx2-element Vector{String}: "A" "C"

Related

source
PortfolioOptimisers.CompleteAssetSelectorType
struct CompleteAssetSelector <: AbstractAssetSelector

Asset selector that keeps the Coverage Universe of the training window, and drops every other asset column.

The returns-level counterpart of MissingDataFilter's column threshold, for pipelines fed returns data directly (where the price stages never run). It has no observation-dropping mode: a fitted selector cannot decide which rows of an unseen window to drop without breaking the weights/returns alignment.

Every selector of the family is fitted on the Coverage Universe, so this one is the identity on the window it receives, and it is the explicit step that asks for the reduction and for nothing else. It drops an asset whose return is non-finite at any row of the window, and an asset the AssetPanel reports inactive at any row of it.

A returns carrier binds X to a matrix of numbers, so a missing never reaches this selector: ReturnsResult rejects a Matrix{Union{Missing, Float64}} at construction. MissingDataFilter removes a missing from the price data upstream.

Constructors

CompleteAssetSelector() -> CompleteAssetSelector

Examples

julia> rd = ReturnsResult(; nx = ["A", "B"], X = [0.1 0.2; 0.3 NaN]);julia> PortfolioOptimisers.fit_preprocessing(CompleteAssetSelector(), rd).nx1-element Vector{String}: "A"

Related

source

Selection rules

A rule turns per-asset scores into a keep-mask. ThresholdRule is literal — it compares raw scores against absolute bounds and ignores orientation, because a zero-variance filter must drop the low-variance assets. RankRule and QuantileRule are ordinal — they consult bigger_is_better and take counts (or fractions) from each tail.

Ties at a rank cut are excluded entirely, so an ordinal rule may return fewer assets than asked. If the 20th and 21st assets score equally, RankRule(; best = 20) keeps 19: the tied block is dropped rather than split arbitrarily. This is the library's "if we cannot tell them apart, trust neither" tie policy.

PortfolioOptimisers.AbstractSelectionRuleType
abstract type AbstractSelectionRule <: AbstractAlgorithm

Abstract supertype for the rules that turn per-asset scores into a keep-mask.

A selection rule is an AbstractAlgorithm: it is consumed through ScoreSelector and never used on its own. Rules split into two kinds.

  • Literal: ThresholdRule compares a score against absolute bounds and ignores bigger_is_better. A threshold on a variance means what it says; reinterpreting it as "keep the better ones" would invert the intent of a zero-variance filter.
  • Ordinal: RankRule and QuantileRule sort assets from best to worst — consulting bigger_is_better, so :best is lowest risk for a risk measure and highest value for a return measure — and take counts or fractions from each tail.

Interfaces

In order to implement a new selection rule that works seamlessly with the library, subtype AbstractSelectionRule with all necessary parameters as part of the struct, and implement the following method:

  • rule_keep(rule::AbstractSelectionRule, scores::VecNum, bib::Bool) -> BitVector: Turn the per-asset scores into a keep-mask.

Arguments

  • rule: The concrete selection rule instance.
  • scores: Per-asset score vector assets × 1.
  • bib: bigger_is_better flag of the risk measure that produced scores. A literal rule ignores it.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every admitted asset.

Related

source
PortfolioOptimisers.ThresholdRuleType
struct ThresholdRule{__T_lo, __T_hi} <: AbstractSelectionRule

Keep assets whose score falls strictly inside the band (lo, hi).

Both bounds are optional and literal: lo and hi are compared against the raw score, never reinterpreted through bigger_is_better. Omitting a bound leaves that side unbounded.

Mathematical definition

\[\begin{align} \mathcal{K} &= \left\{ i : l < s_{i} < u \right\}\,. \end{align}\]

Where:

  • $\mathcal{K}$: Set of the assets a selector keeps.
  • $s_{i}$: Score of asset $i$, the risk measure evaluated on that asset's own return series.
  • $l$: lo, the lower bound. An omitted bound is $-\infty$.
  • $u$: hi, the upper bound. An omitted bound is $+\infty$.

Both comparisons are strict, so the band is open: an asset whose score equals $l$ or $u$ is dropped.

Fields

  • lo: Exclusive lower bound on the score; nothing leaves the lower side unbounded.
  • hi: Exclusive upper bound on the score; nothing leaves the upper side unbounded.

Constructors

ThresholdRule(;    lo::Option{<:Number} = nothing,    hi::Option{<:Number} = nothing,) -> ThresholdRule

Keywords correspond to the struct's fields.

Validation

  • At least one of lo, hi is not nothing.
  • If both are given, lo < hi.

Examples

julia> ThresholdRule(; lo = 1e-12)   # drop (near-)constant assetsThresholdRule  lo ┼ Float64: 1.0e-12  hi ┴ nothing

Related

source
PortfolioOptimisers.RankRuleType
struct RankRule{__T_best, __T_worst, __T_action} <: AbstractSelectionRule

Take best and/or worst assets from the tails of the score ordering, then keep or drop them.

best and worst are counts taken from each end, not positions: best = 20 means twenty assets, not rank twenty. Which end is "best" comes from bigger_is_better on the score, so RankRule(; best = 20) keeps the twenty lowest-risk assets for a risk measure and the twenty highest-return assets for MeanReturn. Giving both takes both tails. action = :drop complements the whole selection, which is how "drop the five worst" is said without knowing the universe size.

Counts saturate at the number of assets: best = 50 on a 30-asset window keeps all 30 rather than throwing, so a hyperparameter search over best is never killed by its largest point.

Warning

Ties at the cut are excluded entirely, so a rule may return fewer assets than asked. If the 20th and 21st assets have equal scores, RankRule(; best = 20) keeps 19 — the tied block is dropped rather than split arbitrarily. This is the library's "if we cannot tell them apart, trust neither" tie policy, shared with find_uncorrelated_indices, which removes both assets of an exactly-tied correlated pair. A window whose scores are all equal therefore selects nothing, and fit_preprocessing throws.

Mathematical definition

\[\begin{align} \mathcal{T}(k) &= \left\{ i : a_{i} + e_{i} \leq k \right\}\,, \\ \mathcal{S} &= \mathcal{T}_{\mathrm{best}}(k_{b}) \cup \mathcal{T}_{\mathrm{worst}}(k_{w})\,, \\ \mathcal{K} &= \begin{cases} \mathcal{S} & \text{keep} \\ \left\{ 1,\, \dots,\, N \right\} \setminus \mathcal{S} & \text{drop} \end{cases}\,. \end{align}\]

Where:

  • $\mathcal{K}$: Set of the assets a selector keeps.
  • $s_{i}$: Score of asset $i$, the risk measure evaluated on that asset's own return series.
  • $N$: Number of assets.
  • $k$: Number of assets taken from one end of the score ordering.
  • $\mathcal{T}(k)$: Tail of size $k$, taken at the end the subscript names.
  • $a_{i}$: Number of assets whose score is strictly better than $s_{i}$. At the best end a larger score is better when bigger_is_better is true and a smaller score is better when it is false; the worst end reverses that.
  • $e_{i}$: Number of assets whose score equals $s_{i}$, asset $i$ included, so $e_{i} \geq 1$.
  • $k_{b}$, $k_{w}$: best and worst, each saturated to $[0,\, N]$. An omitted count is $0$.
  • $\mathcal{S}$: Union of the two tails, before action is applied.

Two consequences follow. A tail admits asset $i$ only when the whole tied block of $i$ fits within $k$, so a block that straddles the cut is excluded and $\left| \mathcal{T}(k) \right| \leq k$. A universe whose scores are all equal gives $a_{i} + e_{i} = N$ for every asset, so every tail of size $k < N$ is empty.

Fields

  • best: Number of assets to take from the best end; nothing takes none.
  • worst: Number of assets to take from the worst end; nothing takes none.
  • action: :keep retains the taken assets, :drop retains everything else.

Constructors

RankRule(;    best::Option{<:Integer} = nothing,    worst::Option{<:Integer} = nothing,    action::Symbol = :keep,) -> RankRule

Keywords correspond to the struct's fields.

Validation

  • At least one of best, worst is not nothing.
  • Any given count is >= 0, and at least one is > 0.
  • action in (:keep, :drop).

Examples

julia> RankRule(; worst = 5, action = :drop)   # drop the five worstRankRule    best ┼ nothing   worst ┼ Int64: 5  action ┴ Symbol: :drop

Related

source
PortfolioOptimisers.QuantileRuleType
struct QuantileRule{__T_best, __T_worst, __T_action} <: AbstractSelectionRule

RankRule with the tail sizes given as fractions of the asset universe.

best and worst are fractions in (0, 1), converted to counts as round(Int, fraction * n, RoundNearestTiesUp) on the window being fitted. An exact half rounds up, so best = 0.625 on a 4-asset window takes 3 assets, not the 2 that Julia's default banker's rounding would give. Everything else — orientation via bigger_is_better, the action complement, count saturation, and the tie policy that excludes a straddling tied block — is identical to RankRule.

Fractions and counts are separate types on purpose: best = 1 (one asset) and best = 1.0 (the whole universe) would otherwise differ only by a literal's type.

Mathematical definition

\[\begin{align} k &= \left\lfloor f N + \frac{1}{2} \right\rfloor\,. \end{align}\]

Where:

  • $k$: Number of assets taken from one end of the score ordering.
  • $N$: Number of assets.
  • $f$: best or worst, a fraction in $(0,\, 1)$.

The floor of $f N + 1/2$ is RoundNearestTiesUp: an exact half rounds up, and not to the even neighbour that Julia's default RoundNearest picks. The two counts are then the $k_{b}$ and $k_{w}$ of RankRule, which states the admitted set.

Fields

  • best: Fraction of the universe to take from the best end; nothing takes none.
  • worst: Fraction of the universe to take from the worst end; nothing takes none.
  • action: :keep retains the taken assets, :drop retains everything else.

Constructors

QuantileRule(;    best::Option{<:Real} = nothing,    worst::Option{<:Real} = nothing,    action::Symbol = :keep,) -> QuantileRule

Keywords correspond to the struct's fields.

Validation

  • At least one of best, worst is not nothing.
  • Any given fraction lies in (0, 1).
  • action in (:keep, :drop).

Examples

julia> QuantileRule(; worst = 0.1, action = :drop)   # drop the worst decileQuantileRule    best ┼ nothing   worst ┼ Float64: 0.1  action ┴ Symbol: :drop

Related

source

Discarding redundant assets

A RedundancySelector discards assets that duplicate information already carried by others. Its alg decides what "redundant" means, and its score decides which member of a redundancy group survives.

PairwiseCorrelation is greedy: it drops one asset at a time until no surviving pair exceeds the threshold, and never chains. CorrelationComponents reads the same correlations transitively, treating a chain A ~ B ~ C as one blob and keeping a single representative — a stronger reduction, and a different answer on the same input. ClusterGroups partitions with clusterise and keeps one representative per cluster.

Leaving score as nothing falls back to the correlation algorithms' own survivor rule: the asset with the lowest summary correlation to the rest of the universe. ClusterGroups has no such fallback and requires a score.

ClusterGroups is also the only redundancy algorithm that reaches a distance estimator — the other two carry a StatsBase.CovarianceEstimator — so it is the only one that can be driven by a feature matrix rather than by the returns. Give its cle a FeatureDistance and the redundancy groups come from exogenous structure: a sector taxonomy, carried as a categorical Panel Field through panel_input, reduces the universe to one representative per classification, not per correlated blob. The panel is read straight off the ReturnsResult, because preselection runs before any prior exists — a producer that reads a prior raises here, and PhylogenyPanel is the one that does not.

PortfolioOptimisers.AbstractRedundancyAlgorithmType
abstract type AbstractRedundancyAlgorithm <: AbstractAlgorithm

Abstract supertype for the algorithms that decide which assets a RedundancySelector discards as redundant.

Each algorithm answers the same question — given the data and, optionally, a per-asset score, which columns survive? — and returns a keep-mask. The keep-mask, not a partition into groups, is the seam: PairwiseCorrelation drops one asset at a time and may keep two members of the same correlated blob, which "partition, then keep the best of each group" cannot express.

Two algorithms do partition, and share groups_argbest:

Interfaces

Concrete redundancy algorithms must implement:

  • redundancy_keep(alg::MyAlgorithm, rd, scores, bib) -> BitVector.
  • requires_score(::MyAlgorithm) -> Bool, if the algorithm cannot pick a survivor without one.

Arguments

  • alg: The concrete redundancy algorithm instance.
  • rd: The returns result to use.
  • scores: Per-asset score vector assets × 1, or nothing when the selector carries no score.
  • bib: bigger_is_better flag of the score. It is false when scores is nothing.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every surviving asset.

Related

source
PortfolioOptimisers.RedundancySelectorType
struct RedundancySelector{__T_alg, __T_score} <: AbstractAssetSelector

Asset selector that discards assets which duplicate information already carried by others.

alg decides what "redundant" means and returns the keep-mask: greedy pairwise correlation pruning (PairwiseCorrelation), one representative per correlated blob (CorrelationComponents), or one representative per cluster (ClusterGroups).

score decides which asset survives a redundancy group — a risk measure evaluated on each asset's own return series, oriented by bigger_is_better, exactly as in ScoreSelector. Leaving it nothing falls back to the correlation algorithms' own rule: the asset with the lowest summary correlation to the rest of the universe survives. ClusterGroups has no such fallback and requires a score.

Fields

  • score: Risk measure choosing the survivor of each redundancy group; nothing uses the algorithm's own rule (AbstractBaseRiskMeasure).

Constructors

RedundancySelector(;    alg::AbstractRedundancyAlgorithm = PairwiseCorrelation(),    score::Option{<:AbstractBaseRiskMeasure} = nothing,) -> RedundancySelector

Keywords correspond to the struct's fields.

Validation

  • If score is given, supports_precomputed_returns(score).
  • If requires_score(alg), score is not nothing.

Examples

julia> rd = ReturnsResult(; nx = ["A", "B", "C"],                          X = [0.10 0.10 -0.05; -0.10 -0.10 0.07; 0.05 0.05 -0.02;                               0.02 0.02 0.09]);julia> sel = RedundancySelector(; alg = PairwiseCorrelation(; t = 0.99), score = SCM());julia> PortfolioOptimisers.fit_preprocessing(sel, rd).nx1-element Vector{String}: "C"

A and B are identical, so neither survives — the tie policy discards both.

Related

source
PortfolioOptimisers.PairwiseCorrelationType
struct PairwiseCorrelation{__T_ce, __T_t, __T_absolute, __T_measure} <: AbstractRedundancyAlgorithm

Greedy pairwise correlation pruning: drop assets until no surviving pair exceeds t.

Correlated pairs are visited from most to least correlated, and the worse asset of each pair is removed. "Worse" means the higher drop score: the RedundancySelector's score when it has one, otherwise each asset's summary correlation to the rest of the universe — so the asset that is redundant with most of the universe goes first.

This algorithm never chains. At t = 0.7, a universe with ρ(A, B) = 0.80, ρ(B, C) = 0.81 and ρ(A, C) = 0.32 loses B and keeps both A and C, honouring the literal promise that no surviving pair exceeds t. CorrelationComponents reads the same three correlations transitively and keeps only A.

How loose the middle correlation can be is bounded by the other two: two edges at ρ force the third above ρ² - (1 - ρ²), so a chain of two 0.97 edges cannot have a third correlation below 0.88. A weakly-connected chain therefore needs weak edges, and the two algorithms diverge most where t sits just under them.

Delegates to find_uncorrelated_indices.

Mathematical definition

\[\begin{align} \rho_{i,\,j} &< t \quad \forall\, i \neq j \in \mathcal{K}\,. \end{align}\]

Where:

  • $\rho_{i,\,j}$: Pairwise correlation coefficient between assets $i$ and $j$. The absolute value $\left| \rho_{i,\,j} \right|$ is read instead when absolute is true.
  • $t$: Correlation at or above which two assets are redundant.
  • $\mathcal{K}$: Set of the assets a selector keeps.

That is a promise about surviving pairs and about nothing else. It does not say that $\mathcal{K}$ is the largest such set, and it does not close under transitivity: a chain of two over-threshold edges whose end points are under the threshold satisfies it with both end points kept.

Fields

  • ce: Covariance estimator supplying the correlation matrix.
  • t: Correlation at or above which two assets are redundant.
  • absolute: Whether to compare the absolute value of the correlation.
  • measure: Reducer producing the fallback drop score from each column of the correlation matrix; ignored when the selector carries a score.

Constructors

PairwiseCorrelation(;    ce::StatsBase.CovarianceEstimator = PortfolioOptimisersCovariance(),    t::Number = 0.95,    absolute::Bool = false,    measure::Num_VecToScaM = MeanValue(),) -> PairwiseCorrelation

Keywords correspond to the struct's fields.

Validation

Related

source
PortfolioOptimisers.CorrelationComponentsType
struct CorrelationComponents{__T_ce, __T_t, __T_absolute, __T_measure} <: AbstractRedundancyAlgorithm

Group assets by connected component of the over-threshold correlation graph, and keep the best-scoring member of each.

Two assets share an edge when their (absolute) correlation is at or above t. Components are transitive, so this reads a chain A ~ B ~ C as one redundant blob even when A and C are uncorrelated, and keeps a single asset from it. That is a stronger claim than PairwiseCorrelation's, and a stronger reduction; choose it when you want one representative per correlated blob rather than a guarantee about surviving pairs.

A component whose best score is tied keeps nobody (see groups_argbest).

Mathematical definition

\[\begin{align} \mathcal{E} &= \left\{ \left\{ i,\, j \right\} : i \neq j\,,\; \rho_{i,\,j} \geq t \right\}\,. \end{align}\]

Where:

  • $\mathcal{E}$: Edge set of the redundancy graph, whose vertices are the assets.
  • $\rho_{i,\,j}$: Pairwise correlation coefficient between assets $i$ and $j$. The absolute value $\left| \rho_{i,\,j} \right|$ is read instead when absolute is true.
  • $t$: Correlation at or above which two assets are redundant.

The groups are the connected components of that graph, so every asset lies in exactly one of them and an asset with no over-threshold partner forms a singleton. A component is closed under transitivity even though the edge relation is not, which is the whole difference from PairwiseCorrelation: a chain $i \sim j \sim k$ is one component whatever $\rho_{i,\,k}$ is.

Fields

  • ce: Covariance estimator supplying the correlation matrix.
  • t: Correlation at or above which two assets are redundant.
  • absolute: Whether to compare the absolute value of the correlation.
  • measure: Reducer producing the fallback drop score from each column of the correlation matrix; ignored when the selector carries a score. Lower is better, so the surviving representative is the least redundant member of its component.

Constructors

CorrelationComponents(;    ce::StatsBase.CovarianceEstimator = PortfolioOptimisersCovariance(),    t::Number = 0.95,    absolute::Bool = false,    measure::Num_VecToScaM = MeanValue(),) -> CorrelationComponents

Keywords correspond to the struct's fields.

Validation

Related

source
PortfolioOptimisers.ClusterGroupsType
struct ClusterGroups{__T_cle} <: AbstractRedundancyAlgorithm

Group assets by clustering them, and keep the best-scoring member of each cluster.

Clusters come from clusterise, so the whole clustering family — hierarchical linkage, DBHT, the non-hierarchical algorithms, and the optimal-number-of-clusters estimators — is available for deciding what "redundant" means. Unlike the correlation algorithms there is no natural fallback survivor rule, so a RedundancySelector using ClusterGroups must carry a score.

A cluster whose best score is tied keeps nobody (see groups_argbest).

Clustering on a feature matrix

A FeatureDistance in the cle's distance slot measures a Feature Matrix rather than the returns, and it derives that matrix from the AssetPanel on rd — the data carrier the selector is fitted on. Preselection is a pre-prior site, so it passes rd alone: a selector is fitted by fit_preprocessing from the returns data alone and never sees a prior result, and in a Pipeline it writes :returns, which invalidates any :prior already computed. A producer that needs a prior therefore raises here, and it names the site. Supply an AssetPanel on the ReturnsResult — for instance from asset_panel — or the clustering throws (see asset_panel).

The selection is decided on the full universe and the surviving columns are sliced only afterwards, so Z is measured over every asset before any is dropped.

The names come off the same panel as the values, so a FeatureDistance carrying a sel names its feature columns here exactly as it does anywhere else. That matters most at this site: a panel presents every slice as a feature, the observed masks and the one-hot levels included, and a redundancy selector that measured all of them would drop assets on a distance the caller never asked for.

Fields

Constructors

ClusterGroups(;    cle::AbstractClustersEstimator = ClustersEstimator(),) -> ClusterGroups

Keywords correspond to the struct's fields.

Related

source

Functions

PortfolioOptimisers.asset_scoresFunction
asset_scores(
    score::AbstractBaseRiskMeasure,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Any

Evaluate score on every asset column of X.

Columns are passed as views: a risk-measure functor reads its argument and never writes to it.

Algorithm

  1. Evaluate score on a view of each asset column of X in turn, giving scores.
  2. Check that every entry of scores is finite.
  3. Return scores.

Arguments

  • score: The risk measure to evaluate on each asset column.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.

Validation

  • Every score is finite, else a DomainError is thrown naming the offending columns. A NaN score (Skewness on a constant series, say, which divides by a zero standard deviation) would make the ordering meaningless, so it throws rather than sorting arbitrarily.

Returns

  • scores::VecNum: Score vector assets × 1, one entry per asset column of X.

Related

source
PortfolioOptimisers.rule_keepFunction
rule_keep(
    rule::ThresholdRule,
    scores::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    _::Bool
) -> BitVector

Turn per-asset scores into a keep-mask under a selection rule.

bib is the bigger_is_better flag of the score that produced them; ThresholdRule ignores it.

One method per rule. The ThresholdRule method compares each score against the open band and returns the mask directly. The RankRule method hands its two counts to tail_action_mask. The QuantileRule method converts each fraction to a count with round(Int, f * n, RoundNearestTiesUp) first, then hands those counts to the same function.

Arguments

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

Returns

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

Related

source
PortfolioOptimisers.redundancy_keepFunction
redundancy_keep(alg::AbstractRedundancyAlgorithm, rd, scores, bib) -> BitVector

Return the keep-mask a redundancy algorithm admits.

scores is nothing when the RedundancySelector carries no score; otherwise it is the per-asset score vector and bib is the score's bigger_is_better flag.

The method shown here is the family's fallback. It runs only for an algorithm that implements none of its own, and it always throws.

Arguments

  • alg: The redundancy algorithm.
  • rd: The returns result to use.
  • scores: Per-asset score vector assets × 1, or nothing.
  • bib: bigger_is_better flag of the score.

Validation

  • The concrete algorithm implements redundancy_keep, else an ArgumentError is thrown naming the two methods an extension author must define.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every surviving asset.

Related

source
redundancy_keep(
    alg::PairwiseCorrelation,
    rd::AbstractReturnsResult,
    scores::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    bib::Bool
) -> BitVector

Keep a maximally uncorrelated subset under PairwiseCorrelation, by delegating to find_uncorrelated_indices.

Algorithm

  1. Start from a keep-mask of size(rd.X, 2) falses.
  2. Turn scores into drop scores with drop_scores, so that downstream a lower number is better. Pass nothing when scores is nothing, which leaves find_uncorrelated_indices to build its own drop score by collapsing each column of the correlation matrix with measure.
  3. Call find_uncorrelated_indices on rd.X with the algorithm's ce, t, absolute and measure, giving idx, the surviving asset indices.
  4. Set the mask at idx, and return it.

Arguments

  • alg: The pairwise correlation algorithm.
  • rd: The returns result to use.
  • scores: Per-asset score vector assets × 1, or nothing.
  • bib: bigger_is_better flag of the score.

Returns

  • keep::BitVector: Mask assets × 1 that is true for every surviving asset.

Related

source
redundancy_keep(
    alg::CorrelationComponents,
    rd::AbstractReturnsResult,
    scores::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    bib::Bool
) -> BitVector

Keep one representative of each correlated blob under CorrelationComponents.

Algorithm

  1. Compute the correlation matrix rho of rd.X with the algorithm's ce.
  2. When absolute is true, replace rho with its entrywise absolute value. This happens before the threshold is applied, so a strongly negative correlation is an edge.
  3. When scores is nothing, collapse each column of rho with measure into the fallback score s, and force sbib to false. A lower summary correlation is then better, so the surviving representative is the asset least correlated with the rest of the universe. Otherwise take s from scores and sbib from bib.
  4. Group the assets with correlation_components on rho and t.
  5. Return the mask groups_argbest admits for those groups under s and sbib.

Arguments

  • alg: The correlation components algorithm.
  • rd: The returns result to use.
  • scores: Per-asset score vector assets × 1, or nothing.
  • bib: bigger_is_better flag of the score.

Returns

  • keep::BitVector: Mask assets × 1 that is true for one asset of every component whose best score is not tied.

Related

source
redundancy_keep(
    alg::ClusterGroups,
    rd::AbstractReturnsResult,
    scores::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    bib::Bool
) -> BitVector

Keep one representative of each cluster under ClusterGroups.

Algorithm

  1. Cluster the assets with clusterise on rd.X, passing rd so that a FeatureDistance resolves its panel off the data carrier, giving the clustering result clr.
  2. Read the cluster assignment of every asset into idx.
  3. Collect the asset indices of each of the clr.k clusters into groups.
  4. Return the mask groups_argbest admits for those groups under scores and bib.

Only rd is passed, because preselection runs before any prior exists, so the data carrier is the only reachable source of a Feature Matrix. ClusterGroups states why the type carries no source selector.

Arguments

  • alg: The cluster groups algorithm.
  • rd: The returns result to use.
  • scores: Per-asset score vector assets × 1. RedundancySelector rejects a nothing score for this algorithm at construction, so it is never nothing here.
  • bib: bigger_is_better flag of the score.

Returns

  • keep::BitVector: Mask assets × 1 that is true for one asset of every cluster whose best score is not tied.

Related

source
PortfolioOptimisers.requires_scoreFunction
requires_score(_::AbstractRedundancyAlgorithm) -> Bool

Return whether a redundancy algorithm needs a score to pick the survivor of a redundancy group.

Correlation-based algorithms fall back on each asset's summary correlation to the rest of the universe when no score is given, so they return false. ClusterGroups has no such fallback and returns true.

The generic method answers true, so a new algorithm is asked for a score until it says otherwise. That is the safe default: an algorithm that silently accepted nothing and had no fallback would pick a survivor from a nothing score vector.

Arguments

  • The algorithm is taken by type alone. No field of it is read.

Returns

  • req::Bool: true when the algorithm cannot pick the survivor of a group without a score.

Related

source