microsoft/qlib · error · KeyError
{} not in current position
Error message
{} not in current position What it means
Position._sell_stock raises KeyError('{stock} not in current position') when a sell order arrives for an instrument the position does not hold. The exchange executed the order, but the position update layer found no entry to decrement. It indicates an inconsistency between what the strategy/exchange thinks is held and the actual position state.
Source
Thrown at qlib/backtest/position.py:355
self.position[stock_id] = {}
self.position[stock_id]["amount"] = amount
self.position[stock_id]["price"] = price
self.position[stock_id]["weight"] = 0 # update the weight in the end of the trade date
def _buy_stock(self, stock_id: str, trade_val: float, cost: float, trade_price: float) -> None:
trade_amount = trade_val / trade_price
if stock_id not in self.position:
self._init_stock(stock_id=stock_id, amount=trade_amount, price=trade_price)
else:
# exist, add amount
self.position[stock_id]["amount"] += trade_amount
self.position["cash"] -= trade_val + cost
def _sell_stock(self, stock_id: str, trade_val: float, cost: float, trade_price: float) -> None:
trade_amount = trade_val / trade_price
if stock_id not in self.position:
raise KeyError("{} not in current position".format(stock_id))
else:
if np.isclose(self.position[stock_id]["amount"], trade_amount):
# Selling all the stocks
# we use np.isclose instead of abs(<the final amount>) <= 1e-5 because `np.isclose` consider both
# relative amount and absolute amount
# Using abs(<the final amount>) <= 1e-5 will result in error when the amount is large
self._del_stock(stock_id)
else:
# decrease the amount of stock
self.position[stock_id]["amount"] -= trade_amount
# check if to delete
if self.position[stock_id]["amount"] < -1e-5:
raise ValueError(
"only have {} {}, require {}".format(
self.position[stock_id]["amount"] + trade_amount,
stock_id,
trade_amount,
),View on GitHub (pinned to 79633dd950)
Solutions
- Guard sell generation: only emit SELL when position.check_stock(order.stock_id) is True
- If shorting is intended, pre-seed the position with the instrument (_init_stock) before the sell
- Regenerate decisions from the current position state each bar instead of caching old signals
Example fix
# before
if signal < 0:
orders.append(Order(stock, Order.SELL, amount))
# after
if signal < 0 and position.check_stock(stock):
sell_amt = min(amount, position.get_stock_amount(stock))
orders.append(Order(stock, Order.SELL, sell_amt)) Defensive patterns
Strategy: validation
Validate before calling
if not position.check_stock(order.stock_id):
raise SkipOrder(f"cannot sell {order.stock_id}: not held") Try / catch
try:
position.update_order(order, trade_val, cost, trade_price)
except KeyError as e:
logger.warning("dropping sell for unheld stock %s", e) Prevention
- Emit sells only after check_stock(stock_id) is True
- Derive sell orders from the current position snapshot each bar
- Never generate short orders against a long-only Position
When it happens
Trigger: update_order called with Order.SELL for a stock_id absent from self.position; e.g. a sell order generated from a stale signal after the stock was fully sold and removed via _del_stock, a short-sale order, or running two exchanges/positions out of sync.
Common situations: Strategy emits sell signals from data computed before a previous step's fills; using a custom order generator that shorts stocks the account never bought; replaying orders against a reset/reloaded position.
Related errors
- only have {} {}, require {}
- do not support order direction {}
- Please implement the `settle_conf` method
- Please implement the `settle_commit` method
- {lack_stock} doesn't have close price in qlib in the latest
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/a5080bb855286c71.
Report an issue: GitHub.