microsoft/qlib · error · NotImplementedError
The decision didn't provide an index range
Error message
The decision didn't provide an index range
What it means
BaseTradeDecision._get_range_limit unconditionally raises NotImplementedError when the decision carries no trade_range, because without a trade range the decision cannot express a unified start/end index pair. This internal method is the primitive that get_range_limit wraps; the public wrapper converts the exception into a default value when one is supplied.
Source
Thrown at qlib/backtest/decision.py:389
trade_calendar : TradeCalendarManager
The calendar of the **inner strategy**!!!!!
Returns
-------
BaseTradeDecision:
New update, use new decision. If no updates, return None (use previous decision (or unavailable))
"""
# purpose 1)
self.total_step = trade_calendar.get_trade_len()
# purpose 2)
return self.strategy.update_trade_decision(self, trade_calendar)
def _get_range_limit(self, **kwargs: Any) -> Tuple[int, int]:
if self.trade_range is not None:
return self.trade_range(trade_calendar=cast(TradeCalendarManager, kwargs.get("inner_calendar")))
else:
raise NotImplementedError("The decision didn't provide an index range")
def get_range_limit(self, **kwargs: Any) -> Tuple[int, int]:
"""
return the expected step range for limiting the decision execution time
Both left and right are **closed**
if no available trade_range, `default_value` will be returned
It is only used in `NestedExecutor`
- The outmost strategy will not follow any range limit (but it may give range_limit)
- The inner most strategy's range_limit will be useless due to atomic executors don't have such
features.
**NOTE**:
1) This function must be called after `self.update` in following cases(ensured by NestedExecutor):
- user relies on the auto-clip feature of `self.update`
2) This function will be called after _init_sub_trading in NestedExecutor.View on GitHub (pinned to 79633dd950)
Solutions
- Call the public decision.get_range_limit(default_value=None) instead, which returns the default instead of raising
- Construct the decision with a trade_range, e.g. TradeRangeByTime('9:30', '14:30'), so a range can be resolved
- Check decision.trade_range is not None before querying the range limit
Example fix
// before
idx = decision._get_range_limit(inner_calendar=cal) # raises if trade_range is None
// after
idx = decision.get_range_limit(default_value=None, inner_calendar=cal)
if idx is None:
idx = 0, total_steps - 1 Defensive patterns
Strategy: validation
Validate before calling
def safe_range_limit(decision, **kwargs):
if decision.trade_range is None:
return kwargs.get('default_value')
return decision._get_range_limit(**kwargs) Type guard
def has_trade_range(decision) -> bool:
return getattr(decision, 'trade_range', None) is not None Try / catch
try:
idx = decision._get_range_limit(inner_calendar=cal)
except NotImplementedError:
idx = (0, decision.total_step - 1) Prevention
- Prefer the public get_range_limit(default_value=None) over the private _get_range_limit
- Check decision.trade_range before probing for range limits
When it happens
Trigger: Calling decision._get_range_limit(**kwargs) on any decision whose trade_range attribute is None (e.g. a BaseTradeDecision or a TradeDecisionWCache built without trade_range).
Common situations: Custom code reaching into the private _get_range_limit instead of the public get_range_limit; strategies generating decisions without a trade_range while an executor or the strategy itself probes for a range limit.
Related errors
- There is no trade_range in this case
- trade_calendar is necessary for getting TradeRangeByTime.
- Please implement the `get_data` method
- InfPosition doesn't support calculating value
- InfPosition doesn't support stock list position
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/b4e496e8ea4629d7.
Report an issue: GitHub.