microsoft/qlib · error · RuntimeError

The calendar is finished, please reset it if you want to cal

Error message

The calendar is finished, please reset it if you want to call it!

What it means

TradeCalendarManager in qlib/backtest/utils.py tracks trading progress with trade_step and trade_len. finished() returns True once trade_step >= trade_len, and step() refuses to advance past the end of the calendar, raising RuntimeError. This is a state-machine guard: the calendar must be reset before being reused after completion.

Source

Thrown at qlib/backtest/utils.py:89

        self._calendar = _calendar
        _, _, _start_index, _end_index = Cal.locate_index(start_time, end_time, freq=freq, future=True)
        self.start_index = _start_index
        self.end_index = _end_index
        self.trade_len = _end_index - _start_index + 1
        self.trade_step = 0

    def finished(self) -> bool:
        """
        Check if the trading finished
        - Should check before calling strategy.generate_decisions and executor.execute
        - If self.trade_step >= self.self.trade_len, it means the trading is finished
        - If self.trade_step < self.self.trade_len, it means the number of trading step finished is self.trade_step
        """
        return self.trade_step >= self.trade_len

    def step(self) -> None:
        if self.finished():
            raise RuntimeError(f"The calendar is finished, please reset it if you want to call it!")
        self.trade_step += 1

    def get_freq(self) -> str:
        return self.freq

    def get_trade_len(self) -> int:
        """get the total step length"""
        return self.trade_len

    def get_trade_step(self) -> int:
        return self.trade_step

    def get_step_time(self, trade_step: int | None = None, shift: int = 0) -> Tuple[pd.Timestamp, pd.Timestamp]:
        """
        Get the left and right endpoints of the trade_step'th trading interval

        About the endpoints:
            - Qlib uses the closed interval in time-series data selection, which has the same performance as

View on GitHub (pinned to 79633dd950)

Solutions

  1. Always check finished() before calling step(): 'while not cal.finished(): ... cal.step()'.
  2. Reset state between runs — recreate the TradeCalendarManager (or call its reset/setup with fresh start/end times) instead of reusing the spent instance.
  3. Audit custom executor loops for step() called both inside executor.execute and again by the outer loop (double-stepping exhausts the calendar early).

Example fix

# before
for _ in range(n_steps):
    cal.step()  # raises when trade_step reaches trade_len
# after
while not cal.finished():
    do_trade()
    cal.step()
Defensive patterns

Strategy: validation

Validate before calling

if cal.finished():
    raise RuntimeError('calendar exhausted; reset it before the next run')
cal.step()

Try / catch

try:
    cal.step()
except RuntimeError as e:
    if 'calendar is finished' in str(e):
        cal.reset()  # or break the trading loop
    else:
        raise

Prevention

When it happens

Trigger: Calling calendar.step() (directly or via an executor/strategy loop) one more time after the last trading bar — typically an off-by-one in a custom executor loop, or reusing the same TradeCalendarManager instance for a second backtest run without reset.

Common situations: Custom NestedExecutor or strategy code that drives the loop manually and calls step() before re-checking finished(); re-running a backtest in a notebook with shared common_infra state; incorrect trade_len computed from a truncated calendar.

Related errors


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