The Asset Panel

The Asset Panel

A point-in-time panel of per-asset fields — market capitalisation, a sector classification, a factor exposure tensor — is what ReturnsResult and PricesResult carry in their pnl slot. Each Panel Field owns its own values and its own observed mask, so the panel is the feature data: no carrier holds a feature matrix beside it, and the Feature Matrix a distance measures is derived by feature_matrix and stored nowhere.

A Feature Selector says which Panel Fields the matrix stacks. An entry names one Panel Field, one field with the levels or labels it keeps, one field with a single level or label, or one field's observed mask. feature_labels names each resulting column with the entry that selects exactly it, so a label vector is itself a selector that rebuilds the same matrix.

A panel takes one of two shapes. A static panel indexes its Panel Fields by asset alone and carries no universe mask; a time-varying panel prepends an observation axis and carries both. The shape rides the type parameters, so a mask consumer dispatches rather than branches.

A blank cell never reaches a carrier. asset_panel resolves every one of them, so every Panel Field comes out finite, and each Panel Field that can blank carries the observed mask that says which cells the resolution touched.

The library persists no panel of its own, and it needs no format to: panel_dataframe renders a panel as a DataFrames.DataFrame, and a caller writes that with whatever they already use. One Panel Field name gives that field laid out as it stands, a :long layout gives one row per (observation, asset) filtered by the active mask, and a :wide layout gives one column per (Panel Field column, asset) and keeps every cell. A TensorPanelField spreads into one column per trailing-axis label there, under the same "<field>=<label>" name it takes in a Feature Matrix.

Types

PortfolioOptimisers.AbstractPanelFieldType
abstract type AbstractPanelField <: AbstractResult

Supertype of the Panel Fields an AssetPanel holds.

All concrete types that carry one Panel Field's name, its values and its observed mask should subtype AbstractPanelField.

A Panel Field owns its values. Nothing else holds them, and nothing needs an index to find them: an AssetPanel is a vector of Panel Fields, and panel_field looks one up by name. A Panel Field is static or time-varying, and every Panel Field of one panel agrees.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractPanelField and implement the following methods:

panel_field_axes

  • panel_field_axes(f::AbstractPanelField) -> Tuple{Vararg{Int}}: (N,) when the Panel Field is static, and (T, N) when it is time-varying.

panel_field_labels

  • panel_field_labels(f::AbstractPanelField) -> Vector{String}: The column names the Panel Field contributes to a derived Feature Matrix, in column order.

panel_field_stack!

  • panel_field_stack!(Z::AbstractArray, f::AbstractPanelField, cols::VecInt) -> nothing: Write the Panel Field's value columns into cols of Z.

panel_field_view

  • panel_field_view(f::AbstractPanelField, i, j) -> AbstractPanelField: The Panel Field over the observations i and the assets j.

Related

source
PortfolioOptimisers.AssetPanelType
struct AssetPanel{__T_pf, __T_amsk, __T_emsk} <: AbstractResult

The Asset Panel: the Panel Fields of one universe, and the two point-in-time universe masks.

The two universe masks are the panel's defining content, and the Panel Fields are optional payload. A panel with fields owns their values, so nothing else on a carrier holds a feature matrix, and the Feature Matrix a distance measures is derived from the panel by panel_feature_matrix and stored nowhere. A panel with no field is the ingestion layer's common case: a caller holding only prices has no market capitalisation and no sector, and the panel states a universe and nothing else. A panel with neither a field nor a mask carries nothing at all and is refused.

One panel takes one of two shapes, and its type parameters say which.

  • Static: every Panel Field is assets or assets × labels, and both masks are nothing. A fundamentals table or a sector classification with no history is this shape. It needs at least one Panel Field, because nothing else would state its asset axis.
  • Time-varying: every Panel Field prepends an observation axis, and both masks are observations × assets. A point-in-time panel is this shape.

nothing masks therefore read as static, or hand-built, and never as gapless: a panel the ingestion layer emits always carries both, all true where the price table held no gap.

Fields

  • pf: The Panel Fields, each owning its own values and its own observed mask.
  • amsk: The active mask (observations × assets): whether the asset is in the universe at that observation. nothing when the panel is static.
  • emsk: The estimation mask (observations × assets): whether the asset enters the cross-sectional estimate at that observation. Always a subset of the active mask. nothing when the panel is static.

Constructor

AssetPanel(pf::AbstractVector{<:AbstractPanelField},           amsk::Option{<:AbstractMatrix{Bool}} = nothing,           emsk::Option{<:AbstractMatrix{Bool}} = nothing)

Validation

  • The panel carries at least one Panel Field or an active mask. See panel_axes.
  • The Panel Field names are non-empty and unique. See assert_panel_labels.
  • Every Panel Field shares one panel_field_axes. Raises a DimensionMismatch.
  • The masks are both nothing when the Panel Fields are static, and both given when they are time-varying. See assert_panel_masks.

Related

source
PortfolioOptimisers.NumericPanelFieldType
struct NumericPanelField{__T_name, __T_vals, __T_omsk} <: AbstractPanelField

A Panel Field holding one number per asset, and per observation when it is time-varying.

A market capitalisation, a book-to-price ratio or a trailing volume is this kind. It contributes one column to a derived Feature Matrix, named after the Panel Field itself.

Fields

  • name: The Panel Field's name, which names its column of a derived Feature Matrix.
  • vals: Values: assets when static, observations × assets when time-varying.
  • omsk: Observed mask, the same size as the values, or nothing when the Panel Field cannot blank.

Constructor

NumericPanelField(name::AbstractString, vals::AbstractArray{<:Real},                  omsk::Option{<:AbstractArray{Bool}} = nothing)

Validation

  • !isempty(name). Raises an IsEmptyError.
  • !isempty(vals). Raises an IsEmptyError.
  • ndims(vals) in (1, 2). Raises a DimensionMismatch.
  • size(omsk) == size(vals) when omsk is given. Raises a DimensionMismatch.

Related

source
PortfolioOptimisers.CategoricalPanelFieldType
struct CategoricalPanelField{__T_name, __T_levels, __T_codes, __T_omsk} <: AbstractPanelField

A Panel Field holding one category label per asset, and per observation when it is time-varying.

A sector, an industry or a country classification is this kind. It stores integer codes over its levels rather than the labels themselves, and rather than a one-hot block: a code is what a cross-sectional group label is, and the one-hot form is built where a matrix is needed. It contributes one column per level to a derived Feature Matrix, named "<field>=<level>".

Fields

  • name: The Panel Field's name, which prefixes each of its columns of a derived Feature Matrix.
  • levels: The category levels, one per derived column, in column order.
  • codes: Integer codes over levels: assets when static, observations × assets when time-varying.
  • omsk: Observed mask, the same size as the codes, or nothing when the Panel Field cannot blank.

Constructor

CategoricalPanelField(name::AbstractString, levels::VecStr,                      codes::AbstractArray{<:Integer},                      omsk::Option{<:AbstractArray{Bool}} = nothing)

Validation

  • !isempty(name). Raises an IsEmptyError.
  • levels is non-empty, holds no empty entry and holds no repeat. See assert_panel_labels.
  • !isempty(codes). Raises an IsEmptyError.
  • ndims(codes) in (1, 2). Raises a DimensionMismatch.
  • Every code lies in 1:length(levels). Raises a DomainError.
  • size(omsk) == size(codes) when omsk is given. Raises a DimensionMismatch.

Related

source
PortfolioOptimisers.TensorPanelFieldType
struct TensorPanelField{__T_name, __T_axis, __T_labels, __T_groups, __T_vals, __T_omsk} <: AbstractPanelField

A Panel Field whose trailing axis carries its own labels, and optionally its own groups.

A factor exposure tensor is this kind: its trailing axis is the factors, and its groups are the Factor Families. It contributes one column per label to a derived Feature Matrix, named "<field>=<label>".

Fields

  • name: The Panel Field's name, which prefixes each of its columns of a derived Feature Matrix.
  • axis: Name of what the trailing axis represents, such as "factor".
  • labels: Labels of the trailing-axis entries, one per trailing-axis entry of the values.
  • groups: Optional group of each trailing-axis entry, such as a Factor Family, one per label.
  • vals: Values: assets × labels when static, observations × assets × labels when time-varying.
  • omsk: Observed mask, the same size as the values, or nothing when the Panel Field cannot blank.

Constructor

TensorPanelField(name::AbstractString, axis::AbstractString, labels::VecStr,                 groups::Option{<:VecStr}, vals::AbstractArray{<:Real},                 omsk::Option{<:AbstractArray{Bool}} = nothing)

Validation

  • !isempty(name). Raises an IsEmptyError.
  • !isempty(axis). Raises an IsEmptyError.
  • labels is non-empty, holds no empty entry and holds no repeat. See assert_panel_labels.
  • length(groups) == length(labels) when groups is given. Raises a DimensionMismatch.
  • !isempty(vals). Raises an IsEmptyError.
  • ndims(vals) in (2, 3). Raises a DimensionMismatch.
  • size(vals, ndims(vals)) == length(labels). Raises a DimensionMismatch.
  • size(omsk) == size(vals) when omsk is given. Raises a DimensionMismatch.

Related

source

Functions

PortfolioOptimisers.panel_fieldFunction
panel_field(pnl::AssetPanel, name::AbstractString) -> AbstractPanelField

Look one Panel Field up in an AssetPanel by name.

This is the only supported route from a Panel Field's name to its values. A consumer that parses a derived column name instead is reading a convention rather than the panel, and the two part company as soon as a Panel Field's own name carries the convention's punctuation.

Algorithm

  1. Find the first Panel Field whose name matches.
  2. Throw a KeyError naming the nearest match and the whole panel when none does.

Arguments

  • pnl: The Asset Panel.
  • name: The Panel Field's name.

Validation

  • The panel holds a Panel Field named name. Raises a KeyError.

Returns

  • f::AbstractPanelField: The Panel Field.

Related

source
PortfolioOptimisers.panel_feature_matrixFunction
panel_feature_matrix(pnl::Nothing) -> Tuple{Nothing, Nothing}
panel_feature_matrix(pnl::AssetPanel) -> Tuple{Vector{String}, Array}

Derive the Feature Matrix an AssetPanel's Panel Fields stack into, and name its columns.

A carrier that holds no panel derives nothing, so the nothing method answers with two of them and no consumer needs a branch of its own.

Nothing stores the result. A Feature Matrix is what a distance measures, so it is built where it is measured and thrown away after: the panel is the data, and the matrix is one view of it.

The column order is the Panel Field order, and within one Panel Field its value columns come first and its observed-mask columns after. A static panel gives an assets × features matrix, and a time-varying one an observations × assets × features matrix.

Algorithm

The method that Julia selects decides whether there is anything to derive.

  1. Walk the Panel Fields in order. Claim each one's value columns from panel_field_labels, then its observed-mask columns from panel_field_observed_labels when it carries a mask.
  2. Allocate the matrix as zeros, over the panel's own observation and asset axes and the claimed column count, in the type panel_value_eltype derives over the Panel Fields.
  3. Write each Panel Field's values with panel_field_stack! and its mask with panel_field_stack_observed!.

Arguments

  • pnl: The Asset Panel.

Returns

  • nz::Vector{String}: One name per column of the derived Feature Matrix.
  • Z::Array: The derived Feature Matrix, in the type panel_value_eltype derives over the Panel Fields it stacks.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(pnl::AssetPanel, i) -> AssetPanel
port_opt_view(pnl::AssetPanel, i, j, nx::Option{<:VecStr} = nothing) -> AssetPanel

Return a view of the AssetPanel over the observations i and the assets j.

Every Panel Field owns its values, so an asset view reaches them all: the one-argument arity keeps every observation and selects assets, and the three-argument arity selects both. A static panel has no observation axis and ignores the observation index, which is the same asymmetry the two port_opt_view arities have for ivpa.

nx is the carrier's asset names, and it is what makes the square case derivable: a tensor Panel Field whose labels are those names is sliced on its label axis by the same asset index. Nothing records the fact, and no carrier carries a flag for it; features_are_assets states the comparison.

Algorithm

  1. View each Panel Field with panel_field_view, passing a Colon for the observation index of a static panel and nx for the square case.
  2. View both masks with panel_mask_view, which keeps them nothing when the panel is static.

Arguments

  • pnl: The Asset Panel.
  • i: Observation index.
  • j: Asset index.
  • nx: The carrier's asset names, or nothing.

Returns

  • new_pnl::AssetPanel: An Asset Panel over the selected observations and assets.

Related

source
PortfolioOptimisers.panel_field_axesFunction
panel_field_axes(f::NumericPanelField) -> Tuple{Vararg{Int}}
panel_field_axes(f::CategoricalPanelField) -> Tuple{Vararg{Int}}
panel_field_axes(f::TensorPanelField) -> Tuple{Vararg{Int}}

Return a Panel Field's observation and asset axes, without its trailing label axis.

This is the shape every Panel Field of one AssetPanel shares, and it is what says whether the panel is static. A static Panel Field returns (N,); a time-varying one returns (T, N).

Algorithm

The method that Julia selects is the algorithm. A numeric and a categorical Panel Field carry no label axis, so their whole size is returned. A tensor Panel Field drops its trailing label axis.

Arguments

  • f: The Panel Field.

Returns

  • ax::Tuple{Vararg{Int}}: (N,) when static, (T, N) when time-varying.

Related

source
PortfolioOptimisers.panel_field_labelsFunction
panel_field_labels(f::NumericPanelField) -> Vector{String}
panel_field_labels(f::CategoricalPanelField) -> Vector{String}
panel_field_labels(f::TensorPanelField) -> Vector{String}

Return the column names one Panel Field contributes to a derived Feature Matrix, in column order.

Algorithm

The method that Julia selects is the algorithm. Each kind names its columns differently.

  1. NumericPanelField: one column, named after the Panel Field itself.
  2. CategoricalPanelField: one column per level, named "<name>=<level>".
  3. TensorPanelField: one column per label, named "<name>=<label>".

Arguments

  • f: The Panel Field.

Returns

  • labels::Vector{String}: One name per derived column.

Related

source
PortfolioOptimisers.panel_field_stack!Function
panel_field_stack!(Z::AbstractArray, f::NumericPanelField, cols::VecInt) -> nothing
panel_field_stack!(Z::AbstractArray, f::CategoricalPanelField, cols::VecInt) -> nothing
panel_field_stack!(Z::AbstractArray, f::TensorPanelField, cols::VecInt) -> nothing

Write one Panel Field's value columns into a derived Feature Matrix.

Z is allocated as zeros, which is what makes a one-hot column correct: the categorical method writes only the 1s.

Algorithm

The method that Julia selects is the algorithm.

  1. NumericPanelField: write the values into the single column.
  2. CategoricalPanelField: write a 1 into the column its code names, for each cell.
  3. TensorPanelField: write one label slice per column.

Arguments

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

Returns

  • nothing.

Related

source
PortfolioOptimisers.panel_field_viewFunction
panel_field_view(f::NumericPanelField, i, j, nx) -> NumericPanelField
panel_field_view(f::CategoricalPanelField, i, j, nx) -> CategoricalPanelField
panel_field_view(f::TensorPanelField, i, j, nx) -> TensorPanelField

Return a view of one Panel Field over the observations i and the assets j.

A static Panel Field has no observation axis, so its caller passes a Colon for i.

The square case is derived here, by name. A tensor Panel Field whose labels are the carrier's asset names (features_are_assets) is sliced on its label axis by the same asset index, together with its labels and its groups: a group belongs to one label, so the labels that survive keep theirs. Every other field's trailing axis addresses features, and an asset view does not reach it.

Algorithm

The method that Julia selects is the algorithm, and each kind views its own value array and its own mask: a numeric and a categorical Panel Field through panel_array_view, and a tensor Panel Field through panel_tensor_view, whose asset axis is not the last one.

Arguments

  • f: The Panel Field.
  • i: Observation index.
  • j: Asset index.
  • nx: The carrier's asset names, or nothing. Read for the square case alone.

Returns

  • A Panel Field of the same kind over the selected observations and assets.

Related

source