Factor Family Basis

Types

PortfolioOptimisers.FactorFamilyBasisType
struct FactorFamilyBasis{__T_fnm, __T_fi, __T_di, __T_ratios, __T_K} <: AbstractFactorFamilyBasis

Compact change of basis between the raw factor axis and the reduced axis a re-based Factor Family is fitted in.

A Factor Family whose one-hot exposures are collinear with a global factor carries one redundant column. The re-basis drops one member of the family and rewrites the family in an equivalent basis of full column rank, in which the benchmark-weighted factor returns of the family sum to zero. The change of basis is time-varying, and this result stores it compactly: per observation it holds the ratios of the benchmark-weighted exposures of the retained members to that of the dropped member, never the dense basis matrix.

Fields

  • fnm: Label of each constrained Factor Family, one entry per family, in the order the families were requested. That order fixes the column order of ratios.
  • fi: Member indices of each constrained Factor Family in the raw factor axis, one vector per family. Two families never share a member.
  • di: Position within fi[j] of the member family j drops, one entry per family. The dropped member is the one the zero-sum condition reconstructs.
  • ratios: Ratios of the benchmark-weighted exposures, observations × C, with C = sum(length(fi[j]) - 1). The block of family j holds one column per retained member, in the member order of fi[j] with the dropped position removed.
  • K: Number of factors on the raw axis. The reduced axis holds K - length(fnm) of them.

Constructors

FactorFamilyBasis(; fnm::VecStr, fi::AbstractVector{<:AbstractVector{<:Integer}},                  di::AbstractVector{<:Integer}, ratios::MatNum, K::Integer)

Keywords correspond to the struct's fields.

Validation

  • K > 0 and !isempty(fnm).
  • fnm, fi and di have the same length.
  • Every family holds at least two members, its members are unique, and every member lies in 1:K.
  • No factor belongs to two families.
  • di[j] indexes fi[j].
  • ratios has one column per retained member of a constrained family, sum(length(fi[j]) - 1) in all.
  • Every entry of ratios is finite.
  • K is greater than the number of families, so the reduced axis is not empty.

Mathematical definition

For a family of $m$ members the benchmark-weighted exposure of member $j$ at observation $t$ is

\[c_t(j) = \sum_{i} w^{b}_{t,i} \, B_{t,i,j},\]

and the family's factor returns satisfy the zero-sum condition $c_t^{\top} f_{\mathrm{family}}(t) = 0$. Dropping member $k$ and writing $r_t(j) = c_t(j) / c_t(k)$ for $j \ne k$ parameterises the reduced basis, and ratios stores those $r_t(j)$.

Where:

  • $w^{b}_{t,i}$: benchmark weight of asset $i$ at observation $t$.
  • $B_{t,i,j}$: exposure of asset $i$ to factor $j$ at observation $t$.
  • $f_{\mathrm{family}}(t)$: factor returns of the family at observation $t$.

Examples

julia> FactorFamilyBasis(; fnm = ["industry"], fi = [[1, 2]], di = [2],                         ratios = reshape([0.5, 0.4], 2, 1), K = 3)FactorFamilyBasis     fnm ┼ Vector{String}: ["industry"]      fi ┼ 1-element Vector{Vector{Int64}}      di ┼ Vector{Int64}: [2]  ratios ┼ 2×1 Matrix{Float64}       K ┴ Int64: 3

Related

source

Functions

PortfolioOptimisers.factor_family_basisFunction
factor_family_basis(families::AbstractVector{<:Pair}, Ms::Arr3Num, bw::MatNum,
                    nf::VecStr, fam::VecStr) -> FactorFamilyBasis

Build the compact change of basis of the requested constrained Factor Families.

Algorithm

  1. Normalise bw so the benchmark weights of each observation sum to one, reading a non-finite weight as zero.
  2. Take the benchmark-weighted exposure c_t(j) of every raw factor, reading a non-finite exposure as zero.
  3. For each requested family, resolve the member indices from fam, and resolve the dropped member. A stated drop is looked up in nf. An unstated drop is the member with the largest time-average absolute benchmark-weighted exposure, which keeps the ratios moderate.
  4. Divide the benchmark-weighted exposures of the retained members by that of the dropped member, giving the family's block of ratios.
  5. Build the FactorFamilyBasis from the resolved families and the concatenated blocks, which re-runs every guard of the constructor.

Arguments

  • families: Pairs of family label => dropped factor name, in the order the families take columns of ratios. A nothing on the right asks for the automatic choice of step 3.
  • Ms::Arr3Num: Exposure history, observations × assets × factors.
  • bw::MatNum: Benchmark weight history, observations × assets.
  • nf::VecStr: Names of the raw factor axis, of length size(Ms, 3).
  • fam::VecStr: Family label of each raw factor, of length size(Ms, 3).

Validation

  • families is not empty, and no family label appears twice.
  • nf and fam are as long as the factor axis of Ms, and nf does not repeat a name.
  • bw matches Ms on the observation and asset axes, and every finite weight is non-negative.
  • Every observation carries a strictly positive benchmark weight sum.
  • Every requested family label appears in fam, and holds at least two factors.
  • A stated dropped factor name appears in nf, and belongs to the family that names it.
  • The rules of FactorFamilyBasis, which refuse a non-finite ratio.

A dropped factor whose benchmark-weighted exposure is zero at some observation produces a non-finite ratio, and the constructor refuses it. One that is merely small produces a large but finite ratio, which is accepted and ill-conditioned: state the drop, or let step 3 choose it.

Returns

  • fcb::FactorFamilyBasis: The compact change of basis.

Examples

julia> Ms = reshape([1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0], 2, 2, 2);julia> factor_family_basis(["ind" => nothing], Ms, [0.5 0.5; 0.5 0.5], ["ind=a", "ind=b"],                           ["ind", "ind"])FactorFamilyBasis     fnm ┼ Vector{String}: ["ind"]      fi ┼ 1-element Vector{Vector{Int64}}      di ┼ Vector{Int64}: [1]  ratios ┼ 2×1 Matrix{Float64}       K ┴ Int64: 2

Related

source