quantlab.risk.predefined.use4.Use4RiskModel#
- class quantlab.risk.predefined.use4.Use4RiskModel(config)[source]#
Bases:
FactorRiskModelUSE4-style factor risk model: country, industries and styles; EWMA, Newey-West, eigenfactors, VRA.
See the module docstring for the regression;
FactorRiskModelfor the contract every factor risk model meets. This model’s stores:The regression store (
regression) holds:factor_returnon(timestamp, factor): the factor returns, NaN for an industry left out of the bar and on a bar without a regression;specific_returnon(timestamp, symbol);r_squaredontimestamp: the weighted R-squared of the fit,1 - sum(w e^2) / sum(w y^2)over the trimmed returnsy(our choice: uncentred, as the country factor plays the intercept);estu_countontimestamp: the symbols in the fit;industry_memberson(timestamp, industry): estimation-universe symbols of each industry with exposures and a return, before thin industries are left out;industry_excludedon(timestamp, industry): whether the industry was left out of the bar.
Its warm-up is one bar: row
treads 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_covarianceon(timestamp, factor_i, factor_j):F_ij = rho_ij sigma_i sigma_j(USE4 eq. 4.1), the volatilitiessigmafrom the lastvolatility_windowfactor returns weighted withvolatility_half_lifewithvolatility_lagsNewey-West lags, the correlationsrhofrom the lastcorrelation_windowweighted withcorrelation_half_lifewithcorrelation_lagslags (USE4 §4.1), then the eigenfactor risk adjustment (USE4 §4.2, Appendix B): the eigenvariances ofFrescaled by the volatility bias of the eigenfactors ofeigen_simulationshistories simulated fromFand estimated the same way, as eq. B7, or witheigen_scalethe scaled eq. B8; each bar’s draws are seeded byeigen_seedand 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 timeslambda_F^2(USE4 §4.3, eqs. 4.3-4.5), which leaves the correlations alone;specific_riskon(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:the time-series volatility: the last
specific_windowspecific returns weighted withspecific_half_life, times the square root of the Newey-West multiplierC_NW(eq. 5.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; seestructural_fitandstructural_history_window), andstructural_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 (seeUse4RiskConfig.structural_modelandblending_min_observations);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,qtheshrinkage);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_multiplierandspecific_volatility_multiplierontimestamp:lambda_Fandlambda_S, each the root of the exponentially weighted mean square of the cross-sectional bias statistics of the lastvra_windowbars, a bar’s returns against the forecasts of the bar before (seeUse4RiskConfig.vra_half_life). The forecasts it reads are the store’s before the adjustment, so a row reads thevra_windowrows before it as well; the specific ones include Newey-West whenspecific_lagsis 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 toL(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_lthe autocorrelation at lagl(USE4 eq. 5.2), weighted with its own half-life over its own window:volatility_autocorrelation_half_lifeand_window(504 and 1512 by default, longer than the volatilities’ so the multiplier is less noisy) andspecific_autocorrelation_half_lifeand_window(the specific adjustment is off by default,specific_lags=0; seeUse4RiskConfig);a correlation is that of the covariance
G_0 + sum_l b_l (G_l + G_l'),G_l[i, j]the weighted mean ofx_i(t) x_j(t - l)over the barstof the correlation window both have, each column about its weighted mean, weighted by the weight oft,G_0the covariance described below; it divides by each factor’s variance over the pair’s bars plus its own lag terms.
Each
rho_landG_lis taken over the bars of the window that have both returns; a lag with fewer thanmin_observationssuch 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=0it stays as it is. A variance, a correlation or a specific volatility with fewer thanmin_observationsbars is NaN. Its warm-up is the longest window less one bar, plusvra_windowwith the volatility regime adjustment, counted on the regression store’s bars.- Parameters:
- configUse4RiskConfig
The exposures factor (a
BarraStyleby default), the dataset it reads and the method’s parameters.
- Raises:
- TypeError
If
configis not aUse4RiskConfig.- ValueError
If a parameter is invalid.
Examples
With
styleaBarraStylewhose store is built andpricesthe 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_configrebuilds this model with.alias of
Use4RiskConfig
- property estimate_warmup_bars#
The longest estimate window less one bar, plus
vra_windowwith 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_namesand 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
countryfor the country factor,industryfor the industries,stylefor 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 withoutstyle_.Examples
>>> labels = model.factor_labels() >>> labels["country"], labels["industry_34"], labels["style_residual_volatility"] ('Country', 'Business Services', 'residual volatility')
- property factor_names#
The
factoraxis: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
treads the exposures and the price of the previous priced bar.Examples
>>> model.regression_warmup_bars 1