TopkDropoutStrategy ignores get_risk_degree() for position sizing
Bug Description
get_risk_degree() is documented as the dynamic market-timing hook ("Dynamically risk_degree will result in Market timing.", qlib/contrib/strategy/signal_strategy.py:69). But TopkDropoutStrategy.generate_trade_decision sizes new buys with the raw attribute self.risk_degree, not the getter:
# qlib/contrib/strategy/signal_strategy.py:266
value = cash * self.risk_degree / len(buy) if len(buy) > 0 else 0
It is the only use of risk_degree in TopkDropoutStrategy, and it bypasses get_risk_degree(). So a subclass that overrides get_risk_degree() to return a time-varying multiplier (the documented way to implement market timing) has zero effect on a TopkDropout book — the override is never called. (By contrast, WeightStrategyBase at line 365 and its subclass EnhancedIndexingStrategy at line 482 do call self.get_risk_degree(...), so the inconsistency is within the same module.)
To Reproduce
- Subclass
TopkDropoutStrategyand overrideget_risk_degree()to return, say,0.0on some dates (a regime gate to cash) andself.risk_degreeotherwise. - Backtest it vs. the un-subclassed
TopkDropoutStrategyon the same signal. - The two produce identical trades/returns — the overridden
get_risk_degree()is never consulted for buy sizing.
Expected Behavior
TopkDropoutStrategy should size buys with self.get_risk_degree(trade_step) (as WeightStrategyBase does), so that overriding get_risk_degree() actually enables market timing as documented:
value = cash * self.get_risk_degree(trade_step) / len(buy) if len(buy) > 0 else 0
Environment
- Qlib version: 0.9.7 (
qlib/contrib/strategy/signal_strategy.py(TopkDropoutStrategy.generate_trade_decision, line ~266) - Python version: 3.12
Additional context
Discovered while building a BTC-trend regime overlay (RegimeGatedTopkStrategy(TopkDropoutStrategy)) that gates gross exposure via get_risk_degree(). The overlay had no effect on backtests until we worked around it by pushing the gated value onto self.risk_degree per step before delegating to super().generate_trade_decision(). A two-line fix in TopkDropoutStrategy (use the getter) would remove the need for the workaround.
Source: microsoft/qlib