quantlab.risk.base.FactorRiskModel#

class quantlab.risk.base.FactorRiskModel(config)[source]#

Bases: Component, ABC

A factor risk model: the stores estimated ahead of a backtest.

The interface every factor risk model presents, whatever its method:

  • factor_names: the factor axis, and factor_groups: each factor’s group (country, industry or style; style by default);

  • exposure_names and exposure_matrix(exposures): the outputs of the exposures factor (config.exposures) it reads, and each symbol’s exposures to factor_names at a bar, with whether it has them all;

  • the regression store (regression), one row per bar t: factor_return on (timestamp, factor), the factor returns over the bar ending at t, and specific_return on (timestamp, symbol), each symbol’s return over that bar less its factor part, the exposures being those of the bar before;

  • the estimate store (estimate), one row per bar t: factor_covariance on (timestamp, factor_i, factor_j) and specific_risk on (timestamp, symbol), forecasts made at t of the next bar’s factor covariance and specific volatilities;

  • prices and exposures: its inputs over a date range.

Every quantity is of one-bar returns, a row uses nothing later than its bar, and a store may add its own diagnostics. The factor axes are factor_names. A missing value is NaN: a factor without a return on a bar, a forecast without enough history.

A subclass implements the method: factor_names, exposure_names, exposure_matrix, _compute_regression and _compute_estimate (the rows of a date range, its warm-up read before it), and the warm-ups regression_warmup_bars and estimate_warmup_bars; it sets config_cls to its config, a FactorRiskConfig, and checks its parameters in _validate (calling this class’s). The rows it computes are checked against the stores’ variables before they are returned or written.

Parameters:
configFactorRiskConfig

The exposures factor, the price dataset and the method’s parameters; an instance of the class’s config_cls.

Raises:
TypeError

If config is not an instance of config_cls.

ValueError

If a parameter is invalid.

Examples

With model a Use4RiskModel:

>>> isinstance(model, FactorRiskModel)
True
>>> model.regression.build("2012-01-01", "2024-12-31")
>>> model.regression.read("2020-01-01", "2020-12-31")["factor_return"].dims
('timestamp', 'factor')
>>> model.estimate.build("2018-01-01", "2024-12-31")
>>> model.estimate.read("2024-12-31", "2024-12-31")["factor_covariance"].dims
('timestamp', 'factor_i', 'factor_j')
property class_name#

Bare class name, used in log and error messages.

Examples

>>> model.class_name
'Use4RiskModel'
property config#

The model’s normalised config: name filled, datasets merged.

Examples

>>> model.config is config
False
>>> model.config.name
'quantlab.risk.predefined.use4.Use4RiskModel'
config_cls#

The config class from_config rebuilds this model with.

alias of FactorRiskConfig

property estimate#

The estimate store: forecast factor covariance and specific risk (see the class docstring).

Examples

>>> model.estimate.build("2018-01-01", "2024-12-31")
>>> rows = model.estimate.read("2024-12-31", "2024-12-31")
>>> rows["factor_covariance"].dims
('timestamp', 'factor_i', 'factor_j')
abstract property estimate_warmup_bars#

Regression-store bars before a range _compute_estimate reads.

Examples

>>> model.estimate_warmup_bars  # Use4RiskModel's default
1637
abstractmethod 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].

coverednp.ndarray

Booleans: the symbol has an exposure to every factor it needs; its row is not meaningful otherwise.

Examples

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

The outputs of the exposures factor the model reads (the estimation universe aside).

Examples

>>> model.exposure_names[-1]
'industry'
exposures(start, end)[source]#

Return the exposure variables from start to end, read or computed.

Parameters:
start, endstr, datetime.date or pd.Timestamp

The range, both inclusive.

Returns:
xr.Dataset

The exposures factor’s exposure_names and, when the config names one, its estimation-universe flag, read from its store or computed per exposure_data_strategy.

Examples

>>> list(model.exposures("2024-01-02", "2024-01-31").data_vars)[-2:]
['industry', 'estu']
factor_groups()[source]#

Return each factor’s group, one of FACTOR_GROUPS, keyed by factor name.

The factor attribution of a backtest (quantlab.risk.attribution) sums its factors’ contributions and risk by group. By default every factor is a "style"; a model with a country (market) factor or industry factors says so by overriding this method.

Returns:
dict

{factor: group} in factor_names order.

Examples

>>> set(model.factor_groups().values()) <= set(FACTOR_GROUPS)
True
>>> model.factor_groups()["country"]  # a Use4RiskModel
'country'
factor_labels()[source]#

Return each factor’s display name, keyed by factor name.

The backtest report names factors by these labels (stored as the label coordinate of a backtest’s factor attribution). By default a factor is labelled by its name; a model with coded factor names (industry_34) overrides this method.

Returns:
dict

{factor: label} in factor_names order.

Examples

>>> list(model.factor_labels()) == list(model.factor_names)
True
>>> model.factor_labels()["industry_34"]  # a Use4RiskModel
'Business Services'
abstract property factor_names#

The factor axis of both stores.

Examples

>>> model.factor_names[:2]
('country', 'industry_1')
forecast(estimate, exposures)[source]#

Return the forecast at one bar from its estimate row and the exposures at the bar.

The one coverage rule of the model’s forecasts. A symbol of exposures is covered when exposure_matrix gives it every exposure, finite on the kept factors, and the row a specific risk. A factor without a covariance at the bar (covered_factors: no variance, or too few common bars with another) is left out of the forecast, and so is every symbol exposed to it.

Parameters:
estimatexr.Dataset

The estimate store’s row at the bar (factor_covariance on (factor_i, factor_j), specific_risk on symbol), as estimate.read(t, t).isel(timestamp=0) gives it and a decision’s context carries it (PortfolioContext.risk_estimate).

exposuresxr.Dataset

The exposures at the bar on symbol (exposure_names), as exposures(t, t) gives them: the symbols the forecast may cover, in its order. The caller chooses them, so a decision can be handed exposures an executor injects.

Returns:
FactorRiskForecast

Examples

>>> row = model.estimate.read(day, day).isel(timestamp=0)
>>> forecast = model.forecast(row, model.exposures(day, day).isel(timestamp=0))
>>> set(forecast.factor_names) <= set(model.factor_names)
True
forecast_window(timestamps, forecasts_through=None)[source]#

Return each bar’s outcome beside the forecast inputs of the bar before it.

Row i holds the regression store’s factor_return and specific_return of timestamps[i], and, of timestamps[i - 1]: the estimate store’s factor_covariance and specific_risk, the exposures (exposures), the risk-free rate (risk_free) and market cap (market_cap) from prices, and estimation_universe (the estimation-universe flag, every symbol without estu_name). That is the forecast made at the bar before and what the bar then did. Row 0 has no forecast inputs (NaN). A row is what forecast reads, as both its arguments.

Parameters:
timestampsarray-like of datetime64

Consecutive bars, in order: a backtest’s, or the regression store’s.

forecasts_throughdatetime-like, optional

The last bar whose forecast is needed: the estimate rows after it are not read (NaN), so the estimate store need not reach the outcomes of a multi-bar horizon. Every bar before the last by default.

Returns:
xr.Dataset

On timestamps, symbol, factor and factor_i/factor_j.

Raises:
ValueError

As require_window, or if the exposures or prices do not cover the bars before the last.

Examples

>>> window = model.forecast_window(bars)
>>> forecast = model.forecast(window.isel(timestamp=5), window.isel(timestamp=5))
prices(start, end)[source]#

Return price, market cap and risk-free rate on the bars with a price.

With risk_free_symbol the rate is that symbol’s, forward-filled and broadcast across the others, and the symbol is dropped.

Parameters:
start, endstr, datetime.date or pd.Timestamp

The range, both inclusive.

Returns:
xr.Dataset

price_column, market_cap_column and risk_free_column on (timestamp, symbol), only the bars where some symbol has a price.

Examples

>>> model.prices("2024-01-02", "2024-01-31")[model.config.market_cap_column].dims
('timestamp', 'symbol')
property regression#

The regression store: realized factor and specific returns (see the class docstring).

Examples

>>> model.regression.build("2024-01-01", "2024-03-31").store_range()
('2024-01-01', '2024-03-31')
abstract property regression_warmup_bars#

Bars before a range _compute_regression reads.

Examples

>>> model.regression_warmup_bars
1
require_window(timestamps, forecasts_through=None)[source]#

Refuse bars the stores cannot serve: the outcome and forecast rows of forecast_window.

Reads lazily, so it is cheap enough to call before a backtest simulates.

Parameters:
timestampsarray-like of datetime64

The bars, in order.

forecasts_throughdatetime-like, optional

The last bar whose forecast is needed; every bar before the last by default.

Raises:
ValueError

If the regression store does not cover the bars, or the estimate store the forecast bars (RiskStore.read).

Examples

>>> model.require_window(bars)  # stores built over the bars
store_uses()[source]#

Return the model’s own stores, owned and written by date range: regression, then estimate.

Each is declared "owns" with its RiskStore as the handle. The order is the order they are extended in, the estimate being computed from the regression store; the exposures factor and the datasets declare their own stores (see Component.store_uses). A store whose path the config leaves unset is left out.

Examples

>>> [(use.relation, use.handle.part) for use in model.store_uses()]
[('owns', 'regression'), ('owns', 'estimate')]