quantlab.portfolio.predefined.top_n.TopNConstructor#

class quantlab.portfolio.predefined.top_n.TopNConstructor(config)[source]#

Bases: PortfolioConstructor

Equal-weight top-n selection on each rebalance bar.

A locked position (held, not tradable at the bar) keeps its current weight and is never picked. The picks come from the other symbols that are tradable with a finite score, ranked with a stable sort, so ties resolve by symbol order. When the cut of a book that picks falls inside a group of equal scores, the row’s attrs["events"]["tie_at_cutoff"] counts the tied symbols left out, so a run records how often its picks were decided by symbol order rather than by the scores. With direction="long_only" each of the k highest-scoring picks gets (1 - L) / k, L the sum of the locked weights; with nothing locked that is 1/k, for a gross exposure of 100%. With direction="long_short" the top k get (0.5 - L_long) / k each and the bottom k get -(0.5 - L_short) / k each, L_long and L_short the locked long and short exposure; the two books never share a symbol. A side with no budget left adds nothing. k is top_n, reduced when fewer symbols can be picked, and a warning names the bar. With no pick the row is the locked positions and 0.0 elsewhere.

Parameters:
configTopNConfig

direction, top_n and score_label, the label ranking the symbols (None for the predictor’s first label).

Raises:
ValueError

If direction is not "long_only" or "long_short", or top_n is smaller than 1.

Examples

>>> import numpy as np, pandas as pd, xarray as xr
>>> from quantlab.portfolio.config import TopNConfig
>>> from quantlab.portfolio.base import PortfolioContext
>>> rule = TopNConstructor(TopNConfig(direction="long_only", top_n=2))
>>> symbols = ["AAA", "BBB", "CCC", "DDD"]
>>> context = PortfolioContext(
...     timestamp=pd.Timestamp("2024-01-02"),
...     predictions=xr.Dataset({"ret": ("symbol", [0.3, 0.1, np.nan, 0.2])},
...                            coords={"symbol": symbols}),
...     tradable=xr.DataArray([True, True, True, True], dims="symbol",
...                           coords={"symbol": symbols}),
...     current_weights=xr.DataArray(np.zeros(4), dims="symbol",
...                                  coords={"symbol": symbols}),
... )
>>> rule.construct(context).values
array([0.5, 0. , 0. , 0.5])
bind(labels)[source]#

Refuse empty label specs, or specs without score_label.

Any scale ranks, so a spec’s scale is not read.

Raises:
ValueError

If labels is empty, or score_label is set and not one of them; the message lists the labels.

Examples

>>> from quantlab.runs.prediction_panel import LabelSpec
>>> specs = [LabelSpec("ret_5", "raw", 1, 5), LabelSpec("ret_1", "raw", 1, 1)]
>>> rule = TopNConstructor(TopNConfig(direction="long_only", top_n=2, score_label="ret_20"))
>>> rule.bind(specs)
Traceback (most recent call last):
ValueError: score_label 'ret_20' is not one of the predicted labels ['ret_5', 'ret_1']
config_cls#

alias of TopNConfig

construct(context)[source]#

Return the equal-weight top-n book of the context’s bar.

See the class docstring for the rule and an example.

Parameters:
contextPortfolioContext

The bar’s predictions, tradability and current weights.

Returns:
xr.DataArray

One finite weight per symbol of context.symbols.

Examples

>>> rule = TopNConstructor(TopNConfig(direction="long_short", top_n=1))
>>> rule.construct(context).values
array([ 0.5, -0.5,  0. ,  0. ])
ranking_label(names)[source]#

Return score_label, or the first predicted label when it is None.

Examples

>>> TopNConstructor(TopNConfig(direction="long_only", top_n=2, score_label="ret_1")).ranking_label(["ret_5", "ret_1"])
'ret_1'