Regression

PortfolioOptimisers.AbstractTimeSeriesRegressionEstimatorType
abstract type AbstractTimeSeriesRegressionEstimator <: AbstractRegressionEstimator

Abstract 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 returns X and factor returns F.

Arguments

  • re: Regression estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • F: Data matrix observations × factors if the dims keyword does not exist or dims = 1, factors × observations when dims = 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 ┴ nothing

Related

source
PortfolioOptimisers.AbstractCrossSectionalRegressionEstimatorType
abstract type AbstractCrossSectionalRegressionEstimator <: AbstractRegressionEstimator

Abstract 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 tensor Z, the asset returns X and the cross-sectional weights W.

Arguments

  • cre: Cross-sectional regression estimator.
  • Z: Exposure tensor observations × assets × factors.
  • X: Asset returns matrix observations × assets.
  • W: Cross-sectional weights matrix observations × 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 ┴ nothing

Related

source
PortfolioOptimisers.LinearModelType
struct LinearModel{__T_kwargs} <: AbstractRegressionTarget

Fits 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 to fit(GLM.LinearModel, X, y; kwargs...).

Constructors

LinearModel(;    kwargs::NamedTuple = (;)) -> LinearModel

Keywords correspond to the struct's fields.

Examples

julia> LinearModel()LinearModel  kwargs ┴ @NamedTuple{}: NamedTuple()

Related

source
PortfolioOptimisers.GeneralisedLinearModelType
struct GeneralisedLinearModel{__T_args, __T_kwargs, __T_variant} <: AbstractRegressionTarget

Fits 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 to fit(GLM.GeneralizedLinearModel, X, y, args...; kwargs...).
  • kwargs: Keyword arguments passed to fit(GLM.GeneralizedLinearModel, X, y, args...; kwargs...).
  • variant: Name of the pseudo-$R^2$ variant a maximisation criterion reads, or nothing to take the default of the criterion.

Constructors

GeneralisedLinearModel(;    args::Tuple = (Normal(),),    kwargs::NamedTuple = (;),    variant::Option{Symbol} = nothing) -> GeneralisedLinearModel

Keywords correspond to the struct's fields.

Validation

Examples

julia> GeneralisedLinearModel()GeneralisedLinearModel     args ┼ Tuple{Distributions.Normal{Float64}}: (Distributions.Normal{Float64}(μ=0.0, σ=1.0),)   kwargs ┼ @NamedTuple{}: NamedTuple()  variant ┴ nothing

Related

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).
source
PortfolioOptimisers.RegressionType
struct Regression{__T_M, __T_L, __T_b, __T_esigma} <: AbstractLoadingsRegressionResult

Holds 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 when b is 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) matrix assets × factors.
  • L: L: Reduced dimensionality coefficient (loadings) matrix assets × 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) -> Regression

Keywords correspond to the struct's fields.

Validation

  • !isempty(M).
  • If provided, !isempty(b), and length(b) == size(M, 1).
  • If provided, !isempty(L), and size(L, 1) == size(M, 1).
  • 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.

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.
source
PortfolioOptimisers.factoryMethod
factory(re::LinearModel, w::ObsWeights) -> LinearModel

Return a new LinearModel regression target with observation weights w added to the keyword arguments.

Algorithm

  1. Merge w into re.kwargs under the key weights, replacing any entry already stored there, giving the keyword arguments of the new target.
  2. Build a new LinearModel from them.

Arguments

  • re: Linear model regression target.
  • w: Observation weights vector observations × 1.

Returns

  • re::LinearModel: Updated regression target with weights included in kwargs.

Related

source
StatsAPI.fitMethod
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

  1. Read tgt.kwargs. When it carries a weights entry holding a DynamicAbstractWeights, resolve that entry against X with get_observation_weights and write the resolved weights back under the same key, giving kwargs. Otherwise take tgt.kwargs unchanged.
  2. 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

source
PortfolioOptimisers.factoryMethod
factory(re::GeneralisedLinearModel, w::ObsWeights) -> GeneralisedLinearModel

Return a new GeneralisedLinearModel regression target with observation weights w added to the keyword arguments.

Algorithm

  1. Merge w into re.kwargs under the key weights, replacing any entry already stored there, giving the keyword arguments of the new target.
  2. Build a new GeneralisedLinearModel from them, carrying re.args and re.variant across unchanged.

Arguments

  • re: Generalised linear model regression target.
  • w: Observation weights vector observations × 1.

Returns

  • re::GeneralisedLinearModel: Updated regression target with weights included in kwargs.

Related

source
StatsAPI.fitMethod
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

  1. Read tgt.kwargs. When it carries a weights entry holding a DynamicAbstractWeights, resolve that entry against X with get_observation_weights and write the resolved weights back under the same key, giving kwargs. Otherwise take tgt.kwargs unchanged.
  2. Call StatsAPI.fit(GLM.GeneralizedLinearModel, X, y, tgt.args...; kwargs...), giving the fitted model.

Arguments

  • tgt: A GeneralisedLinearModel regression 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

source
PortfolioOptimisers.regressionMethod
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

source
PortfolioOptimisers.regressionMethod
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

  1. Check that rd carries both matrices, per # Validation below.
  2. 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 matrices X and F.

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

source
PortfolioOptimisers.port_opt_viewMethod
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

  1. Read L and b with getfield, never through property access. The swap(L, M) rule of Regression makes re.L return re.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 over i, giving the loadings of the selected assets.
  3. Take a row view of L over i when step 1 found a matrix, and nothing otherwise.
  4. Take an element view of b over i when step 1 found a vector, and nothing otherwise.
  5. View esigma with idiosyncratic_covariance_view, which reads its shape: a vector of variances is indexed once, and a full covariance is indexed on both axes.
  6. Build a new Regression from 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 ┴ nothing

Related

source

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).