microsoft/qlib · error · ValueError
atomic executor doesn't support specify `range_limit`
Error message
atomic executor doesn't support specify `range_limit`
What it means
BaseExecutor.execute_folder/check: an atomic executor (one that is not a NestedExecutor subclass) executes decisions within a single step and cannot honor a range_limit spanning multiple steps. If a decision passed to an atomic executor reports a non-None get_range_limit(default_value=None), the mismatch is a programming error and ValueError is raised.
Source
Thrown at qlib/backtest/executor.py:268
execute_result : List[object]
the executed result for trade decision.
** NOTE!!!! **:
1) This is necessary, The return value of generator will be used in NestedExecutor
2) Please note the executed results are not merged.
Yields
-------
object
trade decision
"""
if self.track_data:
yield trade_decision
atomic = not issubclass(self.__class__, NestedExecutor) # issubclass(A, A) is True
if atomic and trade_decision.get_range_limit(default_value=None) is not None:
raise ValueError("atomic executor doesn't support specify `range_limit`")
if self._settle_type != BasePosition.ST_NO:
self.trade_account.current_position.settle_start(self._settle_type)
obj = self._collect_data(trade_decision=trade_decision, level=level)
if isinstance(obj, GeneratorType):
yield_res = yield from obj
assert isinstance(yield_res, tuple) and len(yield_res) == 2
res, kwargs = yield_res
else:
# Some concrete executor don't have inner decisions
res, kwargs = obj
trade_start_time, trade_end_time = self.trade_calendar.get_step_time()
# Account will not be changed in this function
self.trade_account.update_bar_end(
trade_start_time,View on GitHub (pinned to 79633dd950)
Solutions
- Ensure decisions with a trade_range are executed by NestedExecutor levels, with only range-limit-free decisions reaching atomic executors
- If your custom executor supports multi-step execution, inherit from NestedExecutor instead of BaseExecutor
- Strip trade_range (set decision.trade_range = None) before handing the decision to an atomic executor if the whole-step semantics are acceptable
Example fix
# before # SimulatorExecutor (atomic) receives decision with trade_range -> ValueError res = sim_executor.run(execution_strategy, level=0) # after # route through a NestedExecutor for the outer level res = nested_executor.run(execution_strategy, level=0)
Defensive patterns
Strategy: validation
Validate before calling
from qlib.backtest.executor import NestedExecutor
atomic = not isinstance(executor, NestedExecutor)
if atomic and decision.get_range_limit(default_value=None) is not None:
decision.trade_range = None # or route the decision to a nested executor instead
executor.run(...) # proceed Type guard
from qlib.backtest.executor import NestedExecutor
def is_atomic(executor) -> bool:
return not isinstance(executor, NestedExecutor) Prevention
- Keep range-limited decisions on NestedExecutor levels of the stack
- Subclass NestedExecutor, not BaseExecutor, for multi-step custom executors
When it happens
Trigger: Registering SimulatorExecutor (atomic) as the innermost executor while the strategy's decisions carry a trade_range/range_limit; configuring an executor stack where a decision with a range limit reaches a non-nested executor; subclassing BaseExecutor without inheriting NestedExecutor.
Common situations: Custom executor classes deriving from BaseExecutor directly and fed decisions from NestedExecutor-style strategies; mismatched executor/strategy configs in backtest yaml.
Related errors
- This type of input is not supported
- type is invalid, {}
- not found executor
- Invalid mount path
- Unknown mount error: {error_output.strip()}
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/5cd72d29b1796728.
Report an issue: GitHub.