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.ScoreSelector — Type
struct ScoreSelector{__T_score, __T_rule} <: AbstractAssetSelectorAsset 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
score: Risk measure scoring each asset's return series (AbstractBaseRiskMeasure).
rule: Rule mapping the scores to a keep-mask (AbstractSelectionRule).
Constructors
ScoreSelector(; score::AbstractBaseRiskMeasure, rule::AbstractSelectionRule,) -> ScoreSelectorKeywords correspond to the struct's fields.
Validation
supports_precomputed_returns(score).VarianceandStandardDeviationareWeightsInputmeasures and are rejected with a pointer toSCM().
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
PortfolioOptimisers.CompleteAssetSelector — Type
struct CompleteAssetSelector <: AbstractAssetSelectorAsset 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() -> CompleteAssetSelectorExamples
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
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.AbstractSelectionRule — Type
abstract type AbstractSelectionRule <: AbstractAlgorithmAbstract 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:
ThresholdRulecompares a score against absolute bounds and ignoresbigger_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:
RankRuleandQuantileRulesort assets from best to worst — consultingbigger_is_better, so:bestis 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 vectorassets × 1.bib:bigger_is_betterflag of the risk measure that producedscores. A literal rule ignores it.
Returns
keep::BitVector: Maskassets × 1that istruefor every admitted asset.
Related
PortfolioOptimisers.ThresholdRule — Type
struct ThresholdRule{__T_lo, __T_hi} <: AbstractSelectionRuleKeep 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;nothingleaves the lower side unbounded.
hi: Exclusive upper bound on the score;nothingleaves the upper side unbounded.
Constructors
ThresholdRule(; lo::Option{<:Number} = nothing, hi::Option{<:Number} = nothing,) -> ThresholdRuleKeywords correspond to the struct's fields.
Validation
- At least one of
lo,hiis notnothing. - If both are given,
lo < hi.
Examples
julia> ThresholdRule(; lo = 1e-12) # drop (near-)constant assetsThresholdRule lo ┼ Float64: 1.0e-12 hi ┴ nothingRelated
PortfolioOptimisers.RankRule — Type
struct RankRule{__T_best, __T_worst, __T_action} <: AbstractSelectionRuleTake 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.
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
bestend a larger score is better whenbigger_is_betteristrueand a smaller score is better when it isfalse; theworstend reverses that. - $e_{i}$: Number of assets whose score equals $s_{i}$, asset $i$ included, so $e_{i} \geq 1$.
- $k_{b}$, $k_{w}$:
bestandworst, each saturated to $[0,\, N]$. An omitted count is $0$. - $\mathcal{S}$: Union of the two tails, before
actionis 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;nothingtakes none.
worst: Number of assets to take from the worst end;nothingtakes none.
action::keepretains the taken assets,:dropretains everything else.
Constructors
RankRule(; best::Option{<:Integer} = nothing, worst::Option{<:Integer} = nothing, action::Symbol = :keep,) -> RankRuleKeywords correspond to the struct's fields.
Validation
- At least one of
best,worstis notnothing. - 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: :dropRelated
PortfolioOptimisers.QuantileRule — Type
struct QuantileRule{__T_best, __T_worst, __T_action} <: AbstractSelectionRuleRankRule 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$:
bestorworst, 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;nothingtakes none.
worst: Fraction of the universe to take from the worst end;nothingtakes none.
action::keepretains the taken assets,:dropretains everything else.
Constructors
QuantileRule(; best::Option{<:Real} = nothing, worst::Option{<:Real} = nothing, action::Symbol = :keep,) -> QuantileRuleKeywords correspond to the struct's fields.
Validation
- At least one of
best,worstis notnothing. - 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: :dropRelated
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.AbstractRedundancyAlgorithm — Type
abstract type AbstractRedundancyAlgorithm <: AbstractAlgorithmAbstract 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:
CorrelationComponentsgroups by connected component of the over-threshold correlation graph.ClusterGroupsgroups byclusteriseassignment.
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 vectorassets × 1, ornothingwhen the selector carries noscore.bib:bigger_is_betterflag of the score. It isfalsewhenscoresisnothing.
Returns
keep::BitVector: Maskassets × 1that istruefor every surviving asset.
Related
PortfolioOptimisers.RedundancySelector — Type
struct RedundancySelector{__T_alg, __T_score} <: AbstractAssetSelectorAsset 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
alg: Algorithm deciding which assets are redundant (AbstractRedundancyAlgorithm).
score: Risk measure choosing the survivor of each redundancy group;nothinguses the algorithm's own rule (AbstractBaseRiskMeasure).
Constructors
RedundancySelector(; alg::AbstractRedundancyAlgorithm = PairwiseCorrelation(), score::Option{<:AbstractBaseRiskMeasure} = nothing,) -> RedundancySelectorKeywords correspond to the struct's fields.
Validation
- If
scoreis given,supports_precomputed_returns(score). - If
requires_score(alg),scoreis notnothing.
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
PortfolioOptimisers.PairwiseCorrelation — Type
struct PairwiseCorrelation{__T_ce, __T_t, __T_absolute, __T_measure} <: AbstractRedundancyAlgorithmGreedy 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
absoluteistrue. - $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 ascore.
Constructors
PairwiseCorrelation(; ce::StatsBase.CovarianceEstimator = PortfolioOptimisersCovariance(), t::Number = 0.95, absolute::Bool = false, measure::Num_VecToScaM = MeanValue(),) -> PairwiseCorrelationKeywords correspond to the struct's fields.
Validation
-1 <= t <= 1, checked byassert_correlation_threshold.
Related
PortfolioOptimisers.CorrelationComponents — Type
struct CorrelationComponents{__T_ce, __T_t, __T_absolute, __T_measure} <: AbstractRedundancyAlgorithmGroup 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
absoluteistrue. - $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 ascore. 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(),) -> CorrelationComponentsKeywords correspond to the struct's fields.
Validation
-1 <= t <= 1, checked byassert_correlation_threshold.
Related
PortfolioOptimisers.ClusterGroups — Type
struct ClusterGroups{__T_cle} <: AbstractRedundancyAlgorithmGroup 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
cle: Clustering estimator partitioning the assets (AbstractClustersEstimator).
Constructors
ClusterGroups(; cle::AbstractClustersEstimator = ClustersEstimator(),) -> ClusterGroupsKeywords correspond to the struct's fields.
Related
Functions
PortfolioOptimisers.asset_scores — Function
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
- Evaluate
scoreon a view of each asset column ofXin turn, givingscores. - Check that every entry of
scoresis finite. - Return
scores.
Arguments
score: The risk measure to evaluate on each asset column.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.
Validation
- Every score is finite, else a
DomainErroris thrown naming the offending columns. ANaNscore (Skewnesson 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 vectorassets × 1, one entry per asset column ofX.
Related
PortfolioOptimisers.rule_keep — Function
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 vectorassets × 1.bib:bigger_is_betterflag of the score that producedscores.
Returns
keep::BitVector: Maskassets × 1that istruefor every asset the rule admits.
Related
PortfolioOptimisers.redundancy_keep — Function
redundancy_keep(alg::AbstractRedundancyAlgorithm, rd, scores, bib) -> BitVectorReturn 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 vectorassets × 1, ornothing.bib:bigger_is_betterflag of the score.
Validation
- The concrete algorithm implements
redundancy_keep, else anArgumentErroris thrown naming the two methods an extension author must define.
Returns
keep::BitVector: Maskassets × 1that istruefor every surviving asset.
Related
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
- Start from a keep-mask of
size(rd.X, 2)falses. - Turn
scoresinto drop scores withdrop_scores, so that downstream a lower number is better. Passnothingwhenscoresisnothing, which leavesfind_uncorrelated_indicesto build its own drop score by collapsing each column of the correlation matrix withmeasure. - Call
find_uncorrelated_indicesonrd.Xwith the algorithm'sce,t,absoluteandmeasure, givingidx, the surviving asset indices. - Set the mask at
idx, and return it.
Arguments
alg: The pairwise correlation algorithm.rd: The returns result to use.scores: Per-asset score vectorassets × 1, ornothing.bib:bigger_is_betterflag of the score.
Returns
keep::BitVector: Maskassets × 1that istruefor every surviving asset.
Related
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
- Compute the correlation matrix
rhoofrd.Xwith the algorithm'sce. - When
absoluteistrue, replacerhowith its entrywise absolute value. This happens before the threshold is applied, so a strongly negative correlation is an edge. - When
scoresisnothing, collapse each column ofrhowithmeasureinto the fallback scores, and forcesbibtofalse. A lower summary correlation is then better, so the surviving representative is the asset least correlated with the rest of the universe. Otherwise takesfromscoresandsbibfrombib. - Group the assets with
correlation_componentsonrhoandt. - Return the mask
groups_argbestadmits for those groups undersandsbib.
Arguments
alg: The correlation components algorithm.rd: The returns result to use.scores: Per-asset score vectorassets × 1, ornothing.bib:bigger_is_betterflag of the score.
Returns
keep::BitVector: Maskassets × 1that istruefor one asset of every component whose best score is not tied.
Related
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
- Cluster the assets with
clusteriseonrd.X, passingrdso that aFeatureDistanceresolves its panel off the data carrier, giving the clustering resultclr. - Read the cluster assignment of every asset into
idx. - Collect the asset indices of each of the
clr.kclusters intogroups. - Return the mask
groups_argbestadmits for those groups underscoresandbib.
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 vectorassets × 1.RedundancySelectorrejects anothingscore for this algorithm at construction, so it is nevernothinghere.bib:bigger_is_betterflag of the score.
Returns
keep::BitVector: Maskassets × 1that istruefor one asset of every cluster whose best score is not tied.
Related
PortfolioOptimisers.requires_score — Function
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:truewhen the algorithm cannot pick the survivor of a group without a score.
Related