Cross-Sectional Exposure Diagnostics

Functions

PortfolioOptimisers.exposure_correlationFunction
exposure_correlation(B::Arr3Num, w::Option{<:MatNum} = nothing) -> Matrix{<:Real}
exposure_correlation(csfm::CrossSectionalFactorModel;
                     weighting::AbstractOrthogonalityMetric = BenchmarkWeightMetric()) -> Matrix{<:Real}

Return the time-averaged correlation between every pair of factor exposures.

Two exposures that correlate across the assets carry the same reading of the cross-section, so the pair is redundant and the design of the fit is collinear. This answer is the cross-sectional counterpart of the variance inflation factor, which reads the same collinearity off the design of one observation.

Each pair is correlated over the assets at which both exposures are finite, one observation at a time, and the answers are then averaged over the observations at which the pair is defined. A pair one of whose exposures is constant across the assets has no correlation, and it reads 0 by convention. A pair that never shares three finite assets reads NaN. The diagonal is 1.

The level-2 method answers on the raw factor axis, because the exposure history is the reading of the panel and not the design of the fit, and it reads the unlagged history.

Mathematical definition

\[\mathbf{C}_{kl} = \frac{1}{|\mathcal{T}_{kl}|} \sum_{t \in \mathcal{T}_{kl}} \rho \left( \mathbf{B}_{t \cdot k}, \mathbf{B}_{t \cdot l}, \boldsymbol{u}_{t} \right)\]

Where:

  • $\mathbf{B}_{t \cdot k}$: Cross-section of factor $k$ at observation $t$.
  • $\boldsymbol{u}_{t}$: Cross-sectional weights of observation $t$.
  • $\rho$: The weighted correlation of two cross-sections.
  • $\mathcal{T}_{kl}$: The observations at which the pair is defined.

Algorithm

  1. Resolve the weight history with exposure_weights.
  2. For each observation and each pair, accumulate the weighted sums over the assets at which both exposures are finite.
  3. Mark the pair as degenerate when either weighted variance falls to the tolerance of its own weighted square sum, and as insufficient when fewer than three assets are shared.
  4. Average the pairs that are neither over the observations. A pair that is degenerate at every observation it covers reads 0, and one that is never sufficient reads NaN.
  5. Write 1 on the diagonal.

Arguments

  • B: Exposure history observations × assets × factors, unlagged.
  • w: Cross-sectional weight history observations × assets, or nothing for equal weights.
  • csfm: A cross-sectional factor model block.
  • weighting: A member of AbstractOrthogonalityMetric. It names the weight history the block is read with, and cs_diagnostic_weights resolves it over the whole observation axis.

Validation

  • !isempty(B).
  • size(w) == (size(B, 1), size(B, 2)), when w is present.
  • csfm.Ms is not nothing, else an IsNothingError naming Ms is raised.
  • The field weighting names is not nothing, else an IsNothingError naming it is raised.

Returns

  • C::Matrix{<:Real}: factors × factors, symmetric, with a diagonal of 1.

Related

source
PortfolioOptimisers.exposure_icFunction
exposure_ic(B::Arr3Num, R::MatNum, w::Option{<:MatNum} = nothing;
            horizon::Integer = 1, rank::Bool = true) -> Matrix{<:Real}
exposure_ic(csfm::CrossSectionalFactorModel; horizon::Integer = 1, rank::Bool = true,
            reduced::Bool = false) -> Matrix{<:Real}

Return the information coefficient of every factor exposure, one row per pair of observations.

The information coefficient is the cross-sectional correlation between the exposure known at an observation and the mean asset return over the observations that follow it. It scores the exposure as a forecast of the return.

A risk factor with an information coefficient near zero is not a bad risk factor. A risk factor is built to explain the covariance and not to predict the mean, so read exposure_stability and the variance the factor contributes before you judge one, and never the information coefficient alone.

The level-2 method reconstructs the asset returns as $\mathbf{B}_{t-\ell} \boldsymbol{f}_{t} + \boldsymbol{\varepsilon}_{t}$, reads rw for the weights of the Pearson form, and answers on the raw factor axis. reduced maps the exposures through the family re-basis of the block first, and then the answer is on the reduced axis.

Mathematical definition

\[\mathrm{IC}_{tk} = \rho \left( \mathbf{B}_{t \cdot k}, \boldsymbol{y}_{t}, \boldsymbol{u}_{t} \right) \qquad y_{ti} = \frac{1}{H} \sum_{h=1}^{H} r_{t+h,i}\]

Where:

  • $\mathbf{B}_{t \cdot k}$: Cross-section of factor $k$ at observation $t$.
  • $\boldsymbol{y}_{t}$: Forward mean asset return of observation $t$.
  • $\rho$: The rank correlation when rank, and the weighted correlation otherwise.
  • $H$: Forward window, in observations.

Algorithm

  1. Resolve the weight history with exposure_weights.
  2. Build the forward mean return with exposure_forward_mean_return.
  3. Correlate each exposure against it, one observation at a time, with cs_spearman_correlation when rank and cs_weighted_correlation otherwise.

Arguments

  • B: Exposure history observations × assets × factors, unlagged.
  • R: Asset return history observations × assets, on the observation axis of B.
  • w: Cross-sectional weight history observations × assets, or nothing for equal weights. The rank form reads no weights.
  • horizon: Forward window, in observations.
  • rank: Take the rank correlation when true, and the weighted correlation otherwise.
  • csfm: A cross-sectional factor model block.
  • reduced: Map the exposures through the family re-basis of the block before the correlation.

Validation

  • !isempty(B).
  • size(R) == (size(B, 1), size(B, 2)).
  • horizon >= 1 and size(B, 1) > horizon.
  • csfm.Ms and csfm.csr are not nothing, else an IsNothingError naming the field is raised.

Returns

  • ic::Matrix{<:Real}: (observations - horizon) × factors. Row t scores the exposures of observation t against the returns that follow it.

Related

source
PortfolioOptimisers.exposure_ic_summaryFunction
exposure_ic_summary(ic::MatNum)
exposure_ic_summary(csfm::CrossSectionalFactorModel; horizon::Integer = 1,
                    rank::Bool = true, reduced::Bool = false)

Return the summary of an information coefficient series, one entry per factor.

The mean states the average score, the standard deviation states how much the score moves, their ratio states the score per unit of movement, the t-statistic states whether the mean is far enough from zero to believe over the observations that carried a score, and the hit rate states how often the score was positive. All five are read against the observations that carried a score: an observation whose score is NaN is one at which nothing was measured, not a miss, so it is in no denominator here, as it is in none of the library's other summaries. Whether a series was silenced often is a separate question, and the coverage of the series answers it.

Mathematical definition

\[\overline{\mathrm{IC}}_{k} = \frac{1}{|\mathcal{T}_{k}|} \sum_{t \in \mathcal{T}_{k}} \mathrm{IC}_{tk} \qquad \mathrm{IR}_{k} = \frac{\overline{\mathrm{IC}}_{k}}{s_{k}} \qquad t_{k} = \mathrm{IR}_{k} \sqrt{\left| \mathcal{T}_{k} \right|} \qquad \mathrm{hit}_{k} = \frac{1}{|\mathcal{T}_{k}|} \sum_{t \in \mathcal{T}_{k}} \mathbb{1}\left[\mathrm{IC}_{tk} > 0\right]\]

Where:

  • $\mathrm{IC}_{tk}$: Information coefficient of factor $k$ at observation $t$.
  • $\mathcal{T}_{k}$: The observations at which it is finite.
  • $s_{k}$: Its standard deviation over $\mathcal{T}_{k}$, with one degree of freedom removed.

Arguments

  • ic: Information coefficient series pairs × factors.
  • csfm: A cross-sectional factor model block.
  • horizon: Forward window, in observations.
  • rank: Take the rank correlation when true, and the weighted correlation otherwise.
  • reduced: Map the exposures through the family re-basis of the block before the correlation.

Validation

  • !isempty(ic).

Returns

  • summary::NamedTuple: (; mean_ic, std_ic, ic_ir, t_stat, hit_rate), each one entry per factor.

Related

source
PortfolioOptimisers.exposure_stabilityFunction
exposure_stability(B::Arr3Num, w::Option{<:MatNum} = nothing;
                   step::Integer = 21) -> Matrix{<:Real}
exposure_stability(csfm::CrossSectionalFactorModel; step::Integer = 21,
                   weighting::AbstractOrthogonalityMetric = BenchmarkWeightMetric()) -> Matrix{<:Real}

Return the stability of every factor exposure, one row per pair of observations.

The stability is the correlation of the cross-section of an exposure against its own cross-section step observations later. An exposure that ranks the assets the same way from one month to the next reads near 1, and one that re-orders them reads near 0. A risk factor needs a stable exposure, because a turnover of the exposure is a turnover of the portfolio that holds it.

The level-2 method answers on the raw factor axis, and it reads the unlagged history.

Mathematical definition

\[\mathrm{S}_{tk} = \rho \left( \mathbf{B}_{t \cdot k}, \mathbf{B}_{t + s, \cdot k}, \boldsymbol{u}_{t} \right)\]

Where:

  • $\mathbf{B}_{t \cdot k}$: Cross-section of factor $k$ at observation $t$.
  • $\boldsymbol{u}_{t}$: Cross-sectional weights of observation $t$, the earlier of the two.
  • $\rho$: The weighted correlation of two cross-sections.
  • $s$: Number of observations between the two cross-sections.

Arguments

  • B: Exposure history observations × assets × factors, unlagged.
  • w: Cross-sectional weight history observations × assets, or nothing for equal weights.
  • step: Number of observations between the two cross-sections.
  • csfm: A cross-sectional factor model block.
  • weighting: A member of AbstractOrthogonalityMetric. It names the weight history the block is read with, and cs_diagnostic_weights resolves it over the whole observation axis.

Validation

  • !isempty(B).
  • step >= 1 and size(B, 1) > step.
  • csfm.Ms is not nothing, else an IsNothingError naming Ms is raised.

Returns

  • S::Matrix{<:Real}: (observations - step) × factors.

Related

source
PortfolioOptimisers.exposure_dispersionFunction
exposure_dispersion(B::Arr3Num, w::Option{<:MatNum} = nothing) -> Matrix{<:Real}
exposure_dispersion(csfm::CrossSectionalFactorModel;
                    weighting::AbstractOrthogonalityMetric = BenchmarkWeightMetric()) -> Matrix{<:Real}

Return the weighted cross-sectional standard deviation of every factor exposure, one row per observation.

The level of the answer follows the standardisation the exposures were built under, so read the series and not the level: a collapse is a data feed that stopped, and a jump is one asset that ran away. An observation at which the factor carries no weight reads NaN.

The level-2 method answers on the raw factor axis, and it reads the unlagged history.

Mathematical definition

\[\mathrm{D}_{tk} = \sqrt{\sum_{i} \tilde{u}_{tik} \left( B_{tik} - \sum_{j} \tilde{u}_{tjk} B_{tjk} \right)^{2}}\]

Where:

  • $B_{tik}$: Exposure of asset $i$ to factor $k$ at observation $t$.
  • $\tilde{u}_{tik}$: Weight of asset $i$, zero where the exposure is not finite, and normalised over the assets to sum to one.

Arguments

  • B: Exposure history observations × assets × factors, unlagged.
  • w: Cross-sectional weight history observations × assets, or nothing for equal weights.
  • csfm: A cross-sectional factor model block.
  • weighting: A member of AbstractOrthogonalityMetric. It names the weight history the block is read with, and cs_diagnostic_weights resolves it over the whole observation axis.

Validation

  • !isempty(B).
  • csfm.Ms is not nothing, else an IsNothingError naming Ms is raised.

Returns

  • D::Matrix{<:Real}: observations × factors.

Related

source
PortfolioOptimisers.exposure_coverageFunction
exposure_coverage(B::Arr3Num, w::Option{<:MatNum} = nothing) -> Vector{<:Real}
exposure_coverage(csfm::CrossSectionalFactorModel;
                  weighting::AbstractOrthogonalityMetric = BenchmarkWeightMetric()) -> Vector{<:Real}

Return the coverage of every factor exposure, one entry per factor.

The coverage is the share of the universe at which the exposure is finite, averaged over the observations. The universe of an observation is the assets that carry a positive weight, and it is every asset when no weight history is read. A factor whose coverage falls has lost its panel field on part of the universe, and the fit of that observation then rests on fewer assets than the caller believes.

The level-2 method answers on the raw factor axis, and it reads the unlagged history.

Mathematical definition

\[\mathrm{c}_{k} = \frac{1}{T} \sum_{t=1}^{T} \frac{\left| \left\{ i \in \mathcal{U}_{t} : B_{tik} \text{ is finite} \right\} \right|}{\left| \mathcal{U}_{t} \right|}\]

Where:

  • $B_{tik}$: Exposure of asset $i$ to factor $k$ at observation $t$.
  • $\mathcal{U}_{t}$: Universe of observation $t$, the assets of positive weight.
  • $T$: Number of observations.

Arguments

  • B: Exposure history observations × assets × factors, unlagged.
  • w: Cross-sectional weight history observations × assets, or nothing for every asset.
  • csfm: A cross-sectional factor model block.
  • weighting: A member of AbstractOrthogonalityMetric. It names the weight history the universe is read off, and cs_diagnostic_weights resolves it over the whole observation axis.

Validation

  • !isempty(B).
  • csfm.Ms is not nothing, else an IsNothingError naming Ms is raised.

Returns

  • c::Vector{<:Real}: One entry per factor, between 0 and 1. An observation whose universe is empty contributes 0.

Related

source