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

  1. Guard sell generation: only emit SELL when position.check_stock(order.stock_id) is True
  2. If shorting is intended, pre-seed the position with the instrument (_init_stock) before the sell
  3. 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

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


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