{"record":{"id":"0543d1080f69d237","repo":"microsoft/qlib","slug":"inner-order-indicators-is-necessary-in-un-atomic-e","errorCode":null,"errorMessage":"inner_order_indicators is necessary in un-atomic executor","messagePattern":"inner_order_indicators is necessary in un-atomic executor","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/backtest/account.py","lineNumber":383,"sourceCode":"            external trade decision\n        trade_info : List[(Order, float, float, float)], optional\n            trading information, by default None\n            - necessary if atomic is True\n            - list of tuple(order, trade_val, trade_cost, trade_price)\n        inner_order_indicators : Indicator, optional\n            indicators of inner executor, by default None\n            - necessary if atomic is False\n            - used to aggregate outer indicators\n        decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]] = None,\n            The decision list of the inner level: List[Tuple[<decision>, <start_time>, <end_time>]]\n            The inner level\n        indicator_config : dict, optional\n            config of calculating indicators, by default {}\n        \"\"\"\n        if atomic is True and trade_info is None:\n            raise ValueError(\"trade_info is necessary in atomic executor\")\n        elif atomic is False and inner_order_indicators is None:\n            raise ValueError(\"inner_order_indicators is necessary in un-atomic executor\")\n\n        # update current position and hold bar count in each bar end\n        self.update_current_position(trade_start_time, trade_end_time, trade_exchange)\n\n        if self.is_port_metr_enabled():\n            # portfolio_metrics is portfolio related analysis\n            self.update_portfolio_metrics(trade_start_time, trade_end_time)\n            self.update_hist_positions(trade_start_time)\n\n        # update indicator in each bar end\n        self.update_indicator(\n            trade_start_time=trade_start_time,\n            trade_exchange=trade_exchange,\n            atomic=atomic,\n            outer_trade_decision=outer_trade_decision,\n            trade_info=trade_info,\n            inner_order_indicators=inner_order_indicators,\n            decision_list=decision_list,","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/backtest/account.py#L365-L401","documentation":"DiskDatasetCache._dataset (qlib/data/cache.py:706) raises ValueError when inst_processors is non-empty and disk_cache != 0. The on-disk dataset cache stores a raw (resampled) frame keyed by the dataset hash, so per-instrument processors (e.g. price normalization) cannot be baked into the shared cache file without corrupting it for other configs. The error message gives the two supported escapes: D.features(disk_cache=0) or qlib.init(dataset_cache=None).","triggerScenarios":"Calling D.features(instruments, fields, ..., inst_processors=[...]) while a dataset cache is configured and disk_cache is left at its default (1); or DatasetCache.dataset(...) reaching _dataset with truthy inst_processors and disk_cache != 0.","commonSituations":"Using inst_processors like ProcessInstrument (e.g. CSZScoreNorm-style per-instrument transforms at data-loading time) together with dataset caching enabled; the cache would silently return unprocessed data if this guard were absent — hence the hard error. Related FIXME: resampled cache read back with end_time truncation can also yield incomplete dates.","solutions":["Call D.features(..., disk_cache=0) — bypasses the dataset cache and applies inst_processors on freshly loaded data","Disable dataset caching entirely: qlib.init(dataset_cache=None)","Alternatively drop inst_processors and apply the processing after data retrieval (e.g. in your DataHandlerLP processor chain) so the cache stays valid"],"exampleFix":"# before\nD.features(insts, fields, start, end, inst_processors=[MyProcessor()])  # dataset cache on -> ValueError\n\n# after\nD.features(insts, fields, start, end, disk_cache=0, inst_processors=[MyProcessor()])","handlingStrategy":"validation","validationCode":"# decide disk_cache based on processor usage\ndisk_cache = 0 if inst_processors else 1\nD.features(insts, fields, start, end, disk_cache=disk_cache, inst_processors=inst_processors)","typeGuard":"def needs_disk_cache_bypass(inst_processors) -> bool:\n    return bool(inst_processors)","tryCatchPattern":"try:\n    df = D.features(insts, fields, start, end, inst_processors=procs)\nexcept ValueError as e:\n    if \"does not support inst_processor\" in str(e):\n        df = D.features(insts, fields, start, end, disk_cache=0, inst_processors=procs)\n    else:\n        raise","preventionTips":["Never combine inst_processors with disk_cache=1","Apply instrument-level transforms in DataHandlerLP processors instead of data loading","Set disk_cache=0 project-wide once inst_processors are in use"],"tags":["qlib","dataset-cache","inst-processors","configuration"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}