Factor Prior: private API

PortfolioOptimisers.factor_reconstructionFunction
factor_reconstruction(re::AbstractTimeSeriesRegressionEstimator, X::MatNum,
                      F::MatNum) -> Tuple{AbstractLoadingsRegressionResult, MatNum}

Fit the loadings and rebuild the asset returns from the factor returns.

This is the first half of the factor lift, and the only half every factor-axis estimator shares. It fits re on (X, F) and returns the regression result together with the posterior returns matrix F * transpose(M) .+ transpose(b) — the reconstruction that FactorPrior, FactorBlackLittermanPrior and AugmentedBlackLittermanPrior each write into LowOrderPrior.X.

The second half — projecting the factor moments through the loadings — is factor_lift. The two are separate because FactorBlackLittermanPrior needs the reconstruction before it has the moments to project: its views land on the factor distribution, so the factor moments only exist after the Black-Litterman update.

Algorithm

  1. Fit re on (X, F) with regression, giving rr. It carries the $N \times K$ loadings rr.M and the $N \times 1$ intercepts rr.b.
  2. Rebuild the asset returns as F * transpose(rr.M) .+ transpose(rr.b), giving posterior_X, which has F's rows and X's columns.

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

  • rr::AbstractLoadingsRegressionResult: Regression result carrying the loadings M and intercepts b.
  • posterior_X::MatNum: Reconstructed asset returns, observations × assets.

Related

source
PortfolioOptimisers.factor_liftFunction
factor_lift(mp::AbstractMatrixProcessingEstimator, ve::AbstractVarianceEstimator,
            rsd::Bool, rr::AbstractLoadingsRegressionResult, f_mu::VecNum, f_sigma::MatNum,
            X::MatNum, posterior_X::MatNum; kwargs...) -> NamedTuple

Project factor moments onto the asset axis through the regression loadings.

This is the second half of the factor lift, and it owns the algorithm that FactorPrior and FactorBlackLittermanPrior both apply: map f_mu and f_sigma through the loadings, process the resulting covariance, and — when rsd is true — add the diagonal residual block and extend the Cholesky factor with it.

Which factor moments arrive is the caller's decision, and it is the only thing that differs between the two sites: FactorPrior passes the wrapped prior's moments unchanged, while FactorBlackLittermanPrior passes the Black-Litterman posterior moments.

Mathematical definition

\[\begin{align} \hat{\boldsymbol{\mu}} &= \mathbf{B} \hat{\boldsymbol{f}} + \boldsymbol{\alpha}\,, \\ \hat{\mathbf{\Sigma}} &= \mathbf{B} \mathbf{\Sigma}_f \mathbf{B}^\intercal + \mathbf{\Sigma}_\varepsilon\,. \end{align}\]

Where:

  • $\hat{\boldsymbol{\mu}}$: $N \times 1$ asset expected returns vector.
  • $\hat{\mathbf{\Sigma}}$: $N \times N$ asset covariance matrix.
  • $\mathbf{B}$: $N \times K$ factor loadings matrix, rr.M.
  • $\boldsymbol{\alpha}$: $N \times 1$ vector of regression intercepts, rr.b.
  • $\hat{\boldsymbol{f}}$: $K \times 1$ factor expected returns vector, f_mu.
  • $\mathbf{\Sigma}_f$: $K \times K$ factor covariance matrix, f_sigma.
  • $\mathbf{\Sigma}_\varepsilon$: $N \times N$ diagonal matrix of residual variances, present only when rsd is true.

The returned chol is the transpose of $[\mathbf{B} \mathbf{L}_f \quad \mathbf{\Sigma}_\varepsilon^{1/2}]$, where $\mathbf{L}_f$ is the lower Cholesky factor of $\mathbf{\Sigma}_f$. It is therefore $(K + N) \times N$ when rsd is true, and $K \times N$ when rsd is false, the residual block being absent from chol and from $\hat{\mathbf{\Sigma}}$ alike.

$\mathtt{chol}^\intercal \mathtt{chol} = \hat{\mathbf{\Sigma}}$ holds before matrix processing, and that qualifier is load-bearing. chol is built from the f_sigma the caller passed, so step 4 of the algorithm rewrites sigma without rewriting chol. Under an mp that leaves the projected covariance where it found it — which the default MatrixProcessing does, its pdm being a no-op on a matrix that is already positive semi-definite — the two agree and the identity holds on the returned pair. Under an mp that denoises or detones, sigma moves and chol stays behind, so the identity holds against the unprocessed covariance alone. A consumer that needs a factor of the returned sigma must refactorise it.

Algorithm

  1. Read the loadings M and the intercepts b off rr.
  2. Project the factor mean through the loadings, giving posterior_mu.
  3. Project the factor covariance through the loadings, giving posterior_sigma, the systematic block.
  4. Process posterior_sigma in place with matrix_processing!, under mp and posterior_X.
  5. Carry the lower Cholesky factor of f_sigma through the loadings, giving posterior_csigma. This reads the f_sigma the caller passed, which step 4 does not touch.
  6. When rsd is true, take the reconstruction error err = X - posterior_X, and read esigma, the column variances of err under ve. Size the residual block as err_sigma, the diagonal matrix of those variances. When rsd is false, esigma is nothing.
  7. Still under rsd, add err_sigma to posterior_sigma and re-condition the sum with posdef!, under mp.pdm. This is the body's only explicit posdef! call. mp.pdm also reaches posterior_sigma inside step 4, whenever :pdm is a member of mp.order.
  8. Still under rsd, widen posterior_csigma with sqrt.(err_sigma), so the block that step 7 added to the covariance enters the factor as well.
  9. Reshape posterior_csigma to length(posterior_mu) columns, transpose it into chol, and return the four quantities.

Arguments

  • mp: Matrix processing estimator.
  • ve: Variance estimator.
  • rsd: Whether to include residual variance in the posterior covariance.
  • rr: Regression result carrying the loadings M and intercepts b.
  • f_mu: Factor expected returns, factors × 1.
  • f_sigma: Factor covariance matrix, factors × factors.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • posterior_X: Reconstructed asset returns from factor_reconstruction.
  • kwargs...: Additional keyword arguments passed to matrix processing.

Returns

  • (; mu, sigma, chol, esigma)::NamedTuple: Asset expected returns, asset covariance, the Cholesky-like factor whose trailing block is the residual standard deviations when rsd is true, and the residual variances themselves. esigma is nothing when rsd is false, because no residual block was added. A caller writes it onto the esigma field of the loadings result it returns, so that a consumer that needs the idiosyncratic variances reads them off the block instead of recomputing them from the reconstruction error.

Related

source
PortfolioOptimisers.factor_residual_configFunction
factor_residual_config(pe::AbstractPriorEstimator) -> Option{<:NamedTuple}

Declare how a prior estimator adds a residual block to the covariance it lifts.

A consumer that needs to undo the residual block — HighOrderFactorPriorEstimator subtracts it to recover the systematic covariance its residual cokurtosis correction is defined on — cannot read ve and mp.pdm off the wrapped estimator's fields. Its pe slot is bounded AbstractLowOrderPriorEstimator_F_AF, and only FactorPrior and FactorBlackLittermanPrior carry those fields; everything else in that bound is a wrapper or a pooling estimator, and a field access reaches past the type bound into a FieldError.

The declaration closes that gap. Every estimator answers, and it answers beside its own definition: the two that own a residual block report it, a wrapper forwards the answer of the estimator it wraps, and an estimator that adds none says so with an explicit nothing method. A pooling estimator is a wrapper for this purpose. It forwards the one estimator its moments come from, however many priors it pools. A nothing answer — and an answer whose rsd is false — both mean no residual block was added, so the consumer leaves the covariance alone.

There is no default. A silent nothing fallback cannot separate this estimator adds no residual block from the author of this estimator forgot the method, and the second reading drops a residual block the covariance really carries. An undeclared type therefore throws and names itself, which is the polarity range_tails already uses for a per-type declaration whose absence is a defect rather than an answer.

Arguments

  • pe: Prior estimator.

Validation

  • Throws an ArgumentError when the type of pe declares no method.

Returns

  • nothing: The estimator adds no residual block.
  • (; ve, pdm, rsd)::NamedTuple: The variance estimator that sizes the residual block, the positive definite matrix estimator that re-conditions a covariance the block was removed from, and whether the block is added at all.

Related

source
PortfolioOptimisers.assert_factor_residual_configFunction
assert_factor_residual_config(pe::AbstractPriorEstimator, cfg) -> Nothing

Check the shape of a factor_residual_config answer before a consumer reads it.

A consumer reaches ve, pdm and rsd off the returned value by property access, which has no shape check of its own: a declaration that returns the wrong thing surfaces as a FieldError deep inside the correction rather than at the declaration that caused it. This checks the two shapes the contract admits, and names the estimator that answered.

Arguments

  • pe: Prior estimator that answered.
  • cfg: The answer of factor_residual_config(pe).

Validation

  • cfg is nothing, or a NamedTuple carrying ve, pdm and rsd.

Related

source
PortfolioOptimisers.show_fieldsMethod
show_fields(_::FactorPrior) -> NTuple{5, Symbol}

Renders every field of a FactorPrior except cache.

The state a cache holds is the running detail of an incremental fit, not the configuration a reader looks the type up for, and it prints under the estimator at every site that renders one. Set set_show_nothing_fields!(:FactorPrior, true) to render it.

Arguments

  • ::FactorPrior: Prior estimator, read for its type alone.

Returns

  • fields::Tuple: The field names to render, which is (:pe, :mp, :re, :ve, :rsd).

Related

source