The Asset Panel: private API

Types

PortfolioOptimisers.AllTrueMaskType
struct AllTrueMask <: AbstractMatrix{Bool}

Reads a universe mask that is true everywhere, storing no cell.

AllTrueMask is the shape a gapless ingestion emits for both of an AssetPanel's universe masks. Every asset is listed at every observation and every return is finite, so the mask is a constant, and storing it as one costs two integers instead of observations × assets bits. It answers size as (n, N) and getindex as true.

It is unexported and Base-only, exactly as PortfolioOptimisers.ListingSpan is: it owns size, getindex and show, and the public bound stays AbstractMatrix{Bool}. A method specialised on it is a library-internal fast path, not a contract an extension author may rely on. A view of it is an ordinary SubArray over a lazy matrix, so port_opt_view slices it correctly and still allocates no cell.

The library's usual spelling for "all of them" — a nothing Coverage Universe or Investable Mask — is unavailable here: nothing masks on an AssetPanel already mean the panel is static, and a second meaning on the same field would cost that reading.

Fields

  • n: Number of observations.
  • N: Number of assets.

Constructors

AllTrueMask(n::Integer, N::Integer) -> AllTrueMask

Validation

  • n >= 0 and N >= 0. Raises a DomainError.

Examples

julia> msk = PortfolioOptimisers.AllTrueMask(3, 2)AllTrueMask(3 × 2)julia> Matrix(msk)3×2 Matrix{Bool}: 1  1 1  1 1  1

Related

source
PortfolioOptimisers.RepeatedLeadingType
struct RepeatedLeading{T, N, A} <: AbstractArray{T, N}

Reads a static array as though it carried a leading observation axis, storing it once.

RepeatedLeading is the lazy lift: it is how a static Panel Field input joins a time-varying AssetPanel without copying itself T times. It stores the static array as parent and answers size as (n, size(parent)...), so R[t, i] reads parent[i] for every t. A view of it is an ordinary SubArray, which port_opt_view slices like any other value array.

It is unexported and Base-only: it owns size, getindex and show, and nothing else in the library dispatches on it.

Fields

  • parent: The static array, stored once.
  • n: Length of the leading observation axis.

Constructors

RepeatedLeading(parent::AbstractArray, n::Integer) -> RepeatedLeading

Related

source

Functions

PortfolioOptimisers.panel_axesFunction
panel_axes(pf::AbstractVector{<:AbstractPanelField},
           amsk::Option{<:AbstractMatrix{Bool}}) -> Tuple
panel_axes(pnl::AssetPanel) -> Tuple

Read the axes one AssetPanel is stated on.

The two universe masks are the panel's defining content and the Panel Fields are optional payload, so the axes are read from the fields when the panel has any and from the active mask when it has none. The ingestion layer's common case is the second: a caller holding only prices has no market capitalisation and no sector, and the panel it emits states a universe and nothing else.

A panel with neither a Panel Field nor a mask carries nothing at all, and is refused here rather than answering an empty tuple that every reader would then have to test.

Algorithm

The method that Julia selects reads the panel apart or whole; the rule is one.

  1. pf is non-empty: return panel_field_axes of its first Panel Field. The constructor has already checked that every field agrees.
  2. pf is empty: return size(amsk).
  3. An AssetPanel: read its own Panel Fields and active mask by steps 1 and 2. This is the form every consumer calls; the two-argument form is the constructor's, which has no panel yet.

Arguments

  • pf: The Panel Fields, possibly none.
  • amsk: The active mask, or nothing.
  • pnl: The Asset Panel, for the second form.

Validation

  • amsk is not nothing when pf is empty. Raises an IsEmptyError.

Returns

  • ax::Tuple: (assets,) for a static panel, and (observations, assets) for a time-varying one.

Related

source
PortfolioOptimisers.features_are_assetsFunction
features_are_assets(f::TensorPanelField, nx::Option{<:VecStr}) -> Bool
features_are_assets(f::AbstractPanelField, nx) -> Bool

Report whether one Panel Field's trailing axis is the asset axis, so a view must slice both.

True when a tensor Panel Field's labels equal the asset names, which is what a square phylogeny or adjacency matrix put on a carrier produces: an assets × assets block whose labels are "adjacent to asset $k$". Subselecting assets without also subselecting that axis would leave the columns pointing at the full universe while the rows point at the subset — a silently wrong distance rather than an error.

The fact is derived, never recorded. Comparing the names rather than the axis lengths is what makes it derivable: a rectangular-by-accident coincidence of counts is not a claim that the two axes mean the same thing, and the comparison stays correct under repeated views, since both name vectors are sliced by the same indices. A numeric or categorical Panel Field has no trailing axis, so it is never square.

Algorithm

The method that Julia selects is the algorithm.

  1. The field is not a tensor: return false.
  2. nx is nothing: return false. A carrier that does not name its assets makes no claim.
  3. Return f.labels == nx.

Arguments

  • f: The Panel Field.
  • nx: The carrier's asset names, or nothing.

Returns

  • Bool.

Related

source
PortfolioOptimisers.panel_onehotFunction
panel_onehot(f::CategoricalPanelField; datatype::DataType = Float64) -> Array

Build the one-hot block a categorical Panel Field contributes to a derived Feature Matrix.

One column per level, one(datatype) where the cell carries that level and zero(datatype) elsewhere: assets × levels for a static field, observations × assets × levels for a time-varying one. The codes are the storage and this is the matrix form, built only where a matrix is needed.

An indicator is built rather than read, so it carries no type of its own and the caller names the one the block is stacked beside. Every caller inside the library derives that type from the data it stacks the block against, so the default is what a caller who stacks the block against nothing gets. panel_field_stack! writes the same block into a Feature Matrix under construction without building it, and takes its type from the matrix.

Algorithm

  1. Allocate the zero array, the codes' shape with the levels appended.
  2. Write one(datatype) at each cell's own level.

Arguments

  • f: The categorical Panel Field.
  • datatype: The element type of the block.

Returns

  • H::Array{datatype}: The one-hot block.

Related

source
PortfolioOptimisers.panel_field_liftFunction
panel_field_lift(f::NumericPanelField, n::Integer) -> NumericPanelField
panel_field_lift(f::CategoricalPanelField, n::Integer) -> CategoricalPanelField
panel_field_lift(f::TensorPanelField, n::Integer) -> TensorPanelField

Lift a static Panel Field onto n observations, lazily.

A static input that meets a time-varying one, or that meets the two universe masks, joins the panel at the panel's observation count. The values are wrapped in a RepeatedLeading, which stores them once, and the observed mask is dropped: every cell of a static input was observed, so nothing is the mask that says so.

Algorithm

The method that Julia selects is the algorithm. Each kind rebuilds itself with its value array wrapped in a RepeatedLeading and its observed mask set to nothing.

Arguments

  • f: The static Panel Field.
  • n: Length of the observation axis to lift onto.

Returns

  • A Panel Field of the same kind, over n observations.

Related

source
PortfolioOptimisers.panel_value_eltypeFunction
panel_value_eltype(vals::AbstractArray) -> Type
panel_value_eltype(f::NumericPanelField) -> Type
panel_value_eltype(f::CategoricalPanelField) -> Type
panel_value_eltype(f::TensorPanelField) -> Type
panel_value_eltype(fs::AbstractVector{<:AbstractPanelField}) -> Type

Return the numeric type a Panel Field carries, or the one a Feature Matrix over several of them stacks into.

The type comes from the values, never from a written type. A raw field arrives as Union{Missing, Float64}, as Union{Nothing, Float64} or as Any, and none of those is the type its cells carry, so the cells are read and their types promoted. A Float32 field resolves in Float32, a blank-carrying Union{Missing, Float64} field in Float64, and an integer field stays exact.

A categorical Panel Field contributes nothing. An indicator is built rather than read, so it carries no type of its own and takes the type of the blocks it is stacked beside. A Feature Matrix that stacks indicators alone has nothing to derive from, and stacks in Float64.

Algorithm

The method that Julia selects is the algorithm.

  1. AbstractArray: the element type when it is already concrete, and otherwise the promotion of the types its own cells carry. An empty array carries no type, and gives Union{}.
  2. NumericPanelField and TensorPanelField: the type of their values.
  3. CategoricalPanelField: Union{}, which promotes away against every other type.
  4. A vector of Panel Fields: the promotion over the fields, and Float64 when none of them contributes a type.

Arguments

  • vals: The values.
  • f: The Panel Field.
  • fs: The Panel Fields a Feature Matrix stacks.

Returns

  • T::Type: The numeric type.

Related

source
PortfolioOptimisers.panel_field_observed_labelsFunction
panel_field_observed_labels(f::AbstractPanelField) -> Vector{String}

Return the names of the observed-mask columns one Panel Field contributes to a derived Feature Matrix.

A Panel Field with a single observable takes "<name>::observed". One with several takes each value column's own name with "::observed" appended, so a tensor Panel Field keeps one mask column per label.

The separator is "::" rather than the "=" the value columns use, so a mask column cannot be mistaken for a level of the same Panel Field.

Algorithm

  1. Read the Panel Field's value column names from panel_field_labels.
  2. When there is one, return the single name "<name>::observed".
  3. Otherwise append "::observed" to each of them.

Arguments

  • f: The Panel Field.

Returns

  • labels::Vector{String}: One name per observed-mask column.

Related

source
PortfolioOptimisers.panel_field_stack_observed!Function
panel_field_stack_observed!(Z::AbstractArray, f::AbstractPanelField, cols::VecInt) -> nothing

Write one Panel Field's observed mask into a derived Feature Matrix, as 0/1 columns.

Algorithm

  1. Return when the Panel Field carries no mask.
  2. Write the whole mask into the single column when the Panel Field claims one.
  3. Otherwise write one label slice of the mask per column.

Arguments

  • Z: The derived Feature Matrix under construction.
  • f: The Panel Field.
  • cols: The observed-mask columns the Panel Field claims, in order.

Returns

  • nothing.

Related

source
PortfolioOptimisers.panel_groups_viewFunction
panel_groups_view(::Nothing, j, sq::Bool) -> nothing
panel_groups_view(groups::VecStr, j, sq::Bool) -> VecStr

Return the groups of a tensor Panel Field under an asset view.

A group belongs to one label, so in the square case the groups are cut by the same asset index as the labels, and otherwise they are returned whole. A field with no groups answers nothing.

Arguments

  • groups: The groups of the tensor Panel Field, or nothing.
  • j: Asset index.
  • sq: Whether the field is in the square case, from features_are_assets.

Returns

  • The groups of the viewed field, or nothing.

Related

source
PortfolioOptimisers.panel_array_viewFunction
panel_array_view(A::Nothing, i, j) -> nothing
panel_array_view(A::AbstractVector, i, j) -> SubArray
panel_array_view(A::AbstractMatrix, i, j) -> SubArray

View one label-free Panel Field array over the observations i and the assets j.

A NumericPanelField and a CategoricalPanelField carry no label axis, so the rank alone says which axes they have: a vector is static and its one axis is the assets, and a matrix is time-varying and its axes are the observations and the assets. A tensor Panel Field is viewed by panel_tensor_view instead, because its asset axis is not the last one.

Algorithm

The method that Julia selects is the algorithm.

  1. A is nothing: return nothing.
  2. A is a vector: return view(A, j).
  3. A is a matrix: return view(A, i, j).

Arguments

  • A: The array to view, or nothing.
  • i: Observation index.
  • j: Asset index.

Returns

  • A view of A, or nothing.

Related

source
PortfolioOptimisers.panel_tensor_viewFunction
panel_tensor_view(A::Nothing, i, j, k) -> nothing
panel_tensor_view(A::AbstractMatrix, i, j, k) -> SubArray
panel_tensor_view(A::AbstractArray{<:Any, 3}, i, j, k) -> SubArray

View one TensorPanelField array over the observations i, the assets j and the labels k.

A tensor Panel Field keeps its labels on its trailing axis, so its asset axis is the first one when it is static and the second when it is time-varying. The label index is a Colon for every field but the square one, whose labels are the assets; features_are_assets states when that holds.

Algorithm

The method that Julia selects is the algorithm.

  1. A is nothing: return nothing.
  2. A is a matrix, which is assets × labels: return view(A, j, k).
  3. A is a 3-dimensional array, which is observations × assets × labels: return view(A, i, j, k).

Arguments

  • A: The array to view, or nothing.
  • i: Observation index.
  • j: Asset index.
  • k: Label index.

Returns

  • A view of A, or nothing.

Related

source
PortfolioOptimisers.panel_mask_viewFunction
panel_mask_view(msk::Nothing, i, j) -> nothing
panel_mask_view(msk::AbstractMatrix{Bool}, i, j) -> SubArray

View one universe mask of an AssetPanel over the observations i and the assets j.

Algorithm

The method that Julia selects is the algorithm. A static panel carries no mask, so there is nothing to view.

Arguments

  • msk: The mask, or nothing.
  • i: Observation index.
  • j: Asset index.

Returns

  • A view of msk, or nothing.

Related

source
PortfolioOptimisers.panel_claim!Function
panel_claim!(nz::AbstractVector{String}, labels::AbstractVector{String}) -> Vector{Int}

Append a Panel Field's column names to a derived Feature Matrix's names, and return the columns they took.

The one place a derived column index is minted, so the names and the write cannot disagree about where a Panel Field's columns are.

Algorithm

  1. Read the current length of nz, which is the last column already claimed.
  2. Append labels to it.
  3. Return the range of columns the append occupied, as a vector.

Arguments

  • nz: The derived column names under construction. It is appended to.
  • labels: The column names to claim.

Returns

  • cols::Vector{Int}: The columns labels took, in order.

Related

source
PortfolioOptimisers.check_asset_panelFunction
check_asset_panel(pnl::Nothing, na, nobs, na_sym) -> nothing
check_asset_panel(pnl::AssetPanel, na, nobs, na_sym) -> nothing

Check an AssetPanel against the asset and observation axes of the carrier that holds it.

The panel owns its own values, so this is the only check a carrier owes it: that the universe it describes is the carrier's universe.

Algorithm

The method that Julia selects is the algorithm.

  1. pnl is nothing: the carrier has no panel, so there is nothing to check.
  2. pnl is an AssetPanel: read its shape from panel_axes, check the asset axis against na, and check the observation axis against nobs when the panel is time-varying.

Arguments

  • pnl: The Asset Panel, or nothing.
  • na: Asset count of the carrier.
  • nobs: Observation count of the carrier.
  • na_sym: Symbolic name of the asset axis, displayed in the error messages.

Validation

  • na is not nothing. Raises an IsNothingError.
  • The panel's asset axis is na. Raises a DimensionMismatch.
  • nobs is not nothing and matches the panel's observation axis, when the panel is time-varying. Raises an IsNothingError or a DimensionMismatch.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_panel_labelsFunction
assert_panel_labels(labels::VecStr, sym::Sym_Str) -> nothing

Check that a label vector is non-empty, holds no empty entry, and holds no repeat.

Shared by every name vector a panel carries: the levels of a CategoricalPanelField, the labels of a TensorPanelField, and the Panel Field names of an AssetPanel. Each names a column of a derived Feature Matrix, or a Panel Field a consumer looks up, so a repeat makes one name mean two things.

Algorithm

  1. Check that labels is not empty.
  2. Check that no entry is the empty string, naming the first offending position.
  3. Check that no entry repeats, naming the first repeated position.

Arguments

  • labels: The label vector to check.
  • sym: Symbolic name of the vector, displayed in the error messages.

Validation

  • !isempty(labels). Raises an IsEmptyError.
  • No entry is empty. Raises an ArgumentError.
  • allunique(labels). Raises an ArgumentError.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_panel_field_shapeFunction
assert_panel_field_shape(vals::AbstractArray, name::AbstractString, s::Integer, t::Integer) -> nothing

Check that a Panel Field's values are non-empty and carry the static or the time-varying rank.

A Panel Field takes one of two ranks, and which pair of ranks it takes depends on its kind: s is the static rank and t the time-varying one. The time-varying rank is the static one with the observation axis prepended, so the two always differ by one.

Algorithm

  1. Check that vals is not empty.
  2. Check that ndims(vals) is s or t.

Arguments

  • vals: The Panel Field's values.
  • name: The Panel Field's name, displayed in the error messages.
  • s: The static rank.
  • t: The time-varying rank.

Validation

  • !isempty(vals). Raises an IsEmptyError.
  • ndims(vals) in (s, t). Raises a DimensionMismatch.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_panel_field_maskFunction
assert_panel_field_mask(vals::AbstractArray, omsk::Nothing, name::AbstractString) -> nothing
assert_panel_field_mask(vals::AbstractArray, omsk::AbstractArray{Bool}, name::AbstractString) -> nothing

Check that a Panel Field's observed mask covers its values entry for entry.

The mask says which cells the raw source observed, and which a fill policy wrote. It is therefore the same shape as the values, down to a tensor Panel Field's label axis, which keeps the per-entry resolution the raw input carried.

Algorithm

The method that Julia selects is the algorithm.

  1. omsk is nothing: the Panel Field cannot blank, so there is nothing to check.
  2. omsk is an array: check that its size matches the values.

Arguments

  • vals: The Panel Field's values.
  • omsk: The observed mask, or nothing.
  • name: The Panel Field's name, displayed in the error message.

Validation

  • size(omsk) == size(vals). Raises a DimensionMismatch.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_panel_masksFunction
assert_panel_masks(ax::Tuple, amsk::Nothing, emsk::Nothing) -> nothing
assert_panel_masks(ax::Tuple, amsk, emsk) -> nothing

Check an Asset Panel's two universe masks against the shape its Panel Fields agreed on.

The masks are the one thing that says whether a panel is static: they are nothing if and only if its Panel Fields carry no observation axis. That rule is what makes the static shape a type parameter rather than a runtime branch, so a mask consumer dispatches on AssetPanel{PF, Nothing, Nothing} and never tests.

Algorithm

The method that Julia selects is the algorithm.

  1. Both masks are nothing: check that the Panel Fields are static, that is, that ax names one axis.
  2. Otherwise: check that both masks are given, that the Panel Fields are time-varying, that both masks are ax, and that emsk is a subset of amsk.

Arguments

  • ax: The observation and asset axes the Panel Fields agreed on.
  • amsk: The active mask, or nothing.
  • emsk: The estimation mask, or nothing.

Validation

  • length(ax) == 1 when the masks are nothing, and length(ax) == 2 otherwise. Raises a DimensionMismatch.
  • The masks are both nothing or both given. Raises a DimensionMismatch.
  • size(amsk) == size(emsk) == ax. Raises a DimensionMismatch.
  • emsk is a subset of amsk. Raises an ArgumentError.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_panel_finiteFunction
assert_panel_finite(vals::AbstractArray{<:Real}, name::AbstractString) -> nothing

Check that a resolved Panel Field carries no non-finite value.

The fill policies each write a finite value by construction, so this catches a non-finite cell that the raw input carried and that no policy touched: an infinity is not a blank, so is_panel_blank leaves it where it stands.

Algorithm

  1. Find the first non-finite cell.
  2. Throw when there is one, naming the Panel Field and the cell.

Arguments

  • vals: The resolved values.
  • name: The Panel Field's name, displayed in the error message.

Validation

Returns

  • nothing.

Related

source