microsoft/qlib · error · NotImplementedError
There is no trade_range in this case
Error message
There is no trade_range in this case
What it means
This method (decision.py ~line 495, the rtype 'full'/'step' range calculator) computes day-relative index bounds for the decision. If trade_range is None and raise_error=True, it raises NotImplementedError to signal that no per-decision execution window exists; with raise_error=False it silently returns the full day range instead.
Source
Thrown at qlib/backtest/decision.py:495
-------
Tuple[int, int]:
the range limit in data calendar
Raises
------
NotImplementedError:
If the following criteria meet
1) the decision can't provide a unified start and end
2) raise_error is True
"""
# potential performance issue
day_start = pd.Timestamp(self.start_time.date())
day_end = epsilon_change(day_start + pd.Timedelta(days=1))
freq = self.strategy.trade_exchange.freq
_, _, day_start_idx, day_end_idx = Cal.locate_index(day_start, day_end, freq=freq)
if self.trade_range is None:
if raise_error:
raise NotImplementedError(f"There is no trade_range in this case")
else:
return 0, day_end_idx - day_start_idx
else:
if rtype == "full":
val_start, val_end = self.trade_range.clip_time_range(day_start, day_end)
elif rtype == "step":
val_start, val_end = self.trade_range.clip_time_range(self.start_time, self.end_time)
else:
raise ValueError(f"This type of input {rtype} is not supported")
_, _, start_idx, end_index = Cal.locate_index(val_start, val_end, freq=freq)
return start_idx - day_start_idx, end_index - day_start_idx
def empty(self) -> bool:
for obj in self.get_decision():
if isinstance(obj, Order):
# Zero amount order will be treated as empty
if obj.amount > 1e-6:
return FalseView on GitHub (pinned to 79633dd950)
Solutions
- Call with raise_error=False to fall back to the full-day trade calendar range
- Attach a trade_range to the decision, e.g. BaseTradeDecision(..., trade_range=TradeRangeByTime('9:30', '14:30'))
- Catch NotImplementedError and fall back to the order's own time range if you truly need per-order semantics
Example fix
// before start_idx, end_idx = decision.get_range_limit(rtype='step', raise_error=True) // after start_idx, end_idx = decision.get_range_limit(rtype='step', raise_error=False)
Defensive patterns
Strategy: fallback
Validate before calling
if decision.trade_range is None:
idx_range = decision.get_range_limit(rtype='full', raise_error=False)
else:
idx_range = decision.get_range_limit(rtype='full', raise_error=True) Type guard
def has_trade_range(decision) -> bool:
return decision.trade_range is not None Try / catch
try:
idx = decision.get_range_limit(rtype='step', raise_error=True)
except NotImplementedError:
idx = decision.get_range_limit(rtype='step', raise_error=False) Prevention
- Default to raise_error=False unless per-order time boxes are mandatory
- Set trade_range on decisions whenever strategies emit time-boxed orders
When it happens
Trigger: decision.get_range_limit(...-style call with raise_error=True on a decision whose trade_range is None; typically invoked by order-level or nested-executor logic that needs the decision's time box strictly.
Common situations: Strategies that create orders with their own deal_time/trade_range but build the enclosing decision without one; upgraded qlib versions where executors pass raise_error=True when resolving order time ranges.
Related errors
- The decision didn't provide an index range
- 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/c62b7ac0bb64be33.
Report an issue: GitHub.