microsoft/qlib · error · ValueError

trade_account and position can only choose one

Error message

trade_account and position can only choose one

What it means

Exchange.deal_order accepts the post-trade position state through exactly one channel: either a trade_account (whose current_position is used) or a bare position object. Supplying both is ambiguous, so it raises ValueError immediately after the order check. The parameters exist to support both account-based and standalone-position workflows, never both at once.

Source

Thrown at qlib/backtest/exchange.py:445

    ) -> Tuple[float, float, float]:
        """
        Deal order when the actual transaction
        the results section in `Order` will be changed.
        :param order:  Deal the order.
        :param trade_account: Trade account to be updated after dealing the order.
        :param position: position to be updated after dealing the order.
        :param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
        :return: trade_val, trade_cost, trade_price
        """
        # check order first.
        if not self.check_order(order):
            order.deal_amount = 0.0
            # using np.nan instead of None to make it more convenient to show the value in format string
            self.logger.debug(f"Order failed due to trading limitation: {order}")
            return 0.0, 0.0, np.nan

        if trade_account is not None and position is not None:
            raise ValueError("trade_account and position can only choose one")

        # NOTE: order will be changed in this function
        trade_price, trade_val, trade_cost = self._calc_trade_info_by_order(
            order,
            trade_account.current_position if trade_account else position,
            dealt_order_amount,
        )
        if trade_val > 1e-5:
            # If the order can only be deal 0 value. Nothing to be updated
            # Otherwise, it will result in
            # 1) some stock with 0 value in the position
            # 2) `trade_unit` of trade_cost will be lost in user account
            if trade_account:
                trade_account.update_order(order=order, trade_val=trade_val, cost=trade_cost, trade_price=trade_price)
            elif position:
                position.update_order(order=order, trade_val=trade_val, cost=trade_cost, trade_price=trade_price)

        return trade_val, trade_cost, trade_price

View on GitHub (pinned to 79633dd950)

Solutions

  1. Pass only trade_account when trading within an account: exch.deal_order(order, trade_account=account)
  2. Or pass only position for standalone simulation: exch.deal_order(order, position=pos)
  3. If unsure, prefer trade_account in normal backtests; use position only for lightweight custom simulations

Example fix

# before
val, cost, price = exch.deal_order(order, trade_account=account, position=account.current_position)
# after
val, cost, price = exch.deal_order(order, trade_account=account)
Defensive patterns

Strategy: validation

Validate before calling

assert (trade_account is None) != (position is None), 'pass exactly one of trade_account/position'
exch.deal_order(order, trade_account=trade_account, position=position)

Type guard

def deal_order_args_ok(trade_account, position) -> bool:
    return (trade_account is None) != (position is None)

Prevention

When it happens

Trigger: exch.deal_order(order, trade_account=account, position=pos); typical when a caller has an account and also passes the account's position defensively.

Common situations: Wrapper code or tutorials that pass every optional argument; refactors where position was kept for compatibility while trade_account was introduced.

Related errors


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