Cross-Sectional Factor Model

Types

PortfolioOptimisers.CrossSectionalFactorModelType
struct CrossSectionalFactorModel{__T_M, __T_L, __T_b, __T_csr, __T_Ms, __T_vs, __T_esigma, __T_rw, __T_bw, __T_nf, __T_fam, __T_fcb, __T_lag, __T_rf} <: AbstractLoadingsRegressionResult

Holds the loadings, the factor-orthogonal expected return and the fitted history of a factor model fitted per observation across the assets.

The result is the cross-sectional member of AbstractLoadingsRegressionResult, so a consumer that re-bases a constraint or decomposes risk in the factor basis reads it exactly as it reads a Regression. M carries the raw loadings, whose columns are the named original factors, because a constraint must be written in names a caller can put in an equation. Every field after b is optional, so a model that keeps its loadings and drops its histories is a member of the family, and a caller that asks for a dropped history reads nothing.

An unset L reads back as M. A @forward_properties swap(L, M) rule makes csfm.L return csfm.M whenever L was not given, as Regression behaves, so a consumer that decomposes risk in the factor basis needs no Nothing branch, and isnothing(csfm.L) is never true. Read getfield(csfm, :L) when the unset case must be told apart, as port_opt_view does.

Mathematical definition

\[\begin{align} \boldsymbol{x}_{t} &= b_{t} \boldsymbol{1} + \mathbf{M}_{t} \boldsymbol{f}_{t} + \boldsymbol{\varepsilon}_{t}\,, \\ \boldsymbol{\mu} &= \mathbf{M} \boldsymbol{\mu}_{f} + \boldsymbol{b}\,, \\ \mathbf{M} &= \mathbf{M}_{T}\,. \end{align}\]

Where:

  • $\boldsymbol{x}_t$: Asset returns for observation $t$, the $t$-th row of the returns matrix.
  • $b_{t}$: Intercept of observation $t$, the $t$-th entry of the intercept vector CrossSectionalRegression carries. The term is absent when the fit carries no intercept.
  • $\boldsymbol{1}$: Vector of ones $N \times 1$.
  • $\mathbf{M}_{t}$: Exposure slice of observation $t$, $N \times K$, the $t$-th slice of Ms.
  • $\boldsymbol{f}_{t}$: Factor returns of observation $t$, on the axis of $\mathbf{M}_{t}$.
  • $\boldsymbol{\varepsilon}_{t}$: Idiosyncratic returns of observation $t$, the part of $\boldsymbol{x}_{t}$ the exposures and the intercept do not explain.
  • $\boldsymbol{\mu}$: Expected returns vector $N \times 1$.
  • $\boldsymbol{\mu}_{f}$: Expected factor returns $K \times 1$. A factor prior carries it, and this result does not.
  • $\boldsymbol{b}$: Factor-orthogonal expected return $N \times 1$, b. It is the part of $\boldsymbol{\mu}$ the factors do not span, so it is a term of the expected return and never a term of one observation.
  • $\mathbf{M}$: Loadings matrix $N \times K$ of the factor model, M. It is the last slice of the exposure history.
  • $N$: Number of assets.
  • $K$: Number of factors.
  • $T$: Number of observations.

Fields

  • M: Main coefficient (loadings) matrix assets × factors. Its columns are the named original factors, so a constraint written in a factor's name resolves against it.
  • L: Reduced dimensionality coefficient (loadings) matrix assets × reduced_dimensions. It is set only after a family re-basis, and an unset L reads back as M.
  • b: Factor-orthogonal expected return, one entry per asset. It is the part of the expected return the factors do not span, and it is not the per-observation intercept of the fit, which csr carries.
  • csr: The cross-sectional fit the model was built around. It carries the factor returns, the idiosyncratic returns, the eligible asset counts and the per-observation intercepts.
  • Ms: Exposure history observations × assets × factors. Its last slice is the loadings matrix M. The constructor checks the two axes it shares with M and never the entries, so a caller that builds both keeps them in step itself.
  • vs: Idiosyncratic variance history observations × assets. Row t holds the variances estimated from the observations up to t.
  • esigma: Idiosyncratic covariance. A vector holds the variances alone, and a matrix holds the full covariance.
  • rw: Regression weight history observations × assets. Entry (t, i) is the weight asset i carried in the fit of observation t, and a weight of zero excluded the pair.
  • bw: Benchmark weight history observations × assets. Entry (t, i) is the weight asset i carried in the benchmark of observation t.
  • nf: Name of each raw factor, one entry per column of M. The prior derives the axis from its Exposure Estimators and stores the answer here, so a consumer that names a factor reads one list.
  • fam: Family label of each raw factor, one entry per column of M.
  • fcb: The family re-basis L is written in. It is present exactly when L is present, and this result states no other rule about it.
  • lag: Number of observations by which the exposures lag the returns.
  • rf: The Return Forecast the prior fitted, or nothing. Its mu is the forecast b was split out of, so a consumer reads the forecast the split consumed rather than refitting it.

Constructors

CrossSectionalFactorModel(;    M::MatNum,    L::Option{<:MatNum} = nothing,    b::VecNum,    csr::Option{<:CrossSectionalRegression} = nothing,    Ms::Option{<:Arr3Num} = nothing,    vs::Option{<:MatNum} = nothing,    esigma::Option{<:VecNum_MatNum} = nothing,    rw::Option{<:MatNum} = nothing,    bw::Option{<:MatNum} = nothing,    nf::Option{<:VecStr} = nothing,    fam::Option{<:VecStr} = nothing,    fcb::Option{<:AbstractFactorFamilyBasis} = nothing,    lag::Option{<:Integer} = nothing,    rf::Option{<:AbstractReturnForecastResult} = nothing) -> CrossSectionalFactorModel

Keywords correspond to the struct's fields.

Validation

  • !isempty(M), !isempty(b), and length(b) == size(M, 1).
  • L and fcb are present together, or absent together.
  • If provided, !isempty(L), and size(L, 1) == size(M, 1).
  • If provided, !isempty(nf), length(nf) == size(M, 2), and nf repeats no name.
  • If provided, !isempty(fam), and length(fam) == size(M, 2).
  • If provided, !isempty(Ms), size(Ms, 2) == size(M, 1), and size(Ms, 3) == size(M, 2).
  • If provided, size(csr.eps, 2) == size(M, 1).
  • If provided, !isempty(vs), !isempty(rw), !isempty(bw), and each carries size(M, 1) columns.
  • Every two of vs, rw and bw that are present agree on the observation axis, so size(rw) == size(bw) == size(vs) when all three are present.
  • If provided, !isempty(esigma), and esigma carries size(M, 1) entries when it is a vector, or is square with size(M, 1) rows when it is a matrix.
  • If provided, lag >= 0.
  • If provided, length(rf.mu) == size(M, 1), and rf.hist carries size(M, 1) columns when the member computes one.

View parameters

CrossSectionalFactorModel defines its own port_opt_view method rather than deriving one from field tags.

  • M, L and b are sliced on their first axis, which is the asset axis of a loadings result.
  • csr is viewed by its own port_opt_view method.
  • Ms is sliced on its second axis, which is the asset axis of a slice.
  • vs, rw and bw are sliced on their second axis, which is the asset axis of a per-asset history.
  • esigma is sliced by idiosyncratic_covariance_view, on one axis or on both.
  • rf is viewed by its own port_opt_view method, which cuts mu and hist on the asset axis.
  • nf, fam, fcb and lag pass through unchanged. Each is indexed by factor, or by nothing at all, and neither follows an asset selection.

Examples

julia> CrossSectionalFactorModel(; M = [1.0 2.0; 3.0 4.0; 5.0 6.0], b = [0.1, 0.2, 0.3],                                 esigma = [0.4, 0.5, 0.6], fam = ["style", "style"], lag = 1)CrossSectionalFactorModel       M ┼ 3×2 Matrix{Float64}       L ┼ 3×2 Matrix{Float64}       b ┼ Vector{Float64}: [0.1, 0.2, 0.3]     csr ┼ nothing      Ms ┼ nothing      vs ┼ nothing  esigma ┼ Vector{Float64}: [0.4, 0.5, 0.6]      rw ┼ nothing      bw ┼ nothing      nf ┼ nothing     fam ┼ Vector{String}: ["style", "style"]     fcb ┼ nothing     lag ┼ Int64: 1      rf ┴ nothing

Related

source

Functions

PortfolioOptimisers.port_opt_viewMethod
port_opt_view(csfm::CrossSectionalFactorModel, i, args...)

Return a view of a CrossSectionalFactorModel result, selecting only the assets indexed by i.

Algorithm

  1. Read L with getfield, never through property access. The swap(L, M) rule of CrossSectionalFactorModel makes csfm.L return csfm.M when L is unset, so a property read would materialise L as a copy of M and lose the unset-ness.
  2. Take a row view of M, of L when step 1 found a matrix, and an element view of b, giving the loadings and the factor-orthogonal expected return of the selected assets.
  3. View the nested fit with its own port_opt_view method, which cuts its residuals on the asset axis.
  4. Take a view of Ms on its second axis, and of vs, rw and bw on their second axis, giving the histories of the selected assets.
  5. View esigma with idiosyncratic_covariance_view, which reads its shape.
  6. View the Return Forecast with its own port_opt_view method, which cuts mu and hist on the asset axis.
  7. Build a new CrossSectionalFactorModel from the views, passing nf, fam, fcb and lag through, which re-runs every guard of the constructor.

Arguments

  • csfm: A cross-sectional factor model result.
  • i: Indices of the assets to select.
  • args...: Additional positional arguments (ignored).

Returns

  • csfm::CrossSectionalFactorModel: A new result whose per-asset fields are restricted to the selected assets.

Examples

julia> csfm = CrossSectionalFactorModel(; M = [1.0 2.0; 3.0 4.0; 5.0 6.0], b = [0.1, 0.2, 0.3],                                        esigma = [0.4, 0.5, 0.6]);julia> PortfolioOptimisers.port_opt_view(csfm, [1, 3]).b2-element view(::Vector{Float64}, [1, 3]) with eltype Float64: 0.1 0.3julia> isnothing(getfield(PortfolioOptimisers.port_opt_view(csfm, [1, 3]), :L))true

Related

source
PortfolioOptimisers.regressionMethod
regression(csfm::CrossSectionalFactorModel, args...)

Return the cross-sectional factor model unchanged.

This method is a pass-through for CrossSectionalFactorModel results, as the method over Regression is for that result. A consumer that binds RegE_Reg takes either a result or an estimator, and calls regression on both.

Arguments

  • csfm: A cross-sectional factor model result.
  • args...: Additional arguments (ignored).

Returns

  • The input csfm, unchanged.

Related

source