quantlab.model.predefined.smoothed.SmoothedPredictor#

class quantlab.model.predefined.smoothed.SmoothedPredictor(predictor, halflife_bars, warmup_bars=None, standardize=True)[source]#

Bases: PredictorView

A Predictor whose predictions are a causal exponential mean of the wrapped ones.

predict_window(start, end) asks the wrapped predictor for the window and up to warmup_bars bars before it, z-scores every bar over its symbols (cross_sectional_zscore, ddof=1) when standardize, and returns, for every bar of the window, each symbol’s exponentially weighted mean of those values over the bars up to that one: weight 2 ** (-age / halflife_bars) for a value age bars old, normalised over the values the symbol has (pandas ewm(halflife=halflife_bars, adjust=True, ignore_na=False)). A bar where a symbol has no value adds nothing to its mean, but its older values keep ageing. A cell NaN in the wrapped predictions at t is NaN in the output at t, so a mask at t (a MembershipMaskedPredictor) still decides who may be entered.

The warm-up is counted in the wrapped predictor’s bars: the request is widened until warmup_bars bars before the window come back (ten halflives by default, the weight left beyond them at most 2 ** -10) and cut to them. When the wrapped predictor holds fewer (its first bar, a record’s start), the mean starts at its first bar, a warning gives the count, and last_warmup_bars holds it.

The warm-up must be out of sample. A wrapped model predicts the bars before the window with its loaded checkpoint, so a warm-up bar on or before the end of the checkpoint’s fitted training window (fitted_train_bounds) would blend an in-sample fit into the smoothed values, and is refused. A RecordedPredictor with history=True serves each earlier bar as the checkpoint live then predicted it, so it is not checked. Every other protocol member forwards to the wrapped predictor (PredictorView); label_scales is "standardized" for every label when standardising.

Parameters:
predictorPredictor

The model or ensemble whose predictions are smoothed. In a run_cv replay, wrap a RecordedPredictor(..., history=True) so each fold’s warm-up is what the earlier folds predicted.

halflife_barsfloat

The halflife of the weights, in bars; positive.

warmup_barsint, optional

Bars before the window the mean reads; non-negative. Ten halflives, rounded up, by default.

standardizebool, default True

Whether to z-score each bar over its symbols before smoothing.

Attributes:
last_warmup_barsint or None

The warm-up bars the last predict_window read.

Raises:
ValueError

If halflife_bars is not positive and finite or warmup_bars is negative; from predict_window, if a warm-up bar lies inside the loaded checkpoint’s fitted training window.

Examples

With whole a run_cv result, model the predictor it ran and backtester a run_cv backtester over the same folds:

>>> recorded = RecordedPredictor(model, whole.run_dir, history=True)
>>> backtester.config = dataclasses.replace(
...     backtester.config, model=SmoothedPredictor(recorded, halflife_bars=20)
... )
>>> smoothed = backtester.run_cv()  # every fold's warm-up from the earlier folds
>>> SmoothedPredictor(None, halflife_bars=2.5).warmup_bars
25
config_cls#

The config dataclass the wrapper is serialised and rebuilt with.

alias of SmoothedPredictorConfig

property label_scales#

"standardized" for every label when standardising, else the wrapped predictor’s.

Examples

>>> SmoothedPredictor(model, halflife_bars=5).label_scales
{'fwd_ret_1': 'standardized'}
predict_window(start, end)[source]#

The smoothed predictions for every bar from start to end.

The wrapped predictor is asked for the window and days before it, widened until warmup_bars bars before start come back or it holds no earlier bar; the last warmup_bars are kept.

Raises:
ValueError

If a warm-up bar lies inside the loaded checkpoint’s fitted training window (an in-sample fit), unless the wrapped predictor serves recorded history (RecordedPredictor(history=True)).

Examples

With model a loaded model over business days:

>>> out = SmoothedPredictor(model, halflife_bars=5).predict_window("2024-02-12", "2024-03-11")
>>> str(out.timestamp.values[0])[:10]  # the warm-up bars are not returned
'2024-02-12'
smooth(predictions)[source]#

Return the causal exponential mean of every variable of predictions.

Every bar of predictions is read; nothing before it. See the class docstring for the weights.

Parameters:
predictionsxr.Dataset

Predictions on (timestamp, symbol), in time order.

Returns:
xr.Dataset

The smoothed panel on the same coordinates, NaN where the input is NaN (or, standardising, where the bar is degenerate).

Examples

>>> panel = xr.Dataset(
...     {"fwd_ret_1": (("timestamp", "symbol"), [[1.0, 2.0, 3.0], [3.0, 2.0, np.nan]])},
...     coords={"timestamp": pd.bdate_range("2024-01-01", periods=2),
...             "symbol": ["A", "B", "C"]},
... )
>>> SmoothedPredictor(None, halflife_bars=1).smooth(panel)["fwd_ret_1"].values.round(4)
array([[-1.    ,  0.    ,  1.    ],
       [ 0.1381, -0.4714,     nan]])