quantlab.portfolio.predefined.approximate.mean_variance.decide#
- quantlab.portfolio.predefined.approximate.mean_variance.decide(rule, panel, prices=None, stopping=Stopping(tolerance=1e-05, max_iterations=20000), *, posterior=None)[source]#
Return every bar’s mean-variance weights of a decision panel: in time order when they read the holdings, at once otherwise.
muand the covariance are the per-bar rule’s (see the module docstring).The candidates are each bar’s predicted symbols, with
candidate_top_konly the topcandidate_top_kbymu(|mu|long-short), and with a turnover penalty orcandidate_top_kalso the selectable symbols held. A bar with no candidate, or long-only too few to hold the book underweight_cap, holds (an all-NaN row), as a backtest run holds it. Each bar maximises the per-bar rule’s objective on its candidates in compiled code (_support.mean_variance_kernels), only on the columns some bar of the panel can select: accelerated proximal gradient whose step backtracks below the curvature on the budget’s tangent space, until the residual meetsstopping. Long-only on the capped simplex, long-short onsum(w) = 0,|w|_1 <= 1,|w| <= weight_cap.With
max_namesevery bar’s book holds at most that many names, long and short counted together: each bar is first solved without the cap, and the capped descent starts from that book (and from the holdings, the better book kept); each projection keeps the names whose zeroing costs it most and projects again on them, the steps have no momentum, and a swap search then moves one held name’s weight to an unheld candidate while that lowers the objective (#363). That is a local optimum of the mixed-integer program a backtest run solves exactly.With a
turnover_penaltyorcandidate_top_kthe bars are solved forward in time, each once: its holdings are its book (the last decided bar’s weights, orprices.previous, at their fill prices) marked at its close over the book’s value, cash included; the objective losesturnover_penalty * |w - holdings|_1, which enters the projection as an L1 pull toward the holdings, and the solve starts from the holdings. A held bar leaves the book as it was. Otherwise the bars are independent and solved at once, in parallel except on macOS; a held symbol without a prediction is then not a candidate (the per-bar rule keeps it atmu = 0).- Parameters:
- ruleMeanVarianceOptimizer
The rule, bound to the predicted labels (its
spanread).- panelDecisionPanel
The bars’ decision inputs.
- pricesPanelPrices, optional
The book before the first bar and the prices it drifts with;
Nonefor no book.- stoppingStopping
The residual at which a bar has converged, and the iterations after which it stops unconverged.
- posteriorcallable, optional
The per-rule hook ahead of the kernels, the panel counterpart of
MeanVarianceOptimizer.posterior:posterior(rule, panel, problem)returns thePanelProblemto solve, itsmu, its covariance (of any rank) and its candidates (predicted) replaced as the rule’s per-barposteriorreplaces them;candidate_top_kranks the returnedmu. Unlike the per-bar hook’s NaN, the problem’sexpected_returnis 0.0 off the candidates, so a hook tells a symbol without a prediction apart bypredicted.None(the default) solves the problem as it is. An approximate implementation of a rule built on the optimiser passes its own.
- Returns:
- PanelSolution
[bars, symbols]weights, each bar’s iterations and whether it converged.
- Raises:
- RuntimeError
If the rule has not been bound to label specs.
Examples
With
panelthe two-bar decision panel of thequantlab.portfolio.approximate.estimate_allexample (CCCnot covered):>>> from quantlab.portfolio.config import ( ... GrinoldExpectedReturnConfig, LedoitWolfEstimatorConfig, MeanVarianceConfig, ... ) >>> from quantlab.portfolio.predefined.expected_return import GrinoldExpectedReturn >>> from quantlab.portfolio.predefined.ledoit_wolf import LedoitWolfEstimator >>> from quantlab.portfolio.predefined.mean_variance import MeanVarianceOptimizer >>> from quantlab.runs.prediction_panel import LabelSpec >>> optimizer = MeanVarianceOptimizer(MeanVarianceConfig( ... expected_return_label="ret_5", risk_aversion=10.0, weight_cap=0.8, ... expected_return=GrinoldExpectedReturn(GrinoldExpectedReturnConfig(ic=0.05)), ... covariance=LedoitWolfEstimator(LedoitWolfEstimatorConfig(lookback_bars=20)), ... )) >>> optimizer.bind([LabelSpec(name="ret_5", scale="raw", delay=1, span=5)]) >>> decide(optimizer, panel).weights.round(3) array([[0.578, 0.422, 0. ], [0.577, 0.423, 0. ]])
With a turnover penalty, a book of 0.2 AAA and 0.8 BBB held before the first bar, prices unchanged, pulls both bars toward it:
>>> import dataclasses >>> penalised = MeanVarianceOptimizer(dataclasses.replace(optimizer.config, turnover_penalty=0.002)) >>> penalised.bind([LabelSpec(name="ret_5", scale="raw", delay=1, span=5)]) >>> book = PanelPrices( ... previous=np.array([0.2, 0.8, 0.0]), previous_entry=np.ones(3), ... close=np.ones((2, 3)), entry=np.ones((2, 3)), ... ) >>> decide(penalised, panel, book).weights.round(3) array([[0.481, 0.519, 0. ], [0.482, 0.518, 0. ]])