quantlab.risk.predefined.use4.Use4RiskModel#

class quantlab.risk.predefined.use4.Use4RiskModel(config)[source]#

Bases: FactorRiskModel

USE4-style factor risk model: country, industries and styles; EWMA, Newey-West, eigenfactors, VRA.

See the module docstring for the regression; FactorRiskModel for the contract every factor risk model meets. This model’s stores:

The regression store (regression) holds:

  • factor_return on (timestamp, factor): the factor returns, NaN for an industry left out of the bar and on a bar without a regression;

  • specific_return on (timestamp, symbol);

  • r_squared on timestamp: the weighted R-squared of the fit, 1 - sum(w e^2) / sum(w y^2) over the trimmed returns y (our choice: uncentred, as the country factor plays the intercept);

  • estu_count on timestamp: the symbols in the fit;

  • industry_members on (timestamp, industry): estimation-universe symbols of each industry with exposures and a return, before thin industries are left out;

  • industry_excluded on (timestamp, industry): whether the industry was left out of the bar.

Its warm-up is one bar: row t reads the exposures and the price of the previous priced bar. A bar has no regression (all NaN, a count of 0) when it has no more fitted symbols than factors to fit.

The estimate store (estimate) is computed from the regression store and holds forecasts of the next one-bar return:

  • factor_covariance on (timestamp, factor_i, factor_j): F_ij = rho_ij sigma_i sigma_j (USE4 eq. 4.1), the volatilities sigma from the last volatility_window factor returns weighted with volatility_half_life with volatility_lags Newey-West lags, the correlations rho from the last correlation_window weighted with correlation_half_life with correlation_lags lags (USE4 §4.1), then the eigenfactor risk adjustment (USE4 §4.2, Appendix B): the eigenvariances of F rescaled by the volatility bias of the eigenfactors of eigen_simulations histories simulated from F and estimated the same way, as eq. B7, or with eigen_scale the scaled eq. B8; each bar’s draws are seeded by eigen_seed and the bar, so a row is the same however the store is built. The adjusted block is positive semi-definite (see _eigen_adjusted); then the volatility regime adjustment, the whole matrix times lambda_F^2 (USE4 §4.3, eqs. 4.3-4.5), which leaves the correlations alone;

  • specific_risk on (timestamp, symbol) (USE4 §5.1-5.2), for every symbol with a time-series value and, with a structural model, every symbol with exposures at the bar:

    1. the time-series volatility: the last specific_window specific returns weighted with specific_half_life, times the square root of the Newey-West multiplier C_NW (eq. 5.2);

    2. combined with a structural volatility (eqs. 5.3-5.5): a blending coefficient per symbol from how many returns it has and how fat-tailed they are, a daily regression of the log time-series volatility on the exposures and the history length log(1 + h / 252) over every symbol with a time series (USE4 fits the symbols whose coefficient is 1 on the exposures alone; see structural_fit and structural_history_window), and structural_bias (by default the smearing estimate) times the exponential of each symbol’s fitted value; structural_model="fill" (the default) gives it to the symbols without a time-series value, "blend" blends every symbol as USE4 eq. 5.5 (see Use4RiskConfig.structural_model and blending_min_observations);

    3. shrunk toward the cap-weighted mean of the symbol’s size group with intensity q |s - m| / (d + q |s - m|) (eqs. 5.6-5.9, q the shrinkage);

    4. times lambda_S, the volatility regime adjustment (§5.3, eqs. 5.10-5.12).

    The time series, the blending coefficient and the history length read only their windows; the structural fit and the shrinkage use the exposures and market caps of the bar.

  • with the volatility regime adjustment (vra_half_life), factor_volatility_multiplier and specific_volatility_multiplier on timestamp: lambda_F and lambda_S, each the root of the exponentially weighted mean square of the cross-sectional bias statistics of the last vra_window bars, a bar’s returns against the forecasts of the bar before (see Use4RiskConfig.vra_half_life). The forecasts it reads are the store’s before the adjustment, so a row reads the vra_window rows before it as well; the specific ones include Newey-West when specific_lags is set, where USE4 removes it (our choice; off by default).

Newey-West, with Newey and West’s (1987) Bartlett weights b_l = 1 - l / (L + 1) over lags 1 to L (USE4 publishes no weights; our choice):

  • a factor variance and a specific variance are multiplied by C_NW = 1 + 2 sum_l b_l rho_l, at least 0, rho_l the autocorrelation at lag l (USE4 eq. 5.2), weighted with its own half-life over its own window: volatility_autocorrelation_half_life and _window (504 and 1512 by default, longer than the volatilities’ so the multiplier is less noisy) and specific_autocorrelation_half_life and _window (the specific adjustment is off by default, specific_lags=0; see Use4RiskConfig);

  • a correlation is that of the covariance G_0 + sum_l b_l (G_l + G_l'), G_l[i, j] the weighted mean of x_i(t) x_j(t - l) over the bars t of the correlation window both have, each column about its weighted mean, weighted by the weight of t, G_0 the covariance described below; it divides by each factor’s variance over the pair’s bars plus its own lag terms.

Each rho_l and G_l is taken over the bars of the window that have both returns; a lag with fewer than min_observations such bars adds nothing. With 0 lags the estimates are the plain exponentially weighted moments. The forecasts stay one-bar: Newey-West turns a sum of serially correlated bars’ returns into a per-bar variance that scales linearly with the horizon.

The weights halve every half-life back from the bar and stop at the window, so a row reads only its window. Moments are taken about the weighted mean, normalised by the sum of the weights. A missing factor return (an industry left out of a bar) leaves its bar out: each correlation uses the bars both factors have (pairwise, our choice where USE4 uses the EM algorithm), so the estimate need not be positive semi-definite; the eigenfactor adjustment sets its negative eigenvalues to 0, and with eigen_simulations=0 it stays as it is. A variance, a correlation or a specific volatility with fewer than min_observations bars is NaN. Its warm-up is the longest window less one bar, plus vra_window with the volatility regime adjustment, counted on the regression store’s bars.

Parameters:
configUse4RiskConfig

The exposures factor (a BarraStyle by default), the dataset it reads and the method’s parameters.

Raises:
TypeError

If config is not a Use4RiskConfig.

ValueError

If a parameter is invalid.

Examples

With style a BarraStyle whose store is built and prices the dataset it reads:

>>> model = Use4RiskModel(Use4RiskConfig(
...     exposures=style, dataset=prices, exposure_data_strategy="read",
...     regression_path="risk/use4_regression.zarr",
...     estimate_path="risk/use4_estimate.zarr",
... ))
>>> len(model.factor_names), model.factor_names[:2]
(61, ('country', 'industry_1'))
>>> model.regression.build("2012-01-01", "2024-12-31")
>>> model.estimate.build("2018-01-01", "2024-12-31")
config_cls#

The config class from_config rebuilds this model with.

alias of Use4RiskConfig

property estimate_warmup_bars#

The longest estimate window less one bar, plus vra_window with the regime adjustment.

Examples

>>> model.estimate_warmup_bars  # the 1512-bar correlation window, 126 VRA bars
1637
exposure_matrix(exposures)[source]#

Return each symbol’s exposures to factor_names and whether it has them all.

Parameters:
exposuresxr.Dataset

The exposures factor’s values at one bar, on symbol.

Returns:
matrixnp.ndarray

[n_symbols, n_factors]: 1 on the country factor, 1 on the symbol’s industry and 0 on the others, its style exposures.

coverednp.ndarray

Booleans: every style finite and, with industries, an industry among config.industries.

Examples

>>> matrix, covered = model.exposure_matrix(style.compute(day, day).isel(timestamp=0))
>>> matrix.shape[1] == len(model.factor_names)
True
property exposure_names#

The style exposures and, with industries, the industry code.

Examples

>>> model.exposure_names[-2:]
('style_growth', 'industry')
factor_groups()[source]#

Return country for the country factor, industry for the industries, style for the styles.

Examples

>>> groups = model.factor_groups()
>>> groups["country"], groups["industry_1"], groups["style_growth"]
('country', 'industry', 'style')
factor_labels()[source]#

Return Country, each industry’s Fama-French 48 name and each style without style_.

Examples

>>> labels = model.factor_labels()
>>> labels["country"], labels["industry_34"], labels["style_residual_volatility"]
('Country', 'Business Services', 'residual volatility')
property factor_names#

The factor axis: country, industry_<code> per industry, styles.

Examples

>>> model.factor_names[:3], model.factor_names[-1], len(model.factor_names)
(('country', 'industry_1', 'industry_2'), 'style_growth', 61)
property regression_warmup_bars#

One bar: row t reads the exposures and the price of the previous priced bar.

Examples

>>> model.regression_warmup_bars
1