quantlab.portfolio.predefined.topk_dropout.TopkDropoutConstructor#

class quantlab.portfolio.predefined.topk_dropout.TopkDropoutConstructor(config)[source]#

Bases: PortfolioConstructor

Qlib’s TopkDropout on each rebalance bar.

For a book holding the symbols last:

  1. today: the n_drop + topk - len(last) best-scored symbols that are tradable, have a finite score and are not held.

  2. sell: the holdings among the n_drop worst of last and today ranked together, so a holding is sold only for a better symbol. A holding without a finite score ranks worst. A locked position (held, not tradable) is never sold and keeps its weight.

  3. buy: the first len(sell) + topk - len(last) of today, so an empty or short book fills up to topk.

Kept holdings stay at their current weight (context.current_weights); the buys split equally what the kept holdings leave of the book’s budget, or the sold weight when that is more (a side that drifted above its budget spends its sale proceeds, as qlib spends its cash). Long-short, the whole book stays within a gross of 1: the two sides’ buys share what the kept book leaves of it, and a kept book above it is scaled down, its locked positions excepted (see book). A book holding more than topk (locked positions) gets no candidates, so it only sells down toward topk. With direction="long_only" the budget is 1; with "long_short" each side has 0.5, the short side runs the same rule on the negated scores, and its candidates exclude every holding and the bar’s long buys, so the two books never share a symbol. Ties rank by symbol order. qlib’s hold_thresh, random methods and price-limit rules are not reproduced.

Parameters:
configTopkDropoutConfig

direction, topk, n_drop 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", topk is smaller than 1 or n_drop is negative.

Examples

>>> import numpy as np, pandas as pd, xarray as xr
>>> from quantlab.portfolio.config import TopkDropoutConfig
>>> from quantlab.portfolio.base import PortfolioContext
>>> rule = TopkDropoutConstructor(TopkDropoutConfig(direction="long_only", topk=2, n_drop=1))
>>> symbols = ["AAA", "BBB", "CCC", "DDD"]
>>> def on_symbols(values):
...     return xr.DataArray(values, dims="symbol", coords={"symbol": symbols})
>>> context = PortfolioContext(
...     timestamp=pd.Timestamp("2024-01-02"),
...     predictions=xr.Dataset({"ret": on_symbols([0.3, 0.1, 0.2, 0.4])}),
...     tradable=on_symbols([True] * 4),
...     current_weights=on_symbols([0.55, 0.45, 0.0, 0.0]),
... )
>>> rule.construct(context).values  # BBB makes way for DDD; AAA keeps its weight
array([0.55, 0.  , 0.  , 0.45])
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
>>> rule = TopkDropoutConstructor(TopkDropoutConfig("long_only", 2, 1, score_label="ret_20"))
>>> rule.bind([LabelSpec("ret_5", "raw", 1, 5)])
Traceback (most recent call last):
ValueError: score_label 'ret_20' is not one of the predicted labels ['ret_5']
book(scores, current, tradable, locked)[source]#

Return the book of one bar from arrays on its symbols: the rule of the class docstring.

Long-short, the book never exceeds a gross of 1: a kept book above it (the shorts lost, or a side drifted past its half) has its positions that can trade scaled down to it, and the two sides’ buys share what the kept book leaves, in the ratio of what each would spend alone. Without such a drift every weight is the one-side rule’s.

Examples

>>> rule = TopkDropoutConstructor(TopkDropoutConfig("long_short", topk=1, n_drop=0))
>>> rule.book(np.array([0.9, -0.9]), np.array([0.5, -0.51]), np.ones(2, bool), np.zeros(2, bool)).round(4)
array([ 0.495, -0.505])
config_cls#

alias of TopkDropoutConfig

construct(context)[source]#

Return the TopkDropout 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

>>> import numpy as np, pandas as pd, xarray as xr
>>> symbols = ["AAA", "BBB", "CCC", "DDD"]
>>> def on_symbols(values):
...     return xr.DataArray(values, dims="symbol", coords={"symbol": symbols})
>>> context = PortfolioContext(
...     timestamp=pd.Timestamp("2024-01-02"),
...     predictions=xr.Dataset({"ret": on_symbols([0.3, 0.1, 0.2, 0.4])}),
...     tradable=on_symbols([True] * 4),
...     current_weights=on_symbols([0.0] * 4),
... )
>>> rule = TopkDropoutConstructor(TopkDropoutConfig("long_short", topk=1, n_drop=1))
>>> rule.construct(context).values
array([ 0. , -0.5,  0. ,  0.5])
ranking_label(names)[source]#

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

Examples

>>> TopkDropoutConstructor(TopkDropoutConfig("long_only", 2, 1, score_label="ret_1")).ranking_label(["ret_5", "ret_1"])
'ret_1'