Exponentially Weighted Covariance
Types
PortfolioOptimisers.ExpWeightedCovariance — Type
struct ExpWeightedCovariance{__T_decay, __T_min_obs, __T_centred, __T_cache} <: AbstractCovarianceEstimatorEstimates a covariance matrix by an exponentially weighted recursion that freezes on a holiday and resets on an inactive period.
Each observation updates the sub-block of the assets that are valid at it, so a gap never reaches an entry it did not touch. The recursion is seeded at zero, and the output divides out the damping that the cold start costs through a congruence transform, which keeps the state positive semidefinite and leaves every correlation unchanged.
Keeping a young asset investable has a cost the prior pays for it. A prior fitted with this estimator zero-fills the rows the asset was missing through scenario_fill, because every consumer of a Prior Result reads its returns matrix; a scenario-based measure then reads a zero return where the asset had none and understates that asset's risk over those rows, while the covariance stays the estimate this recursion made from the rows it saw. The fill is silent at or below the fitting prior's own fill_limit field, a share of that asset's own observations, warns above it, and refuses any fill under strict; fill_limit defaults to nothing, and this family carries no CoveragePolicy to derive a limit from, so every fill is named.
Fields
decay: Exponential decay factor for the exponentially weighted estimator.
min_obs: Minimum number of observations required before the estimator produces a valid result.
centred: Whether to treat the returns as pre-centred (mean zero). Iffalse, the location is estimated online.
cache: Running state of an incremental fit, ornothingbefore the first call topartial_fit!. It is the one Result this estimator holds, and its type bound is the enforcement of that exception. A fit over a matrix ignores it.
Constructors
ExpWeightedCovariance(; decay::Number = exp2(-inv(40.0)), min_obs::Integer = round(Int, max(1, inv(log2(inv(decay))))), centred::Bool = false, cache::Option{<:AbstractPartialFitState} = nothing) -> ExpWeightedCovarianceKeywords correspond to the struct's fields.
Validation
decay > 0.min_obs > 0.
Mathematical definition
The internal state after the valid observations of each asset is
\[S = (1 - \lambda) \sum_{k} \lambda^{k} e_{k} e_{k}^{\top},\]
taken on the sub-block of the assets that are valid at each observation, and the reported covariance is the congruence transform
\[\hat{\Sigma} = D S D, \qquad D = \operatorname{diag}\left(\frac{1}{\sqrt{1 - \lambda^{n_i}}}\right).\]
Where:
- $\lambda$:
decay. - $e_{k}$: the returns of the valid assets at the observation $k$, less the running location where
centredisfalse. - $n_i$: the count of valid observations of asset $i$.
A congruence transform preserves positive semidefiniteness, so $\hat{\Sigma}$ inherits the property from $S$, and it cancels in a correlation, so the correction moves no correlation. The correction is the square root of $1 - \lambda^{n_i}$, where a first-moment estimator uses the first power.
Examples
julia> ce = ExpWeightedCovariance();julia> ce.decay ≈ exp2(-inv(40.0))truejulia> ce.min_obs40Related
Functions
Statistics.cov — Method
Statistics.cov(
ce::ExpWeightedCovariance,
X::MatNum;
dims::Int = 1,
active_mask::Option{<:AbstractMatrix{<:Bool}} = nothing,
kwargs...
) -> MatNumCompute the exponentially weighted covariance matrix.
Iterates over the observation dimension of X, updating an online covariance cache at each step, then applies the congruence correction and blanks every asset that is not ready.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.dims: Dimension along which to perform the computation.active_mask: Optional boolean matrix with the same size asX. An asset whose entry isfalseis inactive at that observation: its row and column are reset and its answer isNaNwhile it stays inactive. Withnothingevery asset is active, so a non-finite return reads as a holiday and every entry that touches the asset freezes.kwargs: Additional keyword arguments (ignored).
Validation
dims in (1, 2).- If
active_maskis notnothing,size(X) == size(active_mask).
Returns
sigma::MatNum: Covariance matrix of sizeassets × assets. The row and the column of an asset with fewer thance.min_obsvalid observations areNaN.
Examples
julia> X = [0.01 -0.02; -0.015 0.03; 0.02 -0.01; -0.005 0.012];julia> ce = ExpWeightedCovariance(; decay = 0.9, min_obs = 2);julia> size(cov(ce, X))(2, 2)Related
Statistics.cor — Method
Statistics.cor(
ce::ExpWeightedCovariance,
X::MatNum;
dims::Int = 1,
active_mask::Option{<:AbstractMatrix{<:Bool}} = nothing,
kwargs...
) -> MatNumCompute the exponentially weighted correlation matrix.
This is the covariance of the same call, rescaled to a unit diagonal. The congruence correction cancels in the rescale, so the correlation is the correlation of the raw state.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.dims: Dimension along which to perform the computation.active_mask: Optional boolean matrix with the same size asX.kwargs: Additional keyword arguments (ignored).
Returns
rho::MatNum: Correlation matrix of sizeassets × assets. The row and the column of an asset that is not ready areNaN, and so is its diagonal entry.
Related
ExpWeightedCovarianceStatistics.cov(ce::ExpWeightedCovariance, X::MatNum; dims::Int = 1, active_mask::Option{<:AbstractMatrix{<:Bool}} = nothing, kwargs...)regime_adjusted_correlation: the shared rescale, which keeps a blanked assetNaNon the diagonal.
Statistics.cov — Method
Statistics.cov(
ce::ExpWeightedCovariance,
X::MatNum,
pnl::Option{<:AssetPanel};
dims::Int = 1,
kwargs...
) -> MatNumCompute the exponentially weighted covariance from a window of an Asset Panel.
This estimator is mask-aware, so it overrides the reduce-and-expand root of the verb and reads the panel's active mask itself. The answer therefore lives on the whole universe rather than on the Coverage Universe: a young asset that lists inside the window is answered from the observations it has, and it is NaN only while it stays below ce.min_obs.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.pnl: OptionalAssetPanel, whose active mask the Coverage Universe of the fit is derived from.nothingmakes the rule finiteness alone.dims: Dimension along which to perform the computation.kwargs: Additional keyword arguments (ignored).
Returns
sigma::MatNum: Covariance matrix of sizeassets × assets.
Related
Statistics.cor — Method
Statistics.cor(
ce::ExpWeightedCovariance,
X::MatNum,
pnl::Option{<:AssetPanel};
dims::Int = 1,
kwargs...
) -> MatNumCompute the exponentially weighted correlation from a window of an Asset Panel.
This is the covariance of the same call, rescaled to a unit diagonal, and it reads the panel's active mask through the same override.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.pnl: OptionalAssetPanel, whose active mask the Coverage Universe of the fit is derived from.nothingmakes the rule finiteness alone.dims: Dimension along which to perform the computation.kwargs: Additional keyword arguments (ignored).
Returns
rho::MatNum: Correlation matrix of sizeassets × assets.
Related
Statistics.var — Method
Statistics.var(
ce::ExpWeightedCovariance,
X::MatNum,
pnl::Option{<:AssetPanel};
dims::Int = 1,
kwargs...
) -> MatNumCompute the marginal exponentially weighted variance from a window of an Asset Panel.
This is the diagonal of the covariance of the same call, and it reads the panel's active mask through the same override.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.pnl: OptionalAssetPanel, whose active mask the Coverage Universe of the fit is derived from.nothingmakes the rule finiteness alone.dims: Dimension along which to perform the computation.kwargs: Additional keyword arguments (ignored).
Returns
var::MatNum: Marginal variance, as a row wheredimsis1and as a column otherwise.
Related
Statistics.std — Method
Statistics.std(
ce::ExpWeightedCovariance,
X::MatNum,
pnl::Option{<:AssetPanel};
dims::Int = 1,
kwargs...
) -> MatNumCompute the marginal exponentially weighted volatility from a window of an Asset Panel.
This is the square root of the diagonal of the covariance of the same call, and it reads the panel's active mask through the same override.
Arguments
ce: Exponentially weighted covariance estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.pnl: OptionalAssetPanel, whose active mask the Coverage Universe of the fit is derived from.nothingmakes the rule finiteness alone.dims: Dimension along which to perform the computation.kwargs: Additional keyword arguments (ignored).
Returns
std::MatNum: Marginal volatility, as a row wheredimsis1and as a column otherwise.
Related
PortfolioOptimisers.partial_fit! — Method
partial_fit!(
est::Union{AbstractEstimator, CovarianceEstimator},
X::Union{AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}};
dims,
active_mask,
estimation_mask
) -> Any
Folds observations into the sample buffer an estimator carries.
The buffering arm of partial_fit!, and the method every estimator carrying a SampleBufferState reaches. A family that folds exactly writes methods of its own, and each of them narrows the cache type parameter of its own estimator to the state that fold reads, so a buffer never meets them and this method is what remains. The state's type is therefore the whole route, and nothing refuses the step.
It is one method over both arms of the interface rather than two, because the families that refuse the step declare one method over both arms too, and a pair of narrower methods here would be ambiguous against each of them. So the arm is chosen by the type of X inside the body, which is statically resolved at every call site.
A buffer carries the per-observation masks beside the observations, so a CoveragePolicy mask threads through the wrapper as it does through an estimator's own accumulator, and the read-out hands it back to the batch verb. A wrapped estimator folded under a policy therefore answers what a batch fit over the same window under the same policy answers, and the unwrapped and wrapped paths agree.
Algorithm
- Read the buffer out of the
cachefield withassert_sample_buffer, which refuses an estimator that was never wrapped inOnline. - Fold a matrix and its masks through the block arm of
partial_fit!, and a vector and its masks through the single-observation arm. - Rebind
est.cachewithAccessors.@reset, and return the estimator.
Arguments
est: Estimator whose buffer is folded forward.X: Observations to fold. A matrix holds one observation per row whendims == 1, and one per column whendims == 2. A vector is a single observation across the assets, anddimsis ignored.dims: Dimension along which to perform the computation.active_mask: The active mask of the block, of the shape ofX, or of one entry per asset whenXis one observation, ornothing.estimation_mask: The estimation mask, on the same terms asactive_mask.
Validation
estcarries aSampleBufferState. AnArgumentErroris thrown otherwise.- The masks, when they are not
nothing, have the shape ofX. ADimensionMismatchis thrown otherwise. - A buffer holding observations is given the masks it already records. An
ArgumentErroris thrown otherwise. dims in (1, 2).
Returns
est: The estimator, with itscachefield rebound to the buffer after the last observation.
Related
PortfolioOptimisers.partial_fit! — Method
partial_fit!(
est::Union{AbstractEstimator, CovarianceEstimator},
X::Union{AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}};
dims,
active_mask,
estimation_mask
) -> Any
Folds observations into the sample buffer an estimator carries.
The buffering arm of partial_fit!, and the method every estimator carrying a SampleBufferState reaches. A family that folds exactly writes methods of its own, and each of them narrows the cache type parameter of its own estimator to the state that fold reads, so a buffer never meets them and this method is what remains. The state's type is therefore the whole route, and nothing refuses the step.
It is one method over both arms of the interface rather than two, because the families that refuse the step declare one method over both arms too, and a pair of narrower methods here would be ambiguous against each of them. So the arm is chosen by the type of X inside the body, which is statically resolved at every call site.
A buffer carries the per-observation masks beside the observations, so a CoveragePolicy mask threads through the wrapper as it does through an estimator's own accumulator, and the read-out hands it back to the batch verb. A wrapped estimator folded under a policy therefore answers what a batch fit over the same window under the same policy answers, and the unwrapped and wrapped paths agree.
Algorithm
- Read the buffer out of the
cachefield withassert_sample_buffer, which refuses an estimator that was never wrapped inOnline. - Fold a matrix and its masks through the block arm of
partial_fit!, and a vector and its masks through the single-observation arm. - Rebind
est.cachewithAccessors.@reset, and return the estimator.
Arguments
est: Estimator whose buffer is folded forward.X: Observations to fold. A matrix holds one observation per row whendims == 1, and one per column whendims == 2. A vector is a single observation across the assets, anddimsis ignored.dims: Dimension along which to perform the computation.active_mask: The active mask of the block, of the shape ofX, or of one entry per asset whenXis one observation, ornothing.estimation_mask: The estimation mask, on the same terms asactive_mask.
Validation
estcarries aSampleBufferState. AnArgumentErroris thrown otherwise.- The masks, when they are not
nothing, have the shape ofX. ADimensionMismatchis thrown otherwise. - A buffer holding observations is given the masks it already records. An
ArgumentErroris thrown otherwise. dims in (1, 2).
Returns
est: The estimator, with itscachefield rebound to the buffer after the last observation.
Related
Statistics.cov — Method
Statistics.cov(ce::ExpWeightedCovariance, state::ExpWeightedCovarianceState; kwargs...) -> MatNumRead the exponentially weighted covariance out of a state the caller holds.
Arguments
ce: Exponentially weighted covariance estimator.state::ExpWeightedCovarianceState: The state to read.kwargs: Additional keyword arguments (ignored).
Returns
sigma::MatNum: Covariance matrix.
Related
Statistics.cov — Method
Statistics.cov(ce::ExpWeightedCovariance; kwargs...) -> MatNumRead the exponentially weighted covariance out of the estimator's own state.
The one-argument form is what an incremental fit answers: partial_fit! leaves the state in the cache field, and this verb turns it into the ordinary answer. An estimator that has been given no observation carries no state, so the call is refused rather than answered with a zero.
Arguments
ce: Exponentially weighted covariance estimator carrying a state.kwargs: Additional keyword arguments (ignored).
Validation
ce.cacheis notnothing. AnArgumentErroris thrown otherwise.
Returns
sigma::MatNum: Covariance matrix of sizeassets × assets.
Examples
julia> X = [0.01 -0.02; -0.015 0.03; 0.02 -0.01; -0.005 0.012];julia> ce = partial_fit!(ExpWeightedCovariance(; decay = 0.9, min_obs = 2), X);julia> size(cov(ce))(2, 2)julia> cov(ExpWeightedCovariance())ERROR: ArgumentError: `ce` holds no partial-fit state, so there is nothing to read. Call `partial_fit!(ce, X)` first, or `cov(ce, X)` for a fit over a whole sample.[...]Related
Statistics.cor — Method
Statistics.cor(ce::ExpWeightedCovariance, state::ExpWeightedCovarianceState; kwargs...) -> MatNumRead the exponentially weighted correlation out of a state the caller holds.
Arguments
ce: Exponentially weighted covariance estimator.state::ExpWeightedCovarianceState: The state to read.kwargs: Additional keyword arguments (ignored).
Returns
rho::MatNum: Correlation matrix.
Related
Statistics.cor — Method
Statistics.cor(ce::ExpWeightedCovariance; kwargs...) -> MatNumRead the exponentially weighted correlation out of the estimator's own state.
Arguments
ce: Exponentially weighted covariance estimator carrying a state.kwargs: Additional keyword arguments (ignored).
Validation
ce.cacheis notnothing. AnArgumentErroris thrown otherwise.
Returns
rho::MatNum: Correlation matrix of sizeassets × assets.
Related
PortfolioOptimisers.merge_states — Method
merge_states(
a::ExpWeightedCovarianceState,
b::ExpWeightedCovarianceState
)
Refuses to merge two ExpWeightedCovarianceState.
An exponentially weighted state folds forward exactly, S = λ^{n_b} S_a + S_b, but only while no asset resets inside the second block. The state records the count that a reset zeroed and not the reset itself, so the two cases are indistinguishable after the fact and a merge would silently keep a history the reset discarded. Fold the second block into the first with partial_fit! instead.
Arguments
a: State of the first block.b: State of the second block.
Validation
- The pair is refused with an
ArgumentError.
Related