Separate portfolio bias: private API

Functions

PortfolioOptimisers.portfolio_regime_bias — Function
portfolio_regime_bias(::Nothing, ce::RegimeAdjustedExpWeightedCovariance, target::PortfolioTarget, C::MatNum, idx::AbstractVector{<:Integer}, K::Integer)
portfolio_regime_bias(bias::NamedTuple, ce::RegimeAdjustedExpWeightedCovariance, target::PortfolioTarget, C::MatNum, idx::AbstractVector{<:Integer}, K::Integer)

Returns the bias factor of each direction of the portfolio target, at K observations.

On one decay every direction reads the table of regime_bias! at decay, on the law of the centring of the estimator. On the separate correlation path the estimated variance of a direction over its true variance, $Q$, reads the variances at $\lambda$ = decay and the correlation at $\lambda_{c}$ = cor_decay, so each direction mixes the two pre-centred tables by a weight $\theta$ and divides by $1 + \mu$, the shift of the mean of $Q$, from separate_direction_moments:

\[\begin{align} f_{p} &= \frac{f_{\lambda_{c}} + \theta_{p} \left(f_{\lambda} - f_{\lambda_{c}}\right)}{1 + \mu_{p}}\,. \end{align}\]

To the second order $Q - 1$ is $\sum_{t} w^{c}_{t} (u_{t}^{2} - 1) + \sum_{t} (w_{t} - w^{c}_{t}) \xi_{t}$, with $u_{t}$ the standardised return of the direction and $\xi_{t}$ the weighted mean of the squared standardised returns of its assets, so the variance of $Q$ is the variance of one table at $\lambda_{c}$ plus the terms that $\theta$ carries. The tables read every moment of their own law, and the mix holds the second order of each method. The mean of $Q$ moves at the second order by the bias of the estimated correlation, by the root of the two estimated variances, and by their covariance, which $\mu$ holds.

On iid Normal returns at 4, 8 and 12 assets, half-lives of 10, 5 and 20 against correlation half-lives of 20, 40 and 10, $R = I$, equicorrelations of 0.5 and 0.8 and a three-factor model, and an equal-weight and an inverse-volatility direction, the true factor of the steady state over the factor of one table at cor_decay reads 0.9716 to 1.1076. Over the mixed factor it reads 0.9940 to 1.0023 for the three methods. Through the estimator, at 8 assets, an equicorrelation of 0.5, half-lives of 10 and 20, either centring and both directions, the squared multiplier of the separate path is within 0.07 % of the multiplier of one decay on the same data over 3 seeds of 120 000 rows, where the factor at cor_decay alone read 1.6 % to 2.6 % above it. In the warm-up the factor holds no second order of the estimated correlation. Under the default cor_vol = BiasCorrectedVolatility(), at 12 assets, an equicorrelation of 0.5, half-lives of 10 and 20 and 20 observations, the ratio is 1.0172, and 1.0095 at 40. At $R = I$ it is within 0.05 % at 20 observations. Under RawVolatility() the correlation state weights its first products by $1 / (1 - \lambda^{t})$, which the factor does not hold, and the same ratios read 1.0802 and 1.0448. Under HAC the three tables and the weight sums read the Bartlett weights of hac_lags. At two lags and equal weights, on the same data, the separate path reads 0.06 % to 0.89 % below one decay for the root mean square, 0.07 % to 0.61 % for the first moment and 0.05 % to 0.35 % for the log, where the factor at cor_decay alone read 3.1 % to 5.1 % above it. That measure left out the first scored row of a zero block, which the statistic now skips.

Arguments

  • bias: The store of regime_bias_store, or nothing where the estimator takes no correction.
  • ce::RegimeAdjustedExpWeightedCovariance: Covariance estimator configuration.
  • target::PortfolioTarget: Portfolio regime-adjustment target.
  • C::MatNum: Bias-corrected covariance block of the contributing assets.
  • idx::AbstractVector{<:Integer}: Index of those assets in the universe.
  • K::Integer: Count of observations in the estimate.

Returns

  • f::Union{<:Number, <:VecNum}: One where bias is nothing, the factor of every direction on one decay, and the factor of each direction on the separate path.

Related

source
PortfolioOptimisers.separate_direction_moments — Function
separate_direction_moments(
    C::AbstractMatrix{<:Union{var"#s152", var"#s69"} where {var"#s152"<:Number, var"#s69"<:AbstractJuMPScalar}},
    p::AbstractVector,
    min_val::Number,
    sums::NamedTuple
) -> Tuple{Any, Any}

Computes the weight of the table at decay and the shift of the mean of the estimated variance of one direction on the separate correlation path, from the block that stands for the true covariance.

With $a = D p$, $D$ the volatilities and $R$ the correlation of the block, the share of the noise of the variances in the variance of the direction is $\gamma$ alone, and in its square $\beta$. Both are one at one asset and at a correlation of one, where the direction reads the variance alone, and $1 / n$ at $R = I$ and equal $a$, where the noise of $n$ variances averages out.

Mathematical definition

\[\begin{align} b_{i} &= \frac{a_{i} (R a)_{i}}{a^{\top} R a}\,, \quad \gamma = \sum_{i} b_{i} \frac{(R a)_{i}^{2}}{a^{\top} R a}\,, \quad \beta = \sum_{i, j} b_{i} b_{j} R_{ij}^{2}\,, \\ \theta &= \frac{2 \gamma A + \beta B}{2 A + B}\,, \quad \mu = \frac{\sum_{i \neq j} a_{i} a_{j} R_{ij} (1 - R_{ij}^{2})}{a^{\top} R a}\, S\,. \end{align}\]

Where:

Arguments

  • C::MatNum: Bias-corrected covariance block of the contributing assets.
  • p::AbstractVector: Weights of the direction over those assets.
  • min_val::Number: Floor applied to each variance.
  • sums::NamedTuple: mix, spread and shift of separate_path_sums!.

Returns

  • (theta, mu)::Tuple{<:Number, <:Number}: The weight $\theta$ and the shift $\mu$, both zero where the variance of the direction is not positive.

Related

source
PortfolioOptimisers.separate_path_sums! — Function
separate_path_sums!(
    conv::AbstractVector{<:Union{var"#s152", var"#s69"} where {var"#s152"<:Number, var"#s69"<:AbstractJuMPScalar}},
    ce::RegimeAdjustedExpWeightedCovariance,
    K::Integer
) -> NamedTuple{(:mix, :spread, :shift), <:Tuple{Any, Any, Any}}

Returns the three sums of the weights that separate_direction_moments reads at K observations, and grows the table of the convolution where the count is past its end.

The variance at $\lambda$ = decay and the correlation at $\lambda_{c}$ = cor_decay give the sums of the products of their weights, $s_{\lambda\lambda}$, $s_{\lambda\lambda_{c}}$ and $s_{\lambda_{c}\lambda_{c}}$, from exp_weight_cross_sum. The correlation state divides each product by the running volatility, whose noise reaches the correlation through the convolution of the two sets of weights, $Y$ of exp_weight_convolution_table. The table grows as the tables of regime_bias! do. Under HAC each sum reads the weights $k_{i}$ of hac_lags: a lag $i$ adds $2 k_{i}^{2}$ times the part of the sum whose older observation is in the window, as exp_weight_cross_sum does, and the convolution weights depend on $K$ only through their normalisation.

Mathematical definition

\[\begin{align} A &= s_{\lambda\lambda_{c}} - s_{\lambda_{c}\lambda_{c}}\,, \quad B = s_{\lambda\lambda} - 2 s_{\lambda\lambda_{c}} + s_{\lambda_{c}\lambda_{c}}\,, \quad S = s_{\lambda\lambda_{c}} - s_{\lambda\lambda} - \frac{s_{\lambda_{c}\lambda_{c}} - Y^{h}_{K}}{2}\,, \\ Y^{h}_{K} &= Y_{K} + 2 \sum_{i=1}^{\min(L, K - 1)} k_{i}^{2} Y_{K-i} \left(\frac{1 - \lambda_{c}^{K-i}}{1 - \lambda_{c}^{K}}\right)^{2}\,. \end{align}\]

Where:

Arguments

  • conv::VecNum: The table of $Y$ of the state (mutated where it grows).
  • ce::RegimeAdjustedExpWeightedCovariance: Covariance estimator configuration, on the separate path.
  • K::Integer: Count of observations in the estimate.

Returns

  • sums::NamedTuple: mix $A$, spread $B$ and shift $S$.

Related

source
PortfolioOptimisers.exp_weight_convolution_table — Function
exp_weight_convolution_table(
    decay::Number,
    cor_decay::Number,
    len::Integer
) -> Any

Computes the sum of the squares of the convolution of two sets of exponential weights, for every count of observations from one to len.

The weight of the observation $m$ steps before the newest in the correlation state, through the running variance it divides by, is $c_{m} = \sum_{l=0}^{m} w^{c}_{l} w_{m-l}$. The recursion of $h_{m}$ has no difference of the two decays, so it holds where they are close.

Mathematical definition

\[\begin{align} Y_{K} &= \sum_{m=0}^{K-1} c_{m}^{2} = \frac{(1 - \lambda)^{2} (1 - \lambda_{c})^{2}}{(1 - \lambda_{c}^{K})^{2}} \sum_{m=0}^{K-1} h_{m}^{2}\,, \quad h_{m} = \lambda h_{m-1} + \lambda_{c}^{m}\,, \quad h_{0} = 1\,. \end{align}\]

Where:

  • $w_{l} = (1 - \lambda) \lambda^{l}$: Weight of the running variance at $\lambda$ = decay.
  • $w^{c}_{l} = (1 - \lambda_{c}) \lambda_{c}^{l} / (1 - \lambda_{c}^{K})$: Normalised weight of the correlation at $\lambda_{c}$ = cor_decay.

Arguments

  • decay::Number: Decay of the variance.
  • cor_decay::Number: Decay of the correlation.
  • len::Integer: Largest count of observations in the table.

Returns

  • table::VecNum: $Y_{K}$ for each count from one to len.

Related

source
PortfolioOptimisers.regime_covariance_block — Function
regime_covariance_block(
    cache::RegimeAdjustedCovarianceState,
    ce::RegimeAdjustedExpWeightedCovariance,
    idx::AbstractVector{<:Integer}
) -> Any

Returns the bias-corrected covariance block of the assets that contribute to the regime statistic.

This block calibrates the smoother and is never reported, so it takes no repair: the Cholesky factor of the Mahalanobis target refuses a block that is not positive definite. Where one decay runs, each entry is divided by the weight its pair holds with pair_weighted_block. Where the separate path runs, the correlation already sits inside cache.covariance, each pair normalised by its weight, and the block applies the per-asset variance correction alone. bias_corrected_covariance makes the reported estimate from the state.

Arguments

  • cache::RegimeAdjustedCovarianceState: Online covariance computation cache.
  • ce::RegimeAdjustedExpWeightedCovariance: Covariance estimator configuration.
  • idx::AbstractVector{<:Integer}: Index of the assets that contribute to the statistic.

Returns

  • sigma::MatNum: The bias-corrected covariance block of those assets.

Related

source
PortfolioOptimisers.regime_block_built — Function
regime_block_built(
    cache::RegimeAdjustedCovarianceState,
    ce::RegimeAdjustedExpWeightedCovariance
) -> Any

Mask of the assets whose row of the covariance state the regime statistic can read.

Where cor_decay is nothing, the state steps at every observation, so every asset is built. On the separate path, update_var_cor! writes the block only when the diagonal of the correlation state is above min_val. Under HAC an asset contributes nothing on its first row, so the block can stay at its zero seed for the first rows. A written asset has a diagonal equal to its variance state, so an asset with a positive variance and a zero diagonal is not built. A constant asset has a variance of zero, and it stays built: its row is zero by design.

Arguments

  • cache::RegimeAdjustedCovarianceState: Online covariance computation cache.
  • ce::RegimeAdjustedExpWeightedCovariance: Covariance estimator configuration.

Returns

  • built::Union{Bool, AbstractVector{<:Bool}}: true on the path with one decay, else the mask of the built assets.

Related

source
PortfolioOptimisers.portfolio_directions — Function
portfolio_directions(
    target::PortfolioTarget,
    C::AbstractMatrix{<:Union{var"#s152", var"#s69"} where {var"#s152"<:Number, var"#s69"<:AbstractJuMPScalar}},
    idx::AbstractVector{<:Integer},
    min_val::Number
) -> Any

Builds the portfolio directions of the regime statistic over the contributing assets, one direction to a row, each row summing to one.

Where target.w is nothing, one inverse-volatility direction is rebuilt from the block at every observation, which neutralises the dispersion of the volatilities so that a loud asset does not carry the statistic on its own. Where target.w holds weights, each row of that matrix is one direction, its entries are restricted to the assets that contribute, and a row that keeps no weight is dropped.

Arguments

  • target::PortfolioTarget: Portfolio regime-adjustment target.
  • C::MatNum: Bias-corrected covariance block of the contributing assets.
  • idx::AbstractVector{<:Integer}: Index of those assets in the universe.
  • min_val::Number: Floor applied to each variance.

Returns

  • W::Option{<:MatNum}: The directions, or nothing where no row keeps a positive weight.

Related

source