Factor Family Basis
Types
PortfolioOptimisers.FactorFamilyBasis — Type
struct FactorFamilyBasis{__T_fnm, __T_fi, __T_di, __T_ratios, __T_K} <: AbstractFactorFamilyBasisCompact 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 ofratios.
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 withinfi[j]of the member familyjdrops, one entry per family. The dropped member is the one the zero-sum condition reconstructs.
ratios: Ratios of the benchmark-weighted exposures,observations × C, withC = sum(length(fi[j]) - 1). The block of familyjholds one column per retained member, in the member order offi[j]with the dropped position removed.
K: Number of factors on the raw axis. The reduced axis holdsK - 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 > 0and!isempty(fnm).fnm,fianddihave 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]indexesfi[j].ratioshas one column per retained member of a constrained family,sum(length(fi[j]) - 1)in all.- Every entry of
ratiosis finite. Kis 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: 3Related
Functions
PortfolioOptimisers.factor_family_basis — Function
factor_family_basis(families::AbstractVector{<:Pair}, Ms::Arr3Num, bw::MatNum,
nf::VecStr, fam::VecStr) -> FactorFamilyBasisBuild the compact change of basis of the requested constrained Factor Families.
Algorithm
- Normalise
bwso the benchmark weights of each observation sum to one, reading a non-finite weight as zero. - Take the benchmark-weighted exposure
c_t(j)of every raw factor, reading a non-finite exposure as zero. - For each requested family, resolve the member indices from
fam, and resolve the dropped member. A stated drop is looked up innf. An unstated drop is the member with the largest time-average absolute benchmark-weighted exposure, which keeps the ratios moderate. - Divide the benchmark-weighted exposures of the retained members by that of the dropped member, giving the family's block of
ratios. - Build the
FactorFamilyBasisfrom the resolved families and the concatenated blocks, which re-runs every guard of the constructor.
Arguments
families: Pairs offamily label => dropped factor name, in the order the families take columns ofratios. Anothingon 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 lengthsize(Ms, 3).fam::VecStr: Family label of each raw factor, of lengthsize(Ms, 3).
Validation
familiesis not empty, and no family label appears twice.nfandfamare as long as the factor axis ofMs, andnfdoes not repeat a name.bwmatchesMson 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: 2Related