quantlab.portfolio.decision_inputs.DecisionInputs#
- class quantlab.portfolio.decision_inputs.DecisionInputs(dataset, constructor, *, fill_column, valuation_column, rebalance_periods, anchor, execution=None, end=None)[source]#
Bases:
objectAssemble a rule’s decision inputs from a price dataset and decide with it.
The one place a
PortfolioContextis built.weightsdecides a whole prediction panel, replaying the holdings between rebalances, for the research backtester;contextbuilds one bar’s context with the holdings an executor injects, andrebalancestells it which bars to decide. On every rebalance bar both give the same context. A replay building a context per bar reads its window once withpreloaded.- Parameters:
- datasetMarketDataset
The price dataset: its panel, calendar,
tradable_barsanddelisting_bars.- constructorPortfolioConstructor
The rule, already bound to the predictor’s labels.
- fill_column, valuation_columnstr
The price orders fill at and the price the portfolio is valued at.
- rebalance_periodsint
Rebalance every this many bars of the dataset’s calendar.
- anchorpd.Timestamp or datetime-like
The bar the schedule counts from: the first bar of the prediction panel a run decides on.
- executionExecutionSettings, optional
The sizing basis, fees and slippage
weightsreplays the holdings with. Default: sizing at the fill price, no costs.- endpd.Timestamp or datetime-like, optional
The last bar of a replay, on which
rebalancesis False (an order decided there has no next bar to fill on);Nonefor an open-ended run.weightsnever rebalances its panel’s last bar.
- Raises:
- TypeError
If
constructoris not aPortfolioConstructor.- ValueError
If
rebalance_periodsis smaller than 1.
Examples
>>> import numpy as np, pandas as pd, xarray as xr >>> from quantlab.portfolio.config import TopNConfig >>> from quantlab.dataset.memory import FrameDataset >>> from quantlab.portfolio.predefined.top_n import TopNConstructor >>> ts = pd.bdate_range("2024-01-01", periods=4) >>> symbols = ["AAA", "BBB", "CCC"] >>> close = xr.DataArray( ... [[10.0, 20.0, 30.0], [11.0, 20.0, 31.0], [12.0, np.nan, 30.0], [12.0, 21.0, 29.0]], ... dims=("timestamp", "symbol"), coords={"timestamp": ts, "symbol": symbols}, ... ) >>> prices = FrameDataset(xr.Dataset({"open": close, "close": close})) >>> inputs = DecisionInputs( ... prices, ... TopNConstructor(TopNConfig(direction="long_only", top_n=1)), ... fill_column="open", ... valuation_column="close", ... rebalance_periods=2, ... anchor=ts[0], ... ) >>> scores = xr.Dataset( ... {"ret": (("timestamp", "symbol"), [[0.1, 0.9, 0.2], [0.0, 0.0, 0.0], [0.9, 0.5, 0.1], [0.0, 0.0, 0.0]])}, ... coords={"timestamp": ts, "symbol": symbols}, ... ) >>> inputs.weights(scores)["weight"].values array([[ 0., 1., 0.], [nan, nan, nan], [ 0., 1., 0.], [nan, nan, nan]])
BBB is halted at the third bar, so it stays locked. An executor holding all of BBB there gets the same context:
>>> [inputs.rebalances(t) for t in ts] [True, False, True, False] >>> held = xr.DataArray([1.0], dims="symbol", coords={"symbol": ["BBB"]}) >>> bar = inputs.context(ts[2], scores["ret"].isel(timestamp=2).to_dataset(), held) >>> bar.tradable.values, bar.locked.values (array([ True, False, True]), array([False, True, False]))
- context(t, predictions, current_weights)[source]#
Build the context of one bar from the dataset and the holdings given.
The bar’s symbols are the predicted ones followed by any held symbol without a prediction (its predictions NaN, so it is not selectable and stays locked where it cannot trade). The tradability, the return window and staleness of the last
history_barsvaluation prices up tot, and the rule’s factor values att(each computed up totwith its own warm-up) are read from the dataset, and its risk model’s exposures and estimate row attfrom the model (the row on every call, so a store extended since is seen); with the holdings the panel entry replayed, the context equals the oneweightsbuilt att. No warning is given for a short history. Insidepreloadedthe same values are sliced from memory.- Parameters:
- tpd.Timestamp or datetime-like
The bar decided on, a bar of the dataset’s calendar.
- predictionsxr.Dataset
Every label’s prediction at the bar, one variable per label on
symbol. A scalartimestampcoordinate is dropped.- current_weightsxr.DataArray
The weights held, valued at the bar’s valuation price, on
symbol: finite, 0.0 where nothing is held. A symbol it lacks is not held.
- Returns:
- PortfolioContext
- Raises:
- ValueError
If an input is not on
symbolalone or repeats a symbol, a current weight is not finite,tis not a bar of the dataset, the risk model’s exposures store does not covert(under"read"), or, insidepreloaded,tis outside the preloaded window.
Examples
BBB has no price at the bar, and top-n reads a window of one bar:
>>> bar.current_weights.values, bar.staleness.values (array([0., 1., 0.]), array([ 0., nan, 0.]))
- decision_symbols(predictions)[source]#
Return which symbols of a prediction panel a run decides on.
A symbol with a finite prediction of any label on some bar of the panel, the only symbols a rule can buy (and so hold), or with a finite value of one of the rule’s
axis_factorson some bar from the panel’s first bar to its last (those factors computed once over the range, with their own warm-up). A run drops the other symbols from its axis: the rule never holds them, and none of its inputs read them. A rule that keeps every symbol (keeps_every_symbol: a covariance estimated from the context’s symbols together, such asLedoitWolfEstimator’s shrinkage) keeps them all.- Parameters:
- predictionsxr.Dataset
One variable per label on
(timestamp, symbol).
- Returns:
- np.ndarray
Booleans on the panel’s
symbol.
- Raises:
- ValueError
If an axis factor is not an output of a declared factor.
Examples
>>> inputs.decision_symbols(scores.where(scores.symbol != "CCC")) array([ True, True, False])
- classmethod from_run(run_dir, *, end=None)[source]#
Rebuild the decision inputs of a recorded backtest run.
Read through
BacktestRun: the rule (rebuild("constructor")), the price dataset (rebuild("price_dataset"), an in-memory one reading the copy under the run directory), the market columns, the execution settings and the rebalance periods; from the run’s prediction panel, the label specs the rule is bound to and the anchor (the panel’s first bar). Withoutendthe schedule is open-ended, as a live run’s is: it keeps counting past the run’s last bar. Neither the model nor the factor, label or backtest layers are imported (a rule declaring factors imports its factors’ layer).weightson the run’s predictions reproduces the run’s weights.- Parameters:
- run_dirstr or os.PathLike
A run directory written by
run()orrun_cv().- endpd.Timestamp or datetime-like, optional
The last bar of a replay (the run’s own last bar to replay it whole), on which
rebalancesis False;Nonefor an open-ended run.
- Returns:
- DecisionInputs
- Raises:
- FileNotFoundError
If the run has no prediction panel (a
run_weights()run has no model and so no panel).- ValueError
If the run is not a backtest run, records no constructor, or the rule refuses the specs.
Examples
With
run_dirthe directory of arun()with a top-2 rule:>>> inputs = DecisionInputs.from_run(run_dir) >>> inputs.constructor TopNConstructor(direction='long_only', top_n=2, score_label=None) >>> predictions = BacktestRun.open(run_dir).predictions().predictions >>> weights = inputs.weights(predictions) # the run's weights
- history_prices(first, last)[source]#
Return the fill and valuation prices the bars from
firsttolastread, warm-up included.From
history_bars - 1bars beforefirst(a warning names the shortfall when the dataset holds fewer, asweightswarns) tolast, on every symbol, in one request: whatpanelandpanel_readstake aspricesfor any block of those bars.- Parameters:
- first, lastpd.Timestamp or datetime-like
The first and last bar.
- Returns:
- xr.Dataset
Examples
>>> inputs.history_prices(ts[1], ts[3]).sizes["timestamp"] # top-n reads no warm-up 3
- panel(predictions, *, prices=None, reads=None)[source]#
Return the decision inputs of a block of decision bars at once, for the approximate backtest.
The panel counterpart of
context: each bar’s predictions, its tradability (tradable_bars, judged from nothing later than the bar) and the raw valuation prices its return window and staleness are read from (the rule’shistory_barsup to the bar), and the values of the rule’s declared factors (oneFactor.computeeach from the first to the last bar, with its own warm-up), as the per-bar context reads them;DecisionPanel.return_windowsandDecisionPanel.stalenessgive each bar’s window and staleness as its context holds them. With a declared factor risk model, its exposures at the bars and its estimate store’s rows there, aspanel_readsreads them. No holdings: an approximate rule’sdecideis handed those.- Parameters:
- predictionsxr.Dataset
One variable per label on
(timestamp, symbol), at the decision bars (bars of the dataset’s calendar, in time order).- pricesxr.Dataset, optional
The fill and valuation prices on
(timestamp, symbol)from the first bar’s warm-up (history_prices) to the last bar or beyond, already read (an approximate backtest reads its window once); read from the dataset when omitted.- readsPanelReads, optional
The tradability, declared factors and risk model’s exposures and estimate rows covering the bars, already read (
panel_readsover a longer span, read once for many blocks); read over the bars when omitted.
- Returns:
- DecisionPanel
- Raises:
- ValueError
If a bar is not a bar of the prices, or
pricesstarts after the first bar’s warm-up.
Examples
>>> block = inputs.panel(scores.isel(timestamp=[0, 2])) >>> block.tradable array([[ True, True, True], [ True, False, True]]) >>> block.staleness() array([[ 0., 0., 0.], [ 0., nan, 0.]])
- panel_reads(first, last, *, prices=None)[source]#
Read what
paneltakes for any block of the bars fromfirsttolast, once.Each in one request, as
weightsreads them over its panel: the tradability of every bar of the range (tradable_bars, judged from nothing later than the bar), the rule’s declared factors (oneFactor.computeeach over the range, with its own warm-up), and a declared factor risk model’s exposures (FactorRiskModel.exposures, read or computed per itsexposure_data_strategy) and its estimate store’s rows over the part of the range it covers. All on every symbol.- Parameters:
- first, lastpd.Timestamp or datetime-like
The first and last bar.
- pricesxr.Dataset, optional
The fill and valuation prices covering the range, already read (
history_prices); read from the dataset when omitted.
- Returns:
- PanelReads
- Raises:
- ValueError
If the exposures store does not cover the range (under
"read").
Examples
>>> reads = inputs.panel_reads(ts[1], ts[3]) # top-n declares no factor or risk model >>> reads.tradable.values array([[ True, True, True], [ True, False, True], [ True, True, True]]) >>> reads.factors is None, reads.risk_exposures is None (True, True)
- panel_scheduled(timestamps)[source]#
Return which bars of a panel are scheduled rebalance bars:
scheduled, the panel’s last bar excepted.A weight decided at a bar fills on the next one, so the panel’s last bar, with no bar after it, is never decided. Both backtests take their schedule from here (
weightsand the approximate backtest).- Parameters:
- timestampsarray_like of datetime-like
The panel’s bars, in order.
- Returns:
- np.ndarray
One boolean per bar, the last one
False.
Examples
>>> inputs.panel_scheduled(ts) array([ True, False, True, False])
- preloaded(start, end)[source]#
Read what the contexts of the bars from
starttoendneed once; slice them from memory inside.For a replay that builds a context per bar (an event-driven executor’s closed loop, a recheck of its decisions), which otherwise reads its sources again on every bar. On entering, over the dataset’s bars from
starttoend(the window) it reads, each in one request through the usual read seams, so an openquantlab.runs.record.DataRecorderrecords each as one request over the window: the fill and valuation prices from thehistory_bars - 1bars before the window’s first bar (or the dataset’s first bar) to its last, on every symbol; their tradability (tradable_bars); the rule’s declared factors (Factor.computeover the window, asweightscomputes them over its panel); its declared risk model’s exposures; and the model’s estimate rows over the part of the window its estimate store covers.Inside,
contextat a bar of the window slices the bar’s inputs from what was read and returns the context it builds without the preload, value for value: the price window and its warm-up are counted on the dataset’s calendar asbar_beforecounts them, so a bar near the window’s start has the same history, and nothing after a bar enters its context.contextat a bar outside the window raisesValueError;weightsandrebalancesare unaffected. On leaving, everything read is dropped. A factor whose value at a bar depends on where its computation starts, beyond itswarmup_bars, would differ, as it differs betweenweightsandcontextalready.- Parameters:
- start, endpd.Timestamp or datetime-like
The window: the first and last bar whose context is built. A date-only
endincludes every bar of that day.
- Yields:
- DecisionInputs
self, preloaded.
- Raises:
- ValueError
If the dataset has no bar from
starttoend,selfis already preloaded, or a read refuses its range (an exposures store not covering the window, under"read").
Examples
>>> with inputs.preloaded(ts[1], ts[3]): ... again = inputs.context(ts[2], scores["ret"].isel(timestamp=2).to_dataset(), held) >>> again.tradable.values, again.staleness.values (array([ True, False, True]), array([ 0., nan, 0.])) >>> with inputs.preloaded(ts[2], ts[3]): ... inputs.context(ts[1], scores["ret"].isel(timestamp=1).to_dataset(), held) Traceback (most recent call last): ValueError: 2024-01-02 00:00:00 is outside the preloaded window 2024-01-03 00:00:00 to 2024-01-04 00:00:00; ...
- rebalances(t)[source]#
Return whether bar
tis a scheduled rebalance bar.Every
rebalance_periods-th bar of the dataset’s calendar from the anchor rebalances, exceptend. A bar before the anchor, at or afterendor off the calendar never does. The calendar’s timestamps (never its prices) are read once up to the dataset’s last bar, and again only for a bar past it.weightsdecides the bars of its panel this schedule names, its last bar excepted.- Parameters:
- tpd.Timestamp or datetime-like
The bar.
- Returns:
- bool
Examples
>>> inputs.rebalances(ts[2]), inputs.rebalances(ts[3]) (True, False)
- scheduled(timestamps)[source]#
Return which of
timestampsare scheduled rebalance bars:rebalancesfor many bars.- Parameters:
- timestampsarray_like of datetime-like
The bars.
- Returns:
- np.ndarray
One boolean per bar.
Examples
>>> inputs.scheduled(ts) array([ True, False, True, False])
- weights(predictions, *, delisted=None)[source]#
Decide every rebalance bar of a prediction panel and return the target weights.
Reads the dataset’s fill and valuation prices from the
history_bars - 1bars before the panel’s first bar to its last (a warning names the shortfall when the dataset holds fewer, and the first windows are short), the tradability (tradable_bars) of the panel’s bars, the rule’s declared factors over the panel, each with its own warm-up, and the exposures of its declared risk model over the panel and its estimate store’s rows from the first to the last rebalance bar, in one request. It then loopsdecideover the rebalance bars in time order. Each bar’s context holds its predictions, its tradability, the return window and staleness of its lasthistory_barsvaluation prices, its factor values, its risk exposures and estimate row, and the weights currently held: the earlier targets replayed by the Execution module under theexecutionsettings, exactly as the simulation trades them (filled at the next bar’s fill price, rejected without a raw price, a holding marked indelistedsettled at its last valuation on the next bar, sells before buys, fees and slippage), valued at the bar’s forward-filled valuation price; all 0.0 before the first rebalance.A bar that returns all NaN holds, and so does one whose
constructraisesPortfolioConstructionError(with a warning), listed inattrs["failed_bars"]. Events a row reports inattrs["events"]are gathered by name into the result’sattrs["events"], one record per bar:{"bar", "symbols"}for an event naming its symbols,{"bar", "count"}for one counting them. A bar that does not rebalance gets an all-NaN row.The result records the rebalances:
scheduledmarks the bars the schedule named (rebalances, the panel’s last bar excepted) anddecidedthose of them the rule decided, so a held bar (a failure or an all-NaN row) is scheduled and not decided.decidedis exactly the rows with a finite weight; readers take it from the record rather than testing the rows.- Parameters:
- predictionsxr.Dataset
One variable per label on
(timestamp, symbol); its bars are consecutive bars of the dataset’s calendar, the first at or after the anchor.- delistedxr.DataArray, optional
Booleans marking each delisted symbol’s last priced bar, on the panel’s labels or a part of them: the marks the simulation settles with. Default: the dataset’s
delisting_barsof the panel’s bars.
- Returns:
- xr.Dataset
weighton the predictions’ labels,scheduledanddecided(booleans ontimestamp),attrs["failed_bars"](ISO timestamps) andattrs["events"].
- Raises:
- ValueError
If the panel starts before the anchor, a prediction bar is not a price bar, the risk model’s exposures store does not cover the panel (under
"read"), orconstructreturns a row breaking the weights contract.
Examples
>>> out = inputs.weights(scores) >>> out.attrs["failed_bars"], out.attrs["events"] ([], {}) >>> out["scheduled"].values, out["decided"].values (array([ True, False, True, False]), array([ True, False, True, False]))