From 194cc660b5d78a977f48318c3762010003e997f1 Mon Sep 17 00:00:00 2001 From: taro0915 Date: Tue, 11 Aug 2026 17:46:16 +0900 Subject: [PATCH] Docs: clarify equal-weighted portfolio vs cap-weighted benchmark comparison --- docs/component/strategy.rst | 8 ++++++++ qlib/contrib/strategy/signal_strategy.py | 18 ++++++++++++++++++ qlib/workflow/record_temp.py | 14 ++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/docs/component/strategy.rst b/docs/component/strategy.rst index 910ebf7083b..cc7dc8cc299 100644 --- a/docs/component/strategy.rst +++ b/docs/component/strategy.rst @@ -88,6 +88,14 @@ TopkDropoutStrategy - Generate the order list from the target amount +.. note:: + `TopkDropoutStrategy` holds an **equal-weighted** portfolio: the available cash is split + evenly among the stocks bought on each rebalance. When its results are compared against a + capitalization-weighted benchmark such as the default ``SH000300``, the reported excess + return also contains the equal- versus cap-weighting spread of the universe, which is a + property of the weighting scheme rather than of the signal. Use a benchmark whose weighting + scheme matches the strategy if you want to isolate the contribution of the signal. + EnhancedIndexingStrategy ------------------------ `EnhancedIndexingStrategy` Enhanced indexing combines the arts of active management and passive management, diff --git a/qlib/contrib/strategy/signal_strategy.py b/qlib/contrib/strategy/signal_strategy.py index bad19ddfdc9..17718720da4 100644 --- a/qlib/contrib/strategy/signal_strategy.py +++ b/qlib/contrib/strategy/signal_strategy.py @@ -73,6 +73,24 @@ def get_risk_degree(self, trade_step=None): class TopkDropoutStrategy(BaseSignalStrategy): + """ + An equal-weighted top-k strategy with fixed daily turnover (the ``Topk-Drop`` algorithm). + + .. note:: + Positions are **equal-weighted**. On each rebalance the available cash is divided + evenly among the stocks to be bought (see :meth:`generate_trade_decision`), so the + realized portfolio approximates an equal-weighted portfolio of ``topk`` names. + + This is worth keeping in mind when reading the excess return reported by + :class:`qlib.workflow.record_temp.PortAnaRecord`, which is a plain difference + against a single benchmark instrument. The default benchmark ``SH000300`` + (CSI 300) is capitalization-weighted, so that difference also contains the return + spread between equal- and cap-weighting of the universe, which is a property of + the weighting scheme rather than of the signal. To isolate the contribution of the + signal, use a benchmark whose weighting scheme matches the strategy, e.g. + an equal-weighted portfolio of the same universe run through the same executor. + """ + # TODO: # 1. Supporting leverage the get_range_limit result from the decision # 2. Supporting alter_outer_trade_decision diff --git a/qlib/workflow/record_temp.py b/qlib/workflow/record_temp.py index ecd58ec2098..5d4f937ad0b 100644 --- a/qlib/workflow/record_temp.py +++ b/qlib/workflow/record_temp.py @@ -365,6 +365,20 @@ class PortAnaRecord(ACRecordTemp): - The return report and detailed positions of the backtest, returned by `qlib/contrib/evaluate.py:backtest` - port_analysis.pkl : The risk analysis of your portfolio, returned by `qlib/contrib/evaluate.py:risk_analysis` + + .. note:: + ``excess_return_without_cost`` and ``excess_return_with_cost`` are computed as a + plain arithmetic difference between the portfolio return and the return of the + single instrument configured as ``config["backtest"]["benchmark"]``. No adjustment + is made for a difference in weighting scheme between the strategy and the benchmark. + + The default configuration pairs an equal-weighted strategy + (:class:`~qlib.contrib.strategy.signal_strategy.TopkDropoutStrategy`) with a + capitalization-weighted benchmark (``SH000300``). Under that pairing the reported + excess return also contains the equal- versus cap-weighting spread of the universe, + so it should be read as performance relative to that specific index rather than as + the contribution of the signal alone. Choosing a benchmark whose weighting scheme + matches the strategy removes that component. """ artifact_path = "portfolio_analysis"