Cross-Sectional Factor Model
Types
PortfolioOptimisers.CrossSectionalFactorModel — Type
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} <: AbstractLoadingsRegressionResultHolds 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
CrossSectionalRegressioncarries. 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) matrixassets × 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) matrixassets × reduced_dimensions. It is set only after a family re-basis, and an unsetLreads back asM.
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, whichcsrcarries.
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 historyobservations × assets × factors. Its last slice is the loadings matrixM. The constructor checks the two axes it shares withMand never the entries, so a caller that builds both keeps them in step itself.
vs: Idiosyncratic variance historyobservations × assets. Rowtholds the variances estimated from the observations up tot.
esigma: Idiosyncratic covariance. A vector holds the variances alone, and a matrix holds the full covariance.
rw: Regression weight historyobservations × assets. Entry(t, i)is the weight asseticarried in the fit of observationt, and a weight of zero excluded the pair.
bw: Benchmark weight historyobservations × assets. Entry(t, i)is the weight asseticarried in the benchmark of observationt.
nf: Name of each raw factor, one entry per column ofM. 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 ofM.
fcb: The family re-basisLis written in. It is present exactly whenLis 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, ornothing. Itsmuis the forecastbwas 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) -> CrossSectionalFactorModelKeywords correspond to the struct's fields.
Validation
!isempty(M),!isempty(b), andlength(b) == size(M, 1).Landfcbare present together, or absent together.- If provided,
!isempty(L), andsize(L, 1) == size(M, 1). - If provided,
!isempty(nf),length(nf) == size(M, 2), andnfrepeats no name. - If provided,
!isempty(fam), andlength(fam) == size(M, 2). - If provided,
!isempty(Ms),size(Ms, 2) == size(M, 1), andsize(Ms, 3) == size(M, 2). - If provided,
size(csr.eps, 2) == size(M, 1). - If provided,
!isempty(vs),!isempty(rw),!isempty(bw), and each carriessize(M, 1)columns. - Every two of
vs,rwandbwthat are present agree on the observation axis, sosize(rw) == size(bw) == size(vs)when all three are present. - If provided,
!isempty(esigma), andesigmacarriessize(M, 1)entries when it is a vector, or is square withsize(M, 1)rows when it is a matrix. - If provided,
lag >= 0. - If provided,
length(rf.mu) == size(M, 1), andrf.histcarriessize(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,Landbare sliced on their first axis, which is the asset axis of a loadings result.csris viewed by its ownport_opt_viewmethod.Msis sliced on its second axis, which is the asset axis of a slice.vs,rwandbware sliced on their second axis, which is the asset axis of a per-asset history.esigmais sliced byidiosyncratic_covariance_view, on one axis or on both.rfis viewed by its ownport_opt_viewmethod, which cutsmuandhiston the asset axis.nf,fam,fcbandlagpass 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 ┴ nothingRelated
Functions
PortfolioOptimisers.port_opt_view — Method
port_opt_view(csfm::CrossSectionalFactorModel, i, args...)Return a view of a CrossSectionalFactorModel result, selecting only the assets indexed by i.
Algorithm
- Read
Lwithgetfield, never through property access. Theswap(L, M)rule ofCrossSectionalFactorModelmakescsfm.Lreturncsfm.MwhenLis unset, so a property read would materialiseLas a copy ofMand lose the unset-ness. - Take a row view of
M, ofLwhen step 1 found a matrix, and an element view ofb, giving the loadings and the factor-orthogonal expected return of the selected assets. - View the nested fit with its own
port_opt_viewmethod, which cuts its residuals on the asset axis. - Take a view of
Mson its second axis, and ofvs,rwandbwon their second axis, giving the histories of the selected assets. - View
esigmawithidiosyncratic_covariance_view, which reads its shape. - View the Return Forecast with its own
port_opt_viewmethod, which cutsmuandhiston the asset axis. - Build a new
CrossSectionalFactorModelfrom the views, passingnf,fam,fcbandlagthrough, 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))trueRelated
PortfolioOptimisers.regression — Method
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