microsoft/qlib · error · NotImplementedError
direction not supported, `Order.SELL` for sell, `Order.BUY`
Error message
direction not supported, `Order.SELL` for sell, `Order.BUY` for buy
What it means
DatasetURICache/IndexManager.sync_to_disk (qlib/data/cache.py:815) persists the in-memory cache index to an HDF5 .index file; it refuses when self._data is None, i.e. no index was ever loaded (sync_from_disk was not called / no data was set). This prevents writing an empty/garbage index file that would masquerade as a valid cache index.
Source
Thrown at qlib/backtest/decision.py:85
# What the value should be about in all kinds of cases
# - not tradable: the deal_amount == 0 , factor is None
# - the stock is suspended and the entire order fails. No cost for this order
# - dealt or partially dealt: deal_amount >= 0 and factor is not None
deal_amount: float = 0.0 # `deal_amount` is a non-negative value
factor: Optional[float] = None
# TODO:
# a status field to indicate the dealing result of the order
# FIXME:
# for compatible now.
# Please remove them in the future
SELL: ClassVar[OrderDir] = OrderDir.SELL
BUY: ClassVar[OrderDir] = OrderDir.BUY
def __post_init__(self) -> None:
if self.direction not in {Order.SELL, Order.BUY}:
raise NotImplementedError("direction not supported, `Order.SELL` for sell, `Order.BUY` for buy")
self.deal_amount = 0.0
self.factor = None
@property
def amount_delta(self) -> float:
"""
return the delta of amount.
- Positive value indicates buying `amount` of share
- Negative value indicates selling `amount` of share
"""
return self.amount * self.sign
@property
def deal_amount_delta(self) -> float:
"""
return the delta of deal_amount.
- Positive value indicates buying `deal_amount` of share
- Negative value indicates selling `deal_amount` of shareView on GitHub (pinned to 79633dd950)
Solutions
- Call index_manager.get_index(...) or sync_from_disk() first so _data is populated, then sync_to_disk()
- Or use IndexManager.update(data, sync=True) which sets _data then syncs in one step
- If the .index file does not exist yet, generate the cache (DatasetCache.update / gen_dataset_cache) through public APIs instead of hand-driving IndexManager
Example fix
# before im = IndexManager(cache_path) im.sync_to_disk() # ValueError: No data to sync to disk. # after im = IndexManager(cache_path) idx = im.get_index() # triggers sync_from_disk, populates _data im.sync_to_disk()
Defensive patterns
Strategy: validation
Validate before calling
im = IndexManager(cache_path)
if im._data is None:
im.sync_from_disk() # or im.get_index() — populates _data first
im.sync_to_disk() Type guard
def index_ready(im) -> bool:
return im._data is not None Try / catch
try:
im.sync_to_disk()
except ValueError as e:
if "No data to sync" in str(e):
im.sync_from_disk()
im.sync_to_disk()
else:
raise Prevention
- Drive cache index updates through public DatasetCache APIs, not IndexManager directly
- Always read (get_index/sync_from_disk) or update() before sync_to_disk
- Guard custom maintenance scripts with an _data is not None check
When it happens
Trigger: Constructing IndexManager(cache_path) and calling sync_to_disk() before get_index()/sync_from_disk()/update() has populated _data; typically in custom code driving the dataset-URI index, or a code path where sync_from_disk found no '/df' key (empty DataFrame case still sets _data, so the None case means load was skipped entirely).
Common situations: Custom maintenance scripts touching qlib's cache index; edge flows where a fresh cache directory has no .index file yet and the caller tries to write before any read/update.
Related errors
- Invalid mount path
- Unknown mount error: {error_output.strip()}
- I forget why would this case appear. But I think it does not
- Subclass of FeatureStorage must implement `start_index` meth
- Subclass of FeatureStorage must implement `end_index` method
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/41d5ce0179e48e38.
Report an issue: GitHub.