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 False

View on GitHub (pinned to 79633dd950)

Solutions

  1. Call with raise_error=False to fall back to the full-day trade calendar range
  2. Attach a trade_range to the decision, e.g. BaseTradeDecision(..., trade_range=TradeRangeByTime('9:30', '14:30'))
  3. 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

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


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/c62b7ac0bb64be33. Report an issue: GitHub.