Gerber Information Quality Covariance: private API

PortfolioOptimisers.GerberIQEpsType
const GerberIQEps = Union{<:Number, <:Function, <:GerberIQEpsEstimator}

A type alias for the union of Number, Function, and GerberIQEpsEstimator used for Gerber Information Quality lookback and delay parameter definitions.

The three arms are the three ways to supply the delay. A Number is the delay itself. A Function computes it from the returns matrix. A GerberIQEpsEstimator computes it through gerber_iq_eps.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.GerberIQGammaType
const GerberIQGamma = Union{<:Number, Function, <:GerberIQGammaEstimator}

A type alias for the union of Number, Function, and GerberIQGammaEstimator used for Gerber Information Quality temporal decay parameter definitions.

The three arms are the three ways to supply the decay rate. A Number is the rate itself. A Function computes it from the returns matrix. A GerberIQGammaEstimator computes it through gerber_iq_gamma.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.GerberIQScalerType
const GerberIQScaler = Union{Function, <:GerberIQScalerEstimator}

A type alias for the union of Function, and GerberIQScalerEstimator used for scaling the threshold parameters for defining significant co-movements in Gerber Information Quality.

The alias has no Number arm, because a scaler is a rule over the pair's two standard deviations and not a value. A Function takes sdi and sdj and returns the two scaled values. A GerberIQScalerEstimator does the same through gerber_iq_scaling.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.GerberIQKernelType
struct GerberIQKernel{T1<:GerberCovarianceAlgorithm, T2<:GerberIQCovarianceAlgorithm, T3<:GerberIQDecayEstimator, T4, T5<:Number, T6<:(AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}})}

Co-movement policy for gerber_comovement! implementing the Gerber IQ family.

Observations are thresholded against the pair's scaled thresholds from gerber_iq_scaling, classified by the sign of the product of returns, and weighted by the IQ noise-compression template and temporal decay via gerber_IQ_delta. The alg marker selects the denominator policy (comovement_ratio).

Fields

  • alg: Gerber algorithm marker selecting the denominator policy.
  • kind: Gerber IQ noise-compression template.
  • decay: Regenerated temporal decay estimator.
  • sc: Threshold scaling factor estimator.
  • c: Small co-movement threshold.
  • sd: Vector of asset standard deviations.

Related

source
PortfolioOptimisers.clamp_gerber_iq_nFunction
clamp_gerber_iq_n(
    kind::GerberIQCovarianceAlgorithm,
    args...
) -> PartialGerberIQ{<:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number}

No-op for Gerber Information Quality covariance estimation algorithms that do not need their noise suppression parameters clamped.

This fall-through catches every pairing of a template with a GerberCovarianceAlgorithm that the file does not clamp. Two cases reach it. Gerber0 and Gerber1 divide the net weighted vote by a sum of the same weights, so the statistic lies in $[-1, 1]$ whatever the template holds. BasicGerberIQ carries one weight and its square, and neither can exceed the geometric mean of the two diagonal weights that flank it, so it needs no clamp under Gerber2 either.

Arguments

Returns

  • kind: The input kind instance.

Related

source
clamp_gerber_iq_n(
    alg::PartialGerberIQ,
    _::Gerber2
) -> PartialGerberIQ{<:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number}

Lowers the mixed-magnitude weights of a PartialGerberIQ template so that the Gerber2 statistic stays inside [-1, 1]. It does not make the matrix positive definite; that is pdm's work.

Under Gerber2 the pairwise entry is the net pos - neg, divided by the geometric mean of the pair's two diagonal projections. A projection compares an asset with itself, so it is always concordant and it names a same-magnitude weight. The source proves that the ratio is bounded by one if and only if every weight that joins two distinct magnitude classes is at most the geometric mean of the two same-magnitude weights of those classes. Six of the ten weights join two classes, and this method lowers each onto its bound. With n1 = n4 = 0.2 an unclamped n7 = 1.0 returns 5.0, and the clamp brings it to 1.0.

The four discordant weights need a case split. A concordant pair is judged against dcp and dcn, and a discordant pair against ddp and ddn, so a return beyond a discordant boundary need not be beyond the concordant one that fixes its diagonal class. When the two boundaries disagree the method takes the smaller of the two candidate weights, which is the bound that holds for either class.

Note

The clamp is a necessary and a sufficient condition on the template, and it is the whole condition. sc needs no restriction, because Gerber2 reads its denominator in the pair's own units. #494 and #500 are the two defects this closes. The shipped defaults sit exactly on the bound, so the clamp does not move them.

Mathematical definition

Write $h^{+}$ and $l^{+}$ for the diagonal weights of the two positive classes a discordant channel can meet, and $h^{-}$ and $l^{-}$ for the negative pair:

\[\begin{align} h^{+} &= \begin{cases} n_{4} & \delta_{d}^{+} \geq \delta_{c}^{+} \\ \min(n_{1}, n_{4}) & \text{otherwise} \end{cases}\,, &l^{+} &= \begin{cases} n_{1} & \delta_{d}^{+} \leq \delta_{c}^{+} \\ \min(n_{1}, n_{4}) & \text{otherwise} \end{cases}\,, \end{align}\]

with $h^{-}$ and $l^{-}$ the same rule on $n_{5}$, $n_{2}$, $\delta_{d}^{-}$ and $\delta_{c}^{-}$. The six bounds are then

\[\begin{align} n_{7} &\leftarrow \min\left(n_{7},\, \sqrt{n_{1} n_{4}}\right)\,, & n_{8} &\leftarrow \min\left(n_{8},\, \sqrt{n_{2} n_{5}}\right)\,, \\ n_{6} &\leftarrow \min\left(n_{6},\, \sqrt{h^{+} h^{-}}\right)\,, & n_{9} &\leftarrow \min\left(n_{9},\, \sqrt{h^{+} l^{-}}\right)\,, \\ n_{10} &\leftarrow \min\left(n_{10},\, \sqrt{l^{+} h^{-}}\right)\,, & n_{3} &\leftarrow \min\left(n_{3},\, \sqrt{l^{+} l^{-}}\right)\,. \end{align}\]

Where:

  • $n_{1}$, $n_{4}$: Small and large positive concordant weights.
  • $n_{2}$, $n_{5}$: Small and large negative concordant weights.
  • $\delta_{c}^{\pm}$, $\delta_{d}^{\pm}$: The concordant and discordant boundaries, dcp/dcn and ddp/ddn.

Algorithm

  1. Resolve the four discordant diagonal weights hip, lop, hin and lon from the two pairs of boundaries.
  2. Lower n7 and n8, the two mixed concordant channels, onto the geometric mean of the two weights they join.
  3. Lower n3, n6, n9 and n10, the four discordant channels, onto the geometric mean of the resolved weights they join.
  4. Return a new PartialGerberIQ carrying the six lowered weights and every other field unchanged.

Arguments

Returns

  • kind::PartialGerberIQ: A new template. The method allocates and never writes into alg.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
clamp_gerber_iq_n(
    alg::FullGerberIQ,
    _::Gerber2
) -> FullGerberIQ{_A, _B, _C, _D, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number, <:Number} where {_A, _B, _C, _D}

Lowers the mixed-magnitude weights of a FullGerberIQ template so that the Gerber2 statistic stays inside [-1, 1]. It does not make the matrix positive definite; that is pdm's work.

Under Gerber2 the pairwise entry is the net pos - neg, divided by the geometric mean of the pair's two diagonal projections. A projection compares an asset with itself, so both returns fall in the same magnitude class and the co-movement is always concordant. Exactly six of the twenty-one weights therefore sit on the diagonal, one per class: n11, n4, n1, n2, n5 and n12. The other fifteen each join two distinct classes, one weight per unordered pair, discordant channels included, and C(6, 2) = 15.

The source proves that the ratio is bounded by one if and only if each of those fifteen is at most the geometric mean of the two diagonal weights of the classes it joins. This method lowers all fifteen onto that bound. A discordant channel obeys the rule like any other, because both of the classes it joins carry a diagonal weight. A FullGerberIQ with n1 = n11 = 0.1 and n15 = 1.0 returns 10.0 unclamped, and 1.0 clamped.

Note

The clamp is a necessary and a sufficient condition on the template, and it is the whole condition. sc needs no restriction, because Gerber2 reads its denominator in the pair's own units. #494 and #500 are the two defects this closes. Every shipped default meets the bound, so the clamp does not move one.

Mathematical definition

For each of the fifteen mixed weights $n_{k}$, with $a$ and $b$ the two classes its channel joins and $n_{a}$, $n_{b}$ their diagonal weights:

\[\begin{align} n_{k} &\leftarrow \min\left(n_{k},\, \sqrt{n_{a} n_{b}}\right)\,. \end{align}\]

The fifteen channels and the pair of classes each joins are

\[\begin{align} n_{13} &: (n_{11}, n_{12})\,, & n_{14} &: (n_{4}, n_{11})\,, & n_{15} &: (n_{1}, n_{11})\,, \\ n_{16} &: (n_{2}, n_{12})\,, & n_{17} &: (n_{5}, n_{12})\,, & n_{18} &: (n_{2}, n_{11})\,, \\ n_{19} &: (n_{5}, n_{11})\,, & n_{20} &: (n_{4}, n_{12})\,, & n_{21} &: (n_{1}, n_{12})\,, \\ n_{3} &: (n_{1}, n_{2})\,, & n_{6} &: (n_{4}, n_{5})\,, & n_{7} &: (n_{1}, n_{4})\,, \\ n_{8} &: (n_{2}, n_{5})\,, & n_{9} &: (n_{4}, n_{2})\,, & n_{10} &: (n_{1}, n_{5})\,. \end{align}\]

Where:

  • $n_{1}$, $n_{4}$, $n_{11}$: Small, moderate and large positive concordant weights.
  • $n_{2}$, $n_{5}$, $n_{12}$: Small, moderate and large negative concordant weights.

Algorithm

  1. Read the six diagonal weights n1, n2, n4, n5, n11 and n12 from alg.
  2. Lower each of the fifteen mixed weights onto the geometric mean of the two diagonal weights of the classes its channel joins, by the table above.
  3. Return a new FullGerberIQ carrying the fifteen lowered weights and the four boundaries unchanged.

Arguments

Returns

  • kind::FullGerberIQ: A new template. The method allocates and never writes into alg.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_iq_epsFunction
gerber_iq_eps(e::Number, ::MatNum) -> Number
gerber_iq_eps(e::Function, X::MatNum) -> Number
gerber_iq_eps(e::Option{<:GerberIQEpsEstimator}, X::MatNum) -> Number

Computes or returns the Gerber Information Quality delay parameter e, potentially using X as an input.

Mathematical definition

The fall-through sets the delay from the shape of the returns matrix alone.

\[\begin{align} \varepsilon &= \mathrm{round}\left(T - \frac{T}{N}\right)\,. \end{align}\]

Where:

  • $\varepsilon$: Delay. A co-movement of age $T - k$ carries its full weight when $T - k \leq \varepsilon$.
  • $T$: Number of observations.
  • $N$: Number of assets.

An observation is therefore discounted only when its index satisfies $k < T - \varepsilon$, so this default discounts about the oldest $T/N$ observations and leaves the rest at full weight. The formula is the library's, not the source's. The source states only that the delay is a whole number of periods no larger than the lookback duration, and leaves its value to expert judgement or to an outer optimisation.

Algorithm

  1. Return the number unchanged when e is a Number.
  2. Call e(X) when e is a Function, and return its result.
  3. Otherwise read T and N from size(X), and return round(Int, T - T / N).

Arguments

  • e: The delay parameter estimator, function or value for use in the decay equation.
    • ::Number: Use the number as-is.
    • ::Function: A function which takes the data matrix X as an argument and returns a Number.
    • ::Option{<:GerberIQEpsEstimator}: Fallback returning round(Int, T - T / N), where T and N are the number of rows and columns of X respectively.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.

Returns

  • e::Number: The delay parameter for use in the decay equation. Observations no older than e periods are not discounted. This is not the source's window-duration truncation $\tau$, which the estimator does not expose; the estimator always reads the whole matrix and lets the decay discount its oldest rows.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_iq_gammaFunction
gerber_iq_gamma(y::Number, ::MatNum) -> Number
gerber_iq_gamma(y::Function, X::MatNum) -> Number
gerber_iq_gamma(y::Option{<:GerberIQGammaEstimator}, X::MatNum) -> Number

Computes or returns the Gerber Information Quality decay strength parameter y, potentially using X as an input.

Mathematical definition

The fall-through sets the decay rate from the number of assets alone.

\[\begin{align} \gamma &= \frac{\ln 2}{N}\,. \end{align}\]

Where:

  • $\gamma$: Decay rate. A larger value discounts an old co-movement harder.
  • $N$: Number of assets.

This is a half-life of $N$ periods: beyond the delay, the weight of a co-movement halves every $N$ observations, because $\exp(-\gamma N) = 1/2$. The formula is the library's, not the source's. The source requires only $\gamma > 0$ and leaves its value to expert judgement or to an outer optimisation.

Algorithm

  1. Return the number unchanged when y is a Number.
  2. Call y(X) when y is a Function, and return its result.
  3. Otherwise return log(2) / size(X, 2).

Arguments

  • y: The decay strength parameter estimator, function or value for use in the decay equation.
    • ::Number: Use the number as-is.
    • ::Function: A function which takes the data matrix X as an argument and returns a Number.
    • ::Option{<:GerberIQGammaEstimator}: Fallback returning log(2) / size(X, 2).
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.

Returns

  • gamma::Number: The decay strength parameter for use in the decay equation.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_iq_scalingFunction
gerber_iq_scaling(sca::AssetVolatilityGerberIQScaler, sdi::Number, sdj::Number) -> (Number, Number)
gerber_iq_scaling(sca::Function, sdi::Number, sdj::Number) -> (Number, Number)
gerber_iq_scaling(sca::Option{<:GerberIQScalerEstimator}, sdi::Number, sdj::Number) -> (Number, Number)

Computes or returns the threshold scaling parameters for defining significant co-movements in Gerber Information Quality.

Every threshold of the pair — the noise threshold c and each boundary of the squeezing template — is multiplied by the value this function returns for its own axis. So the scaler fixes the units in which a co-movement is judged large.

A scaler is pair-separable when its first component reads sdi alone, so that an asset's thresholds are the same whatever partner it is measured against. AssetVolatilityGerberIQScaler is pair-separable. The fall-through is not, because the pair mean moves with sdj, and a Function need not be. Every scaler is safe under every marker. Gerber2 reads its denominator in the pair's own units through iq_add_diagonal, so a scaler that moves an asset's class moves the numerator and the denominator together. This is the fix of #500.

Mathematical definition

\[\begin{align} (s_i,\, s_j) &= \begin{cases} (\sigma_i,\, \sigma_j) & \text{AssetVolatilityGerberIQScaler} \\ \left(\dfrac{\sigma_i + \sigma_j}{2},\, \dfrac{\sigma_i + \sigma_j}{2}\right) & \text{fall-through} \end{cases}\,. \end{align}\]

Where:

  • $s_i$, $s_j$: Threshold scaling factors of the pair.
  • $\sigma_i$: Standard deviation of asset $i$.

The fall-through gives both assets the same units, so a volatile asset and a quiet one are held to the same absolute threshold. AssetVolatilityGerberIQScaler gives each asset its own units instead. Both are options the source names.

Algorithm

  1. Return (sdi, sdj) unchanged for AssetVolatilityGerberIQScaler.
  2. Call sca(sdi, sdj) for a Function, and return its result.
  3. Otherwise compute (sdi + sdj) / 2 once, and return it for both axes.

Arguments

  • sca: The scaling estimator to use.

    • ::AssetVolatilityGerberIQScaler: Returns the input sdi and sdj as-is. This lets each asset scale according to its own volatility.
    • ::Option{<:GerberIQScalerEstimator}: Fallback returning the mean of sdi and sdj twice so each asset is scaled according to the mean of the two asset volatilities. Overloading this with a custom GerberIQScalerEstimator allows for custom scaling behavior.
    • ::Function: Custom scaling function that takes sdi and sdj as arguments and returns the scaled values.
  • sdi: Standard deviation of asset i.

  • sdj: Standard deviation of asset j.

Returns

  • scai::Number: The scaled value for sdi.
  • scaj::Number: The scaled value for sdj.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_iq_assert_c_dFunction
gerber_iq_assert_c_d(c::Number, kind::BasicGerberIQ)

Asserts that c <= kind.d, where c is the small movement threshold and d the significance threshold parameter of BasicGerberIQ.

The two parameters cut the same axis and are measured in the same scaled units. c closes the noise zone from above and d opens the significant zone from below, so the body of the template is the band c <= |x| < d. A d below c inverts that band, and the template then has no body at all: every co-movement that survives the noise zone is already beyond d, and the weight n can never be selected.

Arguments

Validation

  • c <= kind.d, else a DomainError naming both values.

Returns

  • nothing. The function is called for its raise alone.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
gerber_iq_assert_c_d(c::Number, kind::Union{<:PartialGerberIQ, <:FullGerberIQ}) -> Nothing

Asserts that all c <= kind.d**, where c is the small movement threshold and d** are the significance threshold parameters of PartialGerberIQ or FullGerberIQ.

c and every boundary cut the same axis in the same scaled units, so c must sit inside the innermost boundary. A boundary below c describes a band that the noise zone has already swallowed, and every weight that names that band becomes unselectable. The check runs once per boundary, and the raise names the boundary that failed, so a caller with four boundaries learns which one is wrong.

Arguments

Validation

  • c <= dcp, c <= dcn, c <= ddp and c <= ddn for a PartialGerberIQ, else a DomainError naming the failing boundary.
  • c <= dp1, c <= dp2, c <= dn1 and c <= dn2 for a FullGerberIQ, else a DomainError naming the failing boundary.

Returns

  • nothing. The function is called for its raise alone.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_iq_weightFunction
gerber_iq_weight(
    _::Number,
    _::Number,
    axi::Number,
    axj::Number,
    sci::Number,
    scj::Number,
    kind::BasicGerberIQ
) -> Any

Computes the weight for a co-movement according to the region it falls into from the BasicGerberIQ template.

BasicGerberIQ states the closed form this method selects from. The signed returns are unused, because the template is symmetric about both axes and reads magnitudes alone.

Algorithm

  1. Scale the significance threshold onto each axis: di = d * sci and dj = d * scj.
  2. Return one(n) when both absolute returns reach their scaled threshold.
  3. Return n when neither does.
  4. Return n^2 otherwise, which is the case of exactly one absolute return reaching its threshold.

Arguments

  • xi: Return of asset i (unused).
  • xj: Return of asset j (unused).
  • axi: Absolute return of asset i.
  • axj: Absolute return of asset j.
  • sci: Scaling for movement of asset i.
  • scj: Scaling for movement of asset j.
  • kind: Instance of BasicGerberIQ.

Returns

  • res::Number: Co-movement weight.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
gerber_iq_weight(
    xi::Number,
    xj::Number,
    axi::Number,
    axj::Number,
    sci::Number,
    scj::Number,
    kind::PartialGerberIQ
) -> Any

Computes the weight for a co-movement according to the region it falls into from the PartialGerberIQ template.

PartialGerberIQ states the channel map this method selects from. The absolute returns are unused, because this template reads the sign of each return as well as its size.

Algorithm

  1. Scale each of the four boundaries onto each axis, giving the eight thresholds dcpi, dcni, ddpi, ddni and their j counterparts.
  2. Test the positive concordant quadrant in order of size: n4 when both returns reach dcp, n7 when one does and the other is positive but smaller, and n1 when both are positive and smaller.
  3. Test the negative concordant quadrant the same way against dcn, giving n5, n8 and n2.
  4. Test the two discordant quadrants against ddp and ddn, giving n6 when both returns are beyond their boundary, n9 and n10 when one is and the other is not, and n3 when neither is.
  5. Return zero(xi) when no channel matched, which happens when a return is exactly zero.

The tests are ordered from the largest channel inwards, so the first match wins and no co-movement is counted twice.

Arguments

  • xi: Return of asset i.
  • xj: Return of asset j.
  • axi: Absolute return of asset i (unused).
  • axj: Absolute return of asset j (unused).
  • sci: Scaling for movement of asset i.
  • scj: Scaling for movement of asset j.
  • kind: Instance of PartialGerberIQ.

Returns

  • res::Number: Co-movement weight.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
gerber_iq_weight(
    xi::Number,
    xj::Number,
    axi::Number,
    axj::Number,
    sci::Number,
    scj::Number,
    kind::FullGerberIQ
) -> Any

Computes the weight for a co-movement according to the region it falls into from the FullGerberIQ template.

FullGerberIQ states the channel map this method selects from. The absolute returns are unused, because this template reads the sign of each return as well as its size.

Algorithm

  1. Scale each of the four boundaries onto each axis, giving the eight thresholds dp1i, dp2i, dn1i, dn2i and their j counterparts.
  2. Test the six channels whose asset i return is large positive, in the order large positive, moderate positive, small positive, small negative, moderate negative and large negative on asset j, giving n11, n14, n15, n18, n19 and n13.
  3. Test the five remaining channels whose asset i return is moderate positive, giving n4, n7, n9, n6 and n20.
  4. Test the four remaining channels whose asset i return is small positive, giving n1, n3, n10 and n21.
  5. Test the three remaining channels whose asset i return is small negative, giving n2, n8 and n16.
  6. Test the two remaining channels whose asset i return is moderate negative, giving n5 and n17.
  7. Return n12 when both returns are large negative.
  8. Return zero(xi) when no channel matched, which happens when a return is exactly zero.

Every test names both orderings of the pair, so the result is symmetric in its two returns. The tests run from the largest class inwards, so the first match wins and no co-movement is counted twice. All twenty-one weights are reachable.

Arguments

  • xi: Return of asset i.
  • xj: Return of asset j.
  • axi: Absolute return of asset i (unused).
  • axj: Absolute return of asset j (unused).
  • sci: Scaling for movement of asset i.
  • scj: Scaling for movement of asset j.
  • kind: Instance of FullGerberIQ.

Returns

  • res::Number: Co-movement weight.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.gerber_IQ_deltaFunction
gerber_IQ_delta(
    xi::Number,
    xj::Number,
    axi::Number,
    axj::Number,
    decay::GerberIQDecayEstimator,
    T::Integer,
    k::Number,
    sci::Number,
    scj::Number,
    kind::GerberIQCovarianceAlgorithm
) -> Any

Computes the Gerber IQ statistic for a single co-movement.

This is the product of the two halves of the source's squeezing statistic: the spatial weight the template gives the co-movement, and the temporal discount its age earns. The result is the quantity that comovement_step adds into one of the pair's three accumulators.

Mathematical definition

\[\begin{align} \Delta_{t,\,i,\,j} &= \eta_{t,\,i,\,j} \, v_{t}\,. \end{align}\]

Where:

  • $\Delta_{t,\,i,\,j}$: Contribution of the co-movement of assets $i$ and $j$ at observation $t$.
  • $\eta_{t,\,i,\,j}$: Squeezing weight of that co-movement, from the template.
  • $v_{t}$: Temporal discount of observation $t$, from the decay estimator.

Algorithm

  1. Compute the squeezing weight w with gerber_iq_weight, passing the pair's two scaling factors and the template.
  2. Compute the temporal discount p by calling the decay functor with T and k.
  3. Return w * p.

Arguments

  • xi: Return for asset i.
  • xj: Return for asset j.
  • axi: Absolute return for asset i.
  • axj: Absolute return for asset j.
  • decay: The decay estimator for the Gerber IQ statistic.
  • T: The number of observations.
  • k: The current observation.
  • sci: Scaling factor for asset i.
  • scj: Scaling factor for asset j.
  • kind: The Gerber IQ co-movement template.

Returns

  • rho::Number: The weighted, discounted contribution of one co-movement. It is not itself a correlation.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source
PortfolioOptimisers.iq_add_neutralFunction
iq_add_neutral(
    pol::GerberIQKernel{<:Gerber1},
    acc,
    st,
    xi::Number,
    xj::Number,
    axi::Number,
    axj::Number,
    T::Integer,
    k::Integer
) -> Union{NamedTuple, DataStructures.SortedMultiDict}

Accumulate a neutral (one-sided) observation into the Gerber IQ pair accumulator.

Only Gerber1 tracks neutral co-movements, adding the gerber_IQ_delta weight to the neutral score; the fall-through method returns the accumulator unchanged.

A neutral co-movement is one on which exactly one of the two assets left the noise zone, which iq_crossed decides. Gerber1 is the only branch whose denominator counts it, so the other two branches would carry the sum and never read it.

Arguments

  • pol: The GerberIQKernel policy.
  • acc: Pair accumulator (pos, neg, nn, cpos, cneg, cnn).
  • st: Pair state from comovement_pair_state.
  • xi, xj: Returns of assets i and j at observation k.
  • axi, axj: Their absolute values.
  • T: Number of observations.
  • k: Observation index.

Returns

Related

source
PortfolioOptimisers.iq_add_diagonalFunction
iq_add_diagonal(
    pol::GerberIQKernel{<:Gerber2},
    acc,
    st,
    xi::Number,
    xj::Number,
    axi::Number,
    axj::Number,
    crossi::Bool,
    crossj::Bool,
    T::Integer,
    k::Integer
) -> Union{NamedTuple, DataStructures.SortedMultiDict}

Accumulate the two diagonal projections of one observation into the Gerber IQ pair accumulator.

Only Gerber2 reaches the acting method, because only its denominator reads them. The fall-through method returns the accumulator unchanged, so the other two markers pay for no weight they never divide by.

The projection of an observation in the i direction is the co-movement (x_i, x_i), judged in this pair's units. Both coordinates then fall in the same magnitude class and the co-movement is concordant, so the projection names the diagonal weight of asset i at that observation. The j direction is the mirror. An asset contributes only when it left the noise zone, which is the same admission test the numerator applies.

The projection is what keeps the statistic inside [-1, 1]. An asset's magnitude class moves with its partner whenever sc is not pair-separable, and a denominator read from the assembled diagonal reads the class of the pair (i, i) instead. Cauchy-Schwarz then has nothing to stand on. Reading the class in the pair's own units restores it for every scaler, and #500 is the defect.

Arguments

  • pol: The GerberIQKernel policy.
  • acc: Pair accumulator (pos, neg, nn, cpos, cneg, cnn, di, dj).
  • st: Pair state from comovement_pair_state.
  • xi, xj: Returns of assets i and j at observation k.
  • axi, axj: Their absolute values.
  • crossi, crossj: Whether each asset left the noise zone, from iq_crossed.
  • T: Number of observations.
  • k: Observation index.

Returns

  • The accumulator, with di and dj raised by the projected gerber_IQ_delta contributions under Gerber2, and unchanged otherwise.

Related

source
PortfolioOptimisers.iq_crossedFunction
iq_crossed(x::Number, ax::Number, c::Number) -> Any

Decide whether one asset left the noise zone at one observation.

An asset leaves the noise zone when its return reaches the pair's scaled threshold and is not exactly zero. The sign test is redundant for a positive threshold, because ax >= c > 0 already implies that x is not zero. It binds only at c = 0, where the closed comparison ax >= 0 holds for every return, including one that is exactly zero. A return of exactly zero never crosses, and this is that rule for the Gerber IQ family.

The rule is what keeps the diagonal of the statistic at one. The pair (i, i) either crosses on both axes or on neither, so it never reaches the neutral accumulator that Gerber1 divides by. Without the sign test a zero return crosses on both axes but has no sign, so it fell through to that accumulator and pulled the diagonal below one.

Arguments

  • x: Return of the asset at the observation.
  • ax: Its absolute value.
  • c: The asset's scaled noise threshold, from comovement_pair_state.

Returns

  • crossed::Bool: true when the asset left the noise zone.

Related

source
PortfolioOptimisers.gerber_IQFunction
gerber_IQ(
    ce::GerberIQCovariance,
    X::MatNum,
    sd::ArrNum
) -> MatNum

Computes the Gerber IQ statistic matrix using noise compression template in ce.kind and numerator/denominator definition according to ce.alg.

Mathematical definition

For each asset pair $(i,j)$ accumulate weighted concordant and discordant counts:

\[\begin{align} H_{ij}^{+} &= \sum_{k=1}^{T} w_{ij,k} \cdot d_k \cdot \mathbf{1}[\text{concordant}]\,, \\ H_{ij}^{-} &= \sum_{k=1}^{T} w_{ij,k} \cdot d_k \cdot \mathbf{1}[\text{discordant}]\,. \end{align}\]

Where:

  • $H_{ij}^{+}$, $H_{ij}^{-}$: Weighted concordant and discordant co-movement accumulators.
  • $T$: Number of observations.
  • $w_{ij,k}$: Region weight from the IQ template for observation $k$.
  • $d_k = \exp[-y \max(0, T-k-e)]$: Temporal decay at observation $k$.

GerberIQ correlation:

\[\begin{align} \rho_{ij} &= \begin{cases} (H_{ij}^{+} - H_{ij}^{-}) / (H_{ij}^{+} + H_{ij}^{-}) & \text{Gerber0} \\ (H_{ij}^{+} - H_{ij}^{-}) / (H_{ij}^{+} + H_{ij}^{-} + H_{ij}^{0}) & \text{Gerber1} \\ (H_{ij}^{+} - H_{ij}^{-}) / \sqrt{D_{ij}\,D_{ji}} & \text{Gerber2} \end{cases}\,. \end{align}\]

Where:

  • $\rho_{ij}$: GerberIQ correlation between assets $i$ and $j$.
  • $H_{ij}^{+}$, $H_{ij}^{-}$: Weighted concordant and discordant accumulators.
  • $H_{ij}^{0}$: Weighted neutral (neither concordant nor discordant) accumulator (Gerber1 only).
  • $D_{ij} = \sum_{k} w_{ii,k}\, d_{k}$, over the observations on which asset $i$ left the noise zone: the projection of asset $i$ onto the lead diagonal, in the units of the pair $(i, j)$. The projected co-movement $(x_{ki}, x_{ki})$ falls in one magnitude class on both axes, so $w_{ii,k}$ is the diagonal weight of asset $i$ at that observation. $D_{ij}$ and $D_{ji}$ differ, and both move with the pair whenever sc is not pair-separable.

The Gerber1 branch is the source's own statistic. Its numerator runs over the observations on which both assets left the noise zone, and its denominator over those on which at least one did. The Gerber0 and Gerber2 branches are the classic Gerber family's denominators, applied here to the weighted, discounted accumulators; the source's main text states neither. The source's internet appendix states a third form, whose denominator is the geometric mean of the two diagonal projections taken over the observations on which both assets crossed. This library does not ship that form. Gerber2 projects the same way but keeps the classic denominator's observation set, which is the one that reduces to GerberCovariance.

The Gerber statistic is the special case of this one that switches the squeezing and the decay off. With every weight set to one, $\gamma = 0$, the per-asset volatility scaling of AssetVolatilityGerberIQScaler, and $c$ equal to a Gerber threshold, all three branches reproduce GerberCovariance to the last bit. The reduction holds at $c = 0$ as it does at every positive threshold, because iq_crossed gives this family the same rule as that one: a return of exactly zero never leaves the noise zone.

All three branches are bounded by $|\rho_{ij}| \leq 1$. Gerber0 and Gerber1 are bounded by construction, because each divides by a sum of the same weights it subtracts. Gerber2 is bounded by the source's own condition on the template: every weight that joins two distinct magnitude classes is at most the geometric mean of the two diagonal weights of those classes. clamp_gerber_iq_n enforces that condition on every such weight, and the source proves it necessary and sufficient. Cauchy-Schwarz then bounds the ratio, because $D_{ij}$ reads asset $i$'s class in the same units the numerator reads it in, whatever sc does. The diagonal is exactly one, because the pair $(i, i)$ makes the numerator and both projections the same sum.

Algorithm

  1. Allocate the N × N output matrix rho.
  2. Resolve the decay estimator against X with regenerate_decay, so its delay and rate are numbers before the loop starts.
  3. Build the GerberIQKernel policy from the resolved decay and the estimator's alg, kind, sc, c and the standard deviations sd.
  4. Fill rho with gerber_comovement!, which walks every pair and every observation and reduces each pair's accumulators. That loop skeleton is shared with the Smyth-Broby family and lives in one place.
  5. Write one onto a zero diagonal entry with comovement_unit_diagonal!. An asset that never leaves its noise zone reduces to a zero diagonal entry, and that entry is one by definition.
  6. Repair the matrix with posdef!, because the statistic is not guaranteed to be positive semi-definite. The source records the same and repairs by the nearest correlation matrix.

Step 4 is where the three GerberCovarianceAlgorithm branches differ. comovement_ratio owns the Gerber0 and Gerber1 denominators, and comovement_finalise owns the Gerber2 one. This family does not call standardise_comovement!, which normalises after assembly and cannot read the pair's units.

Arguments

  • ce: Covariance estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • sd: Standard deviation vector of X, shaped to be consistent with X.

Returns

  • rho::MatNum: Correlation matrix assets x assets.

Related

References

  • [15] S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).
source

References

[15]
S. Gerber, W. Smyth, H. Markowitz, Y. Miao, P. Ernst and P. Sargen. Squeezing financial noise: A novel approach to covariance matrix estimation. Available at SSRN 4986939 (2025).