microsoft/qlib · error · ValueError

trade_info is necessary in atomic executor

Error message

trade_info is necessary in atomic executor

What it means

DatasetCache.update (qlib/data/cache.py:464) is the abstract method that extends dataset cache files to the latest calendar, returning 0 (updated) / 1 (no update needed) / 2 (failure). The base class has no implementation, so calling it raises NotImplementedError (the message text about 'expression cache' is a copy-paste from ExpressionCache but the intent is dataset cache refresh).

Source

Thrown at qlib/backtest/account.py:381

            - else, aggregate indicators with inner indicators
        outer_trade_decision: BaseTradeDecision
            external trade decision
        trade_info : List[(Order, float, float, float)], optional
            trading information, by default None
            - necessary if atomic is True
            - list of tuple(order, trade_val, trade_cost, trade_price)
        inner_order_indicators : Indicator, optional
            indicators of inner executor, by default None
            - necessary if atomic is False
            - used to aggregate outer indicators
        decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]] = None,
            The decision list of the inner level: List[Tuple[<decision>, <start_time>, <end_time>]]
            The inner level
        indicator_config : dict, optional
            config of calculating indicators, by default {}
        """
        if atomic is True and trade_info is None:
            raise ValueError("trade_info is necessary in atomic executor")
        elif atomic is False and inner_order_indicators is None:
            raise ValueError("inner_order_indicators is necessary in un-atomic executor")

        # update current position and hold bar count in each bar end
        self.update_current_position(trade_start_time, trade_end_time, trade_exchange)

        if self.is_port_metr_enabled():
            # portfolio_metrics is portfolio related analysis
            self.update_portfolio_metrics(trade_start_time, trade_end_time)
            self.update_hist_positions(trade_start_time)

        # update indicator in each bar end
        self.update_indicator(
            trade_start_time=trade_start_time,
            trade_exchange=trade_exchange,
            atomic=atomic,
            outer_trade_decision=outer_trade_decision,
            trade_info=trade_info,

View on GitHub (pinned to 79633dd950)

Solutions

  1. Use DiskDatasetCache.update(cache_uri, freq), which is implemented for the disk format
  2. Override update(self, cache_uri: Union[str, Path], freq: str = 'day') -> int in your subclass
  3. If incremental update is unsupported by your backend, delete and regenerate the dataset cache instead

Example fix

# before
DatasetCache(provider).update(cache_uri, "day")  # NotImplementedError

# after
from qlib.data.cache import DiskDatasetCache
DiskDatasetCache.update(cache_uri, "day")  # classmethod-friendly in shipped impl
Defensive patterns

Strategy: try-catch

Validate before calling

from qlib.data.cache import DatasetCache

assert MyDSCache.update is not DatasetCache.update, "update() not implemented"

Try / catch

try:
    status = cache.update(cache_uri, freq)
except NotImplementedError:
    shutil.rmtree(cache_dir, ignore_errors=True)
    regenerate_dataset_cache(insts, fields, freq)

Prevention

When it happens

Trigger: Invoking dataset-cache refresh (directly or via scripts that call DatasetCache.update after new trading data is dumped) on the base DatasetCache or a subclass that does not override update.

Common situations: Daily incremental pipeline: dump new bar data, then refresh dataset caches; custom cache backend where only read paths were implemented.

Related errors


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