Regression
PortfolioOptimisers.AbstractTimeSeriesRegressionEstimator — Type
abstract type AbstractTimeSeriesRegressionEstimator <: AbstractRegressionEstimatorAbstract supertype for all time-series regression estimator types.
All concrete and/or abstract types implementing regression estimation algorithms that fit one model per asset over the observations should be subtypes of AbstractTimeSeriesRegressionEstimator.
Interfaces
In order to implement a new time-series regression estimator which will work seamlessly with the library, subtype AbstractTimeSeriesRegressionEstimator with all necessary parameters as part of the struct, and implement the following methods:
Regression
PortfolioOptimisers.regression(re::AbstractTimeSeriesRegressionEstimator, X::MatNum, F::MatNum) -> Regression: Computes the regression result from asset returnsXand factor returnsF.
Arguments
re: Regression estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.F: Data matrixobservations × factorsif thedimskeyword does not exist ordims = 1,factors × observationswhendims = 2.
Returns
reg::Regression: Regression result containing the coefficient matrix and optional intercept.
Examples
We can create a dummy regression estimator as follows:
julia> struct MyRegressionEstimator <: PortfolioOptimisers.AbstractTimeSeriesRegressionEstimator endjulia> function PortfolioOptimisers.regression(::MyRegressionEstimator, X::PortfolioOptimisers.MatNum, F::PortfolioOptimisers.MatNum) return PortfolioOptimisers.Regression(; M = F \ X) endjulia> regression(MyRegressionEstimator(), [1.0 2.0; 3.0 4.0; 5.0 6.0], [1.0 0.0; 0.0 1.0; 0.5 0.5])Regression M ┼ 2×2 Matrix{Float64} L ┼ 2×2 Matrix{Float64} b ┼ nothing esigma ┴ nothingRelated
PortfolioOptimisers.AbstractCrossSectionalRegressionEstimator — Type
abstract type AbstractCrossSectionalRegressionEstimator <: AbstractRegressionEstimatorAbstract supertype for all cross-sectional regression estimator types.
All concrete and/or abstract types implementing regression estimation algorithms that fit one model per observation across the assets should be subtypes of AbstractCrossSectionalRegressionEstimator.
Interfaces
In order to implement a new cross-sectional regression estimator which will work seamlessly with the library, subtype AbstractCrossSectionalRegressionEstimator with all necessary parameters as part of the struct, and implement the following methods:
Cross-sectional regression
PortfolioOptimisers.cross_sectional_regression(cre::AbstractCrossSectionalRegressionEstimator, Z::Arr3Num, X::MatNum, W::MatNum) -> CrossSectionalRegression: Computes the cross-sectional regression result from the exposure tensorZ, the asset returnsXand the cross-sectional weightsW.
Arguments
cre: Cross-sectional regression estimator.Z: Exposure tensorobservations × assets × factors.X: Asset returns matrixobservations × assets.W: Cross-sectional weights matrixobservations × assets.
Returns
csr::CrossSectionalRegression: Cross-sectional regression result carrying the factor returns, the residuals, the counts and the optional intercept.
Examples
We can create a dummy cross-sectional regression estimator as follows:
julia> struct MyCrossSectionalRegressionEstimator <: PortfolioOptimisers.AbstractCrossSectionalRegressionEstimator endjulia> function PortfolioOptimisers.cross_sectional_regression(::MyCrossSectionalRegressionEstimator, Z::PortfolioOptimisers.Arr3Num, X::PortfolioOptimisers.MatNum, W::PortfolioOptimisers.MatNum) f = permutedims(reduce(hcat, Z[t, :, :] \ X[t, :] for t in axes(X, 1))) eps = X - permutedims(reduce(hcat, Z[t, :, :] * f[t, :] for t in axes(X, 1))) return PortfolioOptimisers.CrossSectionalRegression(; f = f, eps = eps, n = fill(size(X, 2), size(X, 1))) endjulia> cross_sectional_regression(MyCrossSectionalRegressionEstimator(), reshape([1.0, 0.0, 0.5, 0.0, 1.0, 0.5], 1, 3, 2), [1.0 2.0 1.5], ones(1, 3))CrossSectionalRegression f ┼ 1×2 Matrix{Float64} eps ┼ 1×3 Matrix{Float64} n ┼ Vector{Int64}: [3] b ┴ nothingRelated
PortfolioOptimisers.LinearModel — Type
struct LinearModel{__T_kwargs} <: AbstractRegressionTargetFits each response by ordinary least squares through GLM.LinearModel.
The kwargs field is forwarded verbatim to GLM, so any option that routine accepts — observation weights among them — reaches the fit. This is the default target of every regression estimator in the library.
Fields
kwargs: Keyword arguments passed tofit(GLM.LinearModel, X, y; kwargs...).
Constructors
LinearModel(; kwargs::NamedTuple = (;)) -> LinearModelKeywords correspond to the struct's fields.
Examples
julia> LinearModel()LinearModel kwargs ┴ @NamedTuple{}: NamedTuple()Related
PortfolioOptimisers.GeneralisedLinearModel — Type
struct GeneralisedLinearModel{__T_args, __T_kwargs, __T_variant} <: AbstractRegressionTargetFits each response by a generalised linear model through GLM.GeneralizedLinearModel.
The args field carries the response distribution and, optionally, the link function; kwargs carries the remaining GLM options. The default args = (Normal(),) with the canonical identity link reproduces ordinary least squares. GLM defines $R^2$ for a fitted LinearModel only, so variant names the pseudo-$R^2$ a maximisation criterion reads instead, and it supplies it to the :r2 and :adjr2 members of STEPWISE_REGRESSION_CRITERIA. A nothing variant takes the default of the criterion, which default_regression_criterion_variant states. The field is dead under a minimisation criterion, which reads no variant at all.
Fields
args: Positional arguments passed tofit(GLM.GeneralizedLinearModel, X, y, args...; kwargs...).
kwargs: Keyword arguments passed tofit(GLM.GeneralizedLinearModel, X, y, args...; kwargs...).
variant: Name of the pseudo-$R^2$ variant a maximisation criterion reads, ornothingto take the default of the criterion.
Constructors
GeneralisedLinearModel(; args::Tuple = (Normal(),), kwargs::NamedTuple = (;), variant::Option{Symbol} = nothing) -> GeneralisedLinearModelKeywords correspond to the struct's fields.
Validation
- If provided,
variant in PSEUDO_R2_VARIANTS, the wider of the two variant tuples.StatsAPI.adjr2acceptsADJUSTED_PSEUDO_R2_VARIANTSalone, andStepwiseRegressionrejects the difference when its criterion is:adjr2.
Examples
julia> GeneralisedLinearModel()GeneralisedLinearModel args ┼ Tuple{Distributions.Normal{Float64}}: (Distributions.Normal{Float64}(μ=0.0, σ=1.0),) kwargs ┼ @NamedTuple{}: NamedTuple() variant ┴ nothingRelated
AbstractRegressionTargetLinearModelPSEUDO_R2_VARIANTSADJUSTED_PSEUDO_R2_VARIANTSSTEPWISE_REGRESSION_CRITERIAStepwiseRegressiondefault_regression_criterion_variantregression_criterion_funcStatsAPI.fit(::GeneralisedLinearModel, ::MatNum, ::VecNum)
References
- [32] J. A. Nelder and R. W. Wedderburn. Generalized linear models. Journal of the Royal Statistical Society: Series A (General) 135, 370–384 (1972).
PortfolioOptimisers.Regression — Type
struct Regression{__T_M, __T_L, __T_b, __T_esigma} <: AbstractLoadingsRegressionResultHolds the loadings matrix, the intercept vector, the reduced-basis loadings and the idiosyncratic covariance of a fitted factor model.
M and b are the loadings matrix and the intercept vector of the factor model, one row per asset. L carries the same loadings written in the reduced basis a dimension reduction produced; it is unset when the estimator regresses on the original factors. An unset L reads back as M. A @forward_properties swap(L, M) rule makes re.L return re.M whenever L was not given, so a consumer that decomposes risk in the factor basis needs no Nothing branch, and isnothing(re.L) is never true. Read getfield(re, :L) when the unset case must be told apart, as port_opt_view does. StepwiseRegression leaves L unset and DimensionReductionRegression sets it, so size(L, 2) is the width of the basis risk is decomposed in: the original factors under the first, the retained principal components under the second.
esigma holds the idiosyncratic covariance the fit left over, and it carries the same name and the same shapes as the field CrossSectionalFactorModel declares, so one reader answers off either block. A regression estimator fits loadings alone and writes nothing here; the field is filled by the prior that lifts the factor moments, and only when that prior adds a residual block. FactorPrior and FactorBlackLittermanPrior write the residual variances of factor_lift under rsd = true, and leave the field unset under rsd = false.
Mathematical definition
\[\begin{align} \boldsymbol{x}_{t} &= \boldsymbol{b} + \mathbf{M} \boldsymbol{f}_{t} + \boldsymbol{\varepsilon}_{t}\,. \end{align}\]
Where:
- $\boldsymbol{x}_t$: Asset returns for observation $t$, the $t$-th row of the returns matrix.
- $\boldsymbol{b}$: Intercept vector $N \times 1$,
b. The term is absent whenbis unset. - $\mathbf{M}$: Loadings matrix $N \times K$ of the factor model,
M. - $\boldsymbol{f}_{t}$: Factor returns for observation $t$, the $t$-th row of the factor matrix.
- $\boldsymbol{\varepsilon}_{t}$: Residual returns for observation $t$, the part of $\boldsymbol{x}_{t}$ the factors do not explain.
- $N$: Number of assets.
- $K$: Number of factors.
Fields
M:M: Main coefficient (loadings) matrixassets × factors.
L:L: Reduced dimensionality coefficient (loadings) matrixassets × reduced_dimensions.
b:b: Regression intercept vector.
esigma:esigma: Idiosyncratic covariance. A vector holds the variances alone, and a matrix holds the full covariance.
Constructors
Regression(; M::MatNum, L::Option{<:MatNum} = nothing, b::Option{<:VecNum} = nothing, esigma::Option{<:VecNum_MatNum} = nothing) -> RegressionKeywords correspond to the struct's fields.
Validation
!isempty(M).- If provided,
!isempty(b), andlength(b) == size(M, 1). - If provided,
!isempty(L), andsize(L, 1) == size(M, 1). - 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.
Examples
julia> Regression(; M = [1 2 3; 4 5 6], L = [1 2 3 4; 5 6 7 8], b = [1, 2], esigma = [0.1, 0.2])Regression M ┼ 2×3 Matrix{Int64} L ┼ 2×4 Matrix{Int64} b ┼ Vector{Int64}: [1, 2] esigma ┴ Vector{Float64}: [0.1, 0.2]Related
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 4.1, Equations 4.2-4.3.
PortfolioOptimisers.factory — Method
factory(re::LinearModel, w::ObsWeights) -> LinearModelReturn a new LinearModel regression target with observation weights w added to the keyword arguments.
Algorithm
- Merge
wintore.kwargsunder the keyweights, replacing any entry already stored there, giving the keyword arguments of the new target. - Build a new
LinearModelfrom them.
Arguments
re: Linear model regression target.w: Observation weights vectorobservations × 1.
Returns
re::LinearModel: Updated regression target with weights included inkwargs.
Related
StatsAPI.fit — Method
StatsAPI.fit(tgt::LinearModel, X::MatNum, y::VecNum)Fit a standard linear regression model using a LinearModel regression target.
This method dispatches to StatsAPI.fit with the GLM.LinearModel type, passing the design matrix X, response vector y, and any keyword arguments stored in tgt.kwargs. It enables flexible configuration of the underlying linear model fitting routine within the regression estimation framework.
Algorithm
- Read
tgt.kwargs. When it carries aweightsentry holding aDynamicAbstractWeights, resolve that entry againstXwithget_observation_weightsand write the resolved weights back under the same key, givingkwargs. Otherwise taketgt.kwargsunchanged. - Call
StatsAPI.fit(GLM.LinearModel, X, y; kwargs...), giving the fitted model.
Arguments
tgt: Regression target specifying model options.X: The design matrix (observations × factors).y: The response vector.
Returns
model::GLM.LinearModel: A fitted linear model object from the GLM.jl package.
Related
PortfolioOptimisers.factory — Method
factory(re::GeneralisedLinearModel, w::ObsWeights) -> GeneralisedLinearModelReturn a new GeneralisedLinearModel regression target with observation weights w added to the keyword arguments.
Algorithm
- Merge
wintore.kwargsunder the keyweights, replacing any entry already stored there, giving the keyword arguments of the new target. - Build a new
GeneralisedLinearModelfrom them, carryingre.argsandre.variantacross unchanged.
Arguments
re: Generalised linear model regression target.w: Observation weights vectorobservations × 1.
Returns
re::GeneralisedLinearModel: Updated regression target with weights included inkwargs.
Related
StatsAPI.fit — Method
StatsAPI.fit(tgt::GeneralisedLinearModel, X::MatNum, y::VecNum)Fit a generalised linear regression model using a GeneralisedLinearModel regression target.
This method dispatches to StatsAPI.fit with the GLM.GeneralizedLinearModel type, passing the design matrix X, response vector y, any positional arguments in tgt.args, and any keyword arguments in tgt.kwargs.
Algorithm
- Read
tgt.kwargs. When it carries aweightsentry holding aDynamicAbstractWeights, resolve that entry againstXwithget_observation_weightsand write the resolved weights back under the same key, givingkwargs. Otherwise taketgt.kwargsunchanged. - Call
StatsAPI.fit(GLM.GeneralizedLinearModel, X, y, tgt.args...; kwargs...), giving the fitted model.
Arguments
tgt: AGeneralisedLinearModelregression target specifying model options.X: The design matrix (observations × factors).y: The response vector.
Returns
model::GLM.GeneralizedLinearModel: A fitted generalised linear model object from the GLM.jl package.
Related
PortfolioOptimisers.regression — Method
regression(re::Regression, args...)Return the regression result object unchanged.
This method is a pass-through for Regression result objects, allowing generic code to call regression on a result and receive the same object. It enables a unified interface for both estimator and result types.
Arguments
re: A regression result object.args...: Additional arguments (ignored).
Returns
- The input
re, unchanged.
Related
PortfolioOptimisers.regression — Method
regression(re::AbstractTimeSeriesRegressionEstimator, rd::ReturnsResult)Compute or extract a regression result from an estimator or result and a ReturnsResult.
This method dispatches to regression(re, rd.X, rd.F), allowing both regression estimators and regression result objects to be used interchangeably in generic workflows. If re is an estimator, it computes the regression result using the data in rd. If re is already a result, it is returned unchanged.
Algorithm
- Check that
rdcarries both matrices, per# Validationbelow. - Call
regression(re, rd.X, rd.F), giving the regression result.
Arguments
re: A regression estimator or result object.rd: A returns result object containing data matricesXandF.
Validation
!isnothing(rd.X). A regression needs the asset returns it explains.!isnothing(rd.F). A regression needs the factor returns it explains them with.
Returns
reg::Regression: The computed or extracted regression result.
Related
PortfolioOptimisers.port_opt_view — Method
port_opt_view(re::Regression, i)Return a view of a Regression result object, selecting only the rows indexed by i.
This function constructs a new Regression result, where the coefficient matrix M, optional auxiliary matrix L, intercept vector b and idiosyncratic covariance esigma are restricted to the rows specified by the index vector i. This is useful for extracting or operating on a subset of regression results, such as for a subset of assets.
Algorithm
- Read
Landbwithgetfield, never through property access. Theswap(L, M)rule ofRegressionmakesre.Lreturnre.MwhenLis unset, so a property read would materialiseLas a copy ofMand lose the unset-ness. - Take a row view of
Moveri, giving the loadings of the selected assets. - Take a row view of
Loveriwhen step 1 found a matrix, andnothingotherwise. - Take an element view of
boveriwhen step 1 found a vector, andnothingotherwise. - View
esigmawithidiosyncratic_covariance_view, which reads its shape: a vector of variances is indexed once, and a full covariance is indexed on both axes. - Build a new
Regressionfrom the four, which re-runs every guard of the constructor.
Arguments
re: A regression result object.i: Indices of the rows to select.
Returns
reg::Regression: A new regression result object with fields restricted to the selected rows.
Examples
julia> re = Regression(; M = [1 2; 3 4; 5 6], L = [10 20; 30 40; 50 60], b = [7, 8, 9])Regression M ┼ 3×2 Matrix{Int64} L ┼ 3×2 Matrix{Int64} b ┼ Vector{Int64}: [7, 8, 9] esigma ┴ nothingjulia> PortfolioOptimisers.port_opt_view(re, [1, 3])Regression M ┼ 2×2 SubArray{Int64, 2, Matrix{Int64}, Tuple{Vector{Int64}, Base.Slice{Base.OneTo{Int64}}}, false} L ┼ 2×2 SubArray{Int64, 2, Matrix{Int64}, Tuple{Vector{Int64}, Base.Slice{Base.OneTo{Int64}}}, false} b ┼ SubArray{Int64, 1, Vector{Int64}, Tuple{Vector{Int64}}, false}: [7, 9] esigma ┴ nothingRelated
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
- [32]
- J. A. Nelder and R. W. Wedderburn. Generalized linear models. Journal of the Royal Statistical Society: Series A (General) 135, 370–384 (1972).