Cross-sectional transforms: private API
Types
PortfolioOptimisers.CS_MISSING_GROUP — Constant
const CS_MISSING_GROUP = -1The group label that says an asset carries no group at an observation.
A cell labelled this way never joins a group statistic, and it takes the whole-row statistics instead. Every other label is a group of its own, so the labels need not be contiguous and need not repeat between observations.
Related
PortfolioOptimisers.CS_MAD_CONSISTENCY — Constant
const CS_MAD_CONSISTENCY = 1.4826022185056018The factor that makes a median absolute deviation consistent with a standard deviation under normality.
It is the reciprocal of the third quartile of the standard normal distribution, so a normal sample's scaled median absolute deviation estimates the same quantity its standard deviation does.
Related
Functions
PortfolioOptimisers.assert_cross_sectional_matrix — Function
assert_cross_sectional_matrix(X::MatNum) -> nothingCheck that a cross-sectional data matrix is non-empty and holds no infinite cell.
A NaN is the marker for a missing cell, so it is admitted and preserved. An infinity is neither a value nor a marker: it survives a quantile, a median and a mean, and it turns every statistic of its observation into an infinity or a NaN without saying why.
Algorithm
- Refuse an empty matrix.
- Find the first cell that is neither finite nor
NaN, and refuse it by name.
Arguments
X: Data matrixobservations × assets.
Validation
!isempty(X). Raises anIsEmptyError.- Every cell of
Xis finite orNaN. Raises aDomainErrornaming the observation and the asset.
Returns
nothing.
Related
PortfolioOptimisers.assert_cross_sectional_weights — Function
assert_cross_sectional_weights(X::MatNum, w::Nothing) -> nothing
assert_cross_sectional_weights(X::MatNum, w::MatNum) -> nothingCheck a benchmark weight matrix against the data matrix it selects the estimation set of.
A weight is a selector first and a weight second: a positive weight puts the cell in the estimation set of its observation, and a zero weight leaves it out. A NaN weight therefore says neither, and a negative weight says less than nothing.
Algorithm
- Return when
wisnothing, because the estimation set is then the finite cells alone. - Check the shape against
X, then the finiteness and the sign of every weight.
Arguments
X: Data matrixobservations × assets.w: Benchmark weight matrixobservations × assets, ornothing.
Validation
size(w) == size(X). Raises aDimensionMismatch.- Every weight is finite. Raises an
IsNonFiniteError. - Every weight is non-negative. Raises a
DomainError.
Returns
nothing.
Related
PortfolioOptimisers.assert_cross_sectional_groups — Function
assert_cross_sectional_groups(X::MatNum, groups::Nothing) -> nothing
assert_cross_sectional_groups(X::MatNum, groups::AbstractMatrix{<:Integer}) -> nothingCheck a group label matrix against the data matrix it partitions.
A label is an identity, not a quantity, so only CS_MISSING_GROUP carries a meaning of its own. A label below it names no group and no missing cell, so it would silently join a partition of its own.
Algorithm
- Return when
groupsisnothing, because every cell then takes the whole-row statistics. - Check the shape against
X, then that no label sits belowCS_MISSING_GROUP.
Arguments
X: Data matrixobservations × assets.groups: Group label matrixobservations × assets, ornothing.
Validation
size(groups) == size(X). Raises aDimensionMismatch.- Every label is at least
CS_MISSING_GROUP. Raises aDomainError.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_groups_observed! — Function
cross_sectional_groups_observed!(G::AbstractMatrix{Int}, omsk::Nothing) -> nothing
cross_sectional_groups_observed!(G::AbstractMatrix{Int}, omsk::AbstractMatrix{Bool}) -> nothingWrite CS_MISSING_GROUP into every group label whose cell a fill policy wrote, in place.
A categorical Panel Field with no observed mask carried no blank, so there is nothing to undo and the method over nothing returns at once.
Arguments
G: Group label matrixobservations × assets, the copied codes of the Panel Field.omsk: The Panel Field's observed mask, ornothing.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_estimation_mask — Function
cross_sectional_estimation_mask(fin::AbstractMatrix{Bool}, w::Nothing) -> AbstractMatrix{Bool}
cross_sectional_estimation_mask(fin::AbstractMatrix{Bool}, w::MatNum) -> AbstractMatrix{Bool}Return the estimation mask of every observation of a cross-sectional transform.
The estimation set of an observation is what its statistics are computed from. Without benchmark weights it is the finite cells, and with them it is the finite cells carrying a positive weight. A cell outside the set is still transformed against it, so the mask selects the estimator and never the output.
Algorithm
- Return the finiteness mask when
wisnothing. - Otherwise intersect it with the cells of positive weight.
Arguments
fin::AbstractMatrix{Bool}: Finiteness maskobservations × assets.w: Benchmark weight matrixobservations × assets, ornothing.
Returns
est::AbstractMatrix{Bool}: Estimation maskobservations × assets. It isfinitself whenwisnothing, and neither mask is written to.
Related
PortfolioOptimisers.cross_sectional_indices — Function
cross_sectional_indices(msk::AbstractMatrix{Bool}, t::Integer) -> Vector{Int}Return the asset indices an observations × assets mask selects at one observation.
Arguments
msk::AbstractMatrix{Bool}: Maskobservations × assets.t::Integer: Observation.
Returns
idx::Vector{Int}: Selected asset indices, in ascending order.
Examples
julia> PortfolioOptimisers.cross_sectional_indices([true false true; false true false], 1)2-element Vector{Int64}: 1 3Related
PortfolioOptimisers.cross_sectional_weight_type — Function
cross_sectional_weight_type(w::Nothing) -> Type
cross_sectional_weight_type(w::MatNum) -> TypeReturn the element type a benchmark weight matrix contributes to the output of a transform.
An absent weight matrix contributes Bool, which promotes against every numeric type without widening it, so the caller needs no branch of its own.
Arguments
w: Benchmark weight matrixobservations × assets, ornothing.
Returns
T::Type:Boolwhenwisnothing, andeltype(w)otherwise.
Related
PortfolioOptimisers.cross_sectional_weighted_mean — Function
cross_sectional_weighted_mean(A::AbstractMatrix, w::Nothing, t::Integer, idx::AbstractVector{<:Integer})
cross_sectional_weighted_mean(A::AbstractMatrix, w::MatNum, t::Integer, idx::AbstractVector{<:Integer})Return the centre of one observation's estimation set.
The centre is weighted when benchmark weights are given, so a large benchmark holding pulls it towards itself, and it is the plain mean otherwise.
Arguments
A::AbstractMatrix: Data matrixobservations × assets, already floating point.w: Benchmark weight matrixobservations × assets, ornothing.t::Integer: Observation.idx::AbstractVector{<:Integer}: Estimation set of the observation, which must not be empty.
Returns
mu::Number: The centre, in the element type ofA. It is zero when every weight of the estimation set is zero, which the estimation set's own definition excludes.
Related
PortfolioOptimisers.cross_sectional_equal_std — Function
cross_sectional_equal_std(A::AbstractMatrix, t::Integer, idx::AbstractVector{<:Integer}, mu::Number)Return the equal-weighted dispersion of one observation's estimation set around a centre.
The scale is equal-weighted even where the centre is weighted, because a benchmark weight says how much of the market an asset is and not how precisely its value is measured. The divisor is the sample size less one.
Arguments
A::AbstractMatrix: Data matrixobservations × assets, already floating point.t::Integer: Observation.idx::AbstractVector{<:Integer}: Estimation set of the observation.mu::Number: Centre to measure the dispersion around.
Returns
sigma::Number: The dispersion, in the element type ofA. It is zero when the estimation set holds fewer than two assets, and every consumer then reads the cell as having no dispersion rather than an undefined one.
Related
PortfolioOptimisers.cross_sectional_stat — Function
cross_sectional_stat(v::AbstractVector, i::Integer) -> Number
cross_sectional_stat(v::Number, i::Integer) -> NumberRead the statistic that applies to one asset, whether it is per asset or shared by the observation.
An ungrouped transform holds one centre and one scale for the whole observation, and a grouped one holds a pair per asset. This reader lets one scoring loop serve both.
Arguments
v: Statistic, either one number per asset or one number for the observation.i::Integer: Asset.
Returns
val::Number: The statistic of asseti.
Related
PortfolioOptimisers.cross_sectional_blank_row! — Function
cross_sectional_blank_row!(Y::AbstractMatrix, t::Integer) -> nothingWrite a missing marker into every asset of one observation.
An observation whose estimation set is empty has nothing to be transformed against, so no cell of it carries a value, not even a cell that was finite.
Arguments
Y::AbstractMatrix: Output matrixobservations × assets.t::Integer: Observation.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_zscore_row! — Function
cross_sectional_zscore_row!(Y::AbstractMatrix, A::AbstractMatrix, fin::AbstractMatrix{Bool}, t::Integer, idx::AbstractVector{<:Integer}, mu, sigma, atol::Real) -> nothingScore one observation against a centre and a scale.
An observation with no dispersion scores every cell zero rather than dividing by it, so a cross-section that carries the same value everywhere reads as a neutral exposure instead of a missing one.
Algorithm
- Blank the whole observation when its estimation set is empty.
- Otherwise write a missing marker at every non-finite cell.
- Write the centred and rescaled value where the scale is above
atol, and zero where it is not.
Arguments
Y::AbstractMatrix: Output matrixobservations × assets.A::AbstractMatrix: Data matrixobservations × assets, already floating point.fin::AbstractMatrix{Bool}: Finiteness maskobservations × assets.t::Integer: Observation.idx::AbstractVector{<:Integer}: Estimation set of the observation.mu: Centre, one number for the observation or one per asset.sigma: Scale, one number for the observation or one per asset.atol::Real:atol: Absolute tolerance below which a cross-sectional scale counts as zero. An observation at or below it carries no dispersion, so its finite cells score zero rather than dividing by that scale.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_recentre_rescale! — Function
cross_sectional_recentre_rescale!(Y::AbstractMatrix, fin::AbstractMatrix{Bool}, est::AbstractMatrix{Bool}, w::Option{<:MatNum}, atol::Real, scale::Bool) -> nothingRecentre, and optionally rescale, an already scored matrix over the whole cross-section.
A grouped score is comparable inside its group and not between groups, so the score is brought back to a weighted centre of zero over the whole observation, and to a unit equal-weighted scale when the caller asks for one.
Algorithm
- Blank an observation whose estimation set is empty.
- Subtract the weighted centre of the estimation set from every finite cell.
- Return when
scaleisfalse. - Divide by the equal-weighted scale of the estimation set, writing zero where that scale is at or below
atol.
Arguments
Y::AbstractMatrix: Score matrixobservations × assets, written in place.fin::AbstractMatrix{Bool}: Finiteness maskobservations × assetsof the matrix the score came from.est::AbstractMatrix{Bool}: Estimation maskobservations × assets.w: Benchmark weight matrixobservations × assets, ornothing.atol::Real:atol: Absolute tolerance below which a cross-sectional scale counts as zero. An observation at or below it carries no dispersion, so its finite cells score zero rather than dividing by that scale.scale::Bool: Whether to divide by the equal-weighted scale.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_midranks! — Function
cross_sectional_midranks!(P::AbstractMatrix, A::AbstractMatrix, t::Integer, idx::AbstractVector{<:Integer}, qry::AbstractVector{<:Integer}) -> nothingWrite the percentile rank of each queried asset against one estimation set.
A tie shares the average of the ranks its members would otherwise occupy, and the rank is centred inside its bin, so a percentile sits strictly inside the open unit interval and an inverse normal of it is always finite.
Algorithm
- Write a missing marker at every queried asset when the estimation set is empty.
- Otherwise sort the estimation values once.
- For each queried asset, count the estimation values strictly below it and those at or below it, and average the two counts.
- Divide by the size of the estimation set, and clamp into the closed interval between half a bin and one less half a bin.
Arguments
P::AbstractMatrix: Percentile matrixobservations × assets, written in place.A::AbstractMatrix: Data matrixobservations × assets, already floating point.t::Integer: Observation.idx::AbstractVector{<:Integer}: Estimation set to rank against.qry::AbstractVector{<:Integer}: Assets to rank.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_rank_counts — Function
cross_sectional_rank_counts(v::AbstractVector, x::Number) -> Tuple{Int, Int}Return how many entries of a sorted vector sit below a value, and how many sit at or below it.
The pair is what a midrank needs: their average is the rank a tie shares. Two binary searches read it in logarithmic time, and the second starts where the first stopped, because a value at or below x is never below one that is below it.
The searches are written out rather than taken from Base, because a loaded dependency adds its own methods to searchsortedfirst and searchsortedlast, and the static analysis gate reads those arms and refuses the call.
Algorithm
- Binary search for the largest prefix of
vwhose entries are all belowx. - Binary search again, from that prefix to the end, for the largest prefix whose entries are all at or below
x.
Arguments
v::AbstractVector: Values to count against, sorted in ascending order.x::Number: Value to count around.
Returns
nlt::Int: Number of entries strictly belowx.nle::Int: Number of entries at or belowx.
Examples
julia> PortfolioOptimisers.cross_sectional_rank_counts([1.0, 2.0, 2.0, 3.0], 2.0)(1, 3)julia> PortfolioOptimisers.cross_sectional_rank_counts([1.0, 2.0, 2.0, 3.0], 0.5)(0, 0)Related
PortfolioOptimisers.cross_sectional_row_groups — Function
cross_sectional_row_groups(est::AbstractMatrix{Bool}, groups::AbstractMatrix{<:Integer}, t::Integer) -> Dict{Int, Vector{Int}}Return the estimation set of each group of one observation.
A cell labelled CS_MISSING_GROUP joins no group, so it contributes to no group statistic while still belonging to the observation's own estimation set.
Arguments
est::AbstractMatrix{Bool}: Estimation maskobservations × assets.groups::AbstractMatrix{<:Integer}: Group label matrixobservations × assets.t::Integer: Observation.
Returns
gidx::Dict{Int, Vector{Int}}: Estimation asset indices of each label present at the observation, in ascending order.
Related
PortfolioOptimisers.cross_sectional_group_split — Function
cross_sectional_group_split(fin::AbstractMatrix{Bool}, groups::AbstractMatrix{<:Integer}, t::Integer, gidx::Dict{Int, Vector{Int}}, mgs::Integer)Split the finite assets of one observation into the groups that stand and the ones that fall back.
A group stands when its own estimation set is large enough to estimate from. Every other finite asset takes the whole-row statistics, which is what the fallback list carries.
Algorithm
- Walk the finite assets of the observation.
- Send an asset whose label is
CS_MISSING_GROUP, or whose group holds fewer thanmgsestimation assets, to the fallback list. - Send every other asset to the query list of its own group.
Arguments
fin::AbstractMatrix{Bool}: Finiteness maskobservations × assets.groups::AbstractMatrix{<:Integer}: Group label matrixobservations × assets.t::Integer: Observation.gidx::Dict{Int, Vector{Int}}: Estimation set of each group of the observation.mgs::Integer:min_group_size: Smallest estimation set a group may carry and still be estimated from. A group below it, and every asset that carries no group, takes the whole observation's statistics instead.
Returns
qry::Dict{Int, Vector{Int}}: Finite assets of each group that stands.fb::Vector{Int}: Finite assets that take the whole-row statistics.
Related
PortfolioOptimisers.cross_sectional_percentile_ranks — Function
cross_sectional_percentile_ranks(A::AbstractMatrix, fin::AbstractMatrix{Bool}, est::AbstractMatrix{Bool}, groups::Nothing, mgs::Integer)
cross_sectional_percentile_ranks(A::AbstractMatrix, fin::AbstractMatrix{Bool}, est::AbstractMatrix{Bool}, groups::AbstractMatrix{<:Integer}, mgs::Integer)Return the percentile rank of every finite cell of a matrix, against its own cross-section.
Without group labels a cell is ranked against the whole estimation set of its observation. With them it is ranked inside its own group, and it falls back to the whole estimation set when its group is too small to rank inside.
Algorithm
- Walk the observations.
- Rank every finite asset against the estimation set of the observation when no labels are given.
- Otherwise split the finite assets into the groups that stand and the ones that fall back, rank each standing group against its own estimation set, and rank the fallback list against the whole estimation set.
Arguments
A::AbstractMatrix: Data matrixobservations × assets, already floating point.fin::AbstractMatrix{Bool}: Finiteness maskobservations × assets.est::AbstractMatrix{Bool}: Estimation maskobservations × assets.groups: Group label matrixobservations × assets, ornothing.mgs::Integer:min_group_size: Smallest estimation set a group may carry and still be estimated from. A group below it, and every asset that carries no group, takes the whole observation's statistics instead.
Returns
P::Matrix{<:AbstractFloat}: Percentile matrixobservations × assets, carrying a missing marker at every cell that is not finite and at every cell whose estimation set is empty.
Related
PortfolioOptimisers.cross_sectional_standardise! — Function
cross_sectional_standardise!(Y::AbstractMatrix, A::AbstractMatrix, fin::AbstractMatrix{Bool}, est::AbstractMatrix{Bool}, w::Option{<:MatNum}, groups::Nothing, mgs::Integer, atol::Real) -> nothing
cross_sectional_standardise!(Y::AbstractMatrix, A::AbstractMatrix, fin::AbstractMatrix{Bool}, est::AbstractMatrix{Bool}, w::Option{<:MatNum}, groups::AbstractMatrix{<:Integer}, mgs::Integer, atol::Real) -> nothingScore a matrix as cross-sectional z-scores, with or without groups.
Without group labels one centre and one scale serve the whole observation. With them each group that stands carries its own pair, every other asset takes the observation's pair, and the scores are recentred and rescaled over the whole observation afterwards.
Algorithm
- Walk the observations, and take the estimation set of each.
- Compute the observation's own centre and scale.
- Without group labels, score every finite cell against that pair and stop.
- With them, compute the pair of each group that stands, give every other finite cell the observation's pair, score, and then recentre and rescale over the whole observation.
Arguments
Y::AbstractMatrix: Output matrixobservations × assets.A::AbstractMatrix: Data matrixobservations × assets, already floating point.fin::AbstractMatrix{Bool}: Finiteness maskobservations × assets.est::AbstractMatrix{Bool}: Estimation maskobservations × assets.w: Benchmark weight matrixobservations × assets, ornothing.groups: Group label matrixobservations × assets, ornothing.mgs::Integer:min_group_size: Smallest estimation set a group may carry and still be estimated from. A group below it, and every asset that carries no group, takes the whole observation's statistics instead.atol::Real:atol: Absolute tolerance below which a cross-sectional scale counts as zero. An observation at or below it carries no dispersion, so its finite cells score zero rather than dividing by that scale.
Returns
nothing.
Related
PortfolioOptimisers.cross_sectional_cell_stats — Function
cross_sectional_cell_stats(A::AbstractMatrix, w::Option{<:MatNum}, t::Integer, groups::AbstractMatrix{<:Integer}, gidx::Dict{Int, Vector{Int}}, mgs::Integer, mu, sigma)Return the centre and the scale that apply to each asset of one grouped observation.
An asset whose group stands takes its group's pair, and every other asset takes the observation's own pair. Building both vectors first keeps the scoring loop free of the fallback rule.
Algorithm
- Compute the pair of each group holding at least
mgsestimation assets. - Fill both vectors with the observation's pair.
- Overwrite the entries of every asset whose group stands.
Arguments
A::AbstractMatrix: Data matrixobservations × assets, already floating point.w: Benchmark weight matrixobservations × assets, ornothing.t::Integer: Observation.groups::AbstractMatrix{<:Integer}: Group label matrixobservations × assets.gidx::Dict{Int, Vector{Int}}: Estimation set of each group of the observation.mgs::Integer:min_group_size: Smallest estimation set a group may carry and still be estimated from. A group below it, and every asset that carries no group, takes the whole observation's statistics instead.mu: Centre of the whole observation.sigma: Scale of the whole observation.
Returns
M::Vector{<:Number}: Centre of each asset.S::Vector{<:Number}: Scale of each asset.
Related