microsoft/qlib · error · ValueError

`factor` and (`stock_id`, `start_time`, `end_time`) can't bo

Error message

`factor` and (`stock_id`, `start_time`, `end_time`) can't both be None

What it means

Exchange._get_factor_or_raise_error resolves the adjustment factor either directly from the `factor` argument or by looking it up via (stock_id, start_time, end_time). If factor is None and any of the three lookup keys is missing, there is no way to obtain a factor, so ValueError is raised. It backs get_amount_of_trade_unit and round_amount_by_trade_unit when rounding order amounts to board lots.

Source

Thrown at qlib/backtest/exchange.py:724

                        direction=direction,
                    )
                    * amount_dict[stock_id]
                )
        return value

    def _get_factor_or_raise_error(
        self,
        factor: float | None = None,
        stock_id: str | None = None,
        start_time: pd.Timestamp = None,
        end_time: pd.Timestamp = None,
    ) -> float:
        """Please refer to the docs of get_amount_of_trade_unit"""
        if factor is None:
            if stock_id is not None and start_time is not None and end_time is not None:
                factor = self.get_factor(stock_id=stock_id, start_time=start_time, end_time=end_time)
            else:
                raise ValueError(f"`factor` and (`stock_id`, `start_time`, `end_time`) can't both be None")
        assert factor is not None
        return factor

    def get_amount_of_trade_unit(
        self,
        factor: float | None = None,
        stock_id: str | None = None,
        start_time: pd.Timestamp = None,
        end_time: pd.Timestamp = None,
    ) -> Optional[float]:
        """
        get the trade unit of amount based on **factor**
        the factor can be given directly or calculated in given time range and stock id.
        `factor` has higher priority than `stock_id`, `start_time` and `end_time`
        Parameters
        ----------
        factor : float
            the adjusted factor

View on GitHub (pinned to 79633dd950)

Solutions

  1. Pass a complete lookup triple: exch.get_amount_of_trade_unit(stock_id=sid, start_time=t0, end_time=t1)
  2. Or supply the factor directly: exch.get_amount_of_trade_unit(factor=1.0) (1.0 means unadjusted prices)
  3. When calling from order-processing code, forward order.stock_id/order.start_time/order.end_time together

Example fix

# before
unit_amt = exch.get_amount_of_trade_unit(stock_id=sid, start_time=t0)  # end_time missing
# after
unit_amt = exch.get_amount_of_trade_unit(stock_id=sid, start_time=t0, end_time=t1)
Defensive patterns

Strategy: validation

Validate before calling

if factor is None:
    assert stock_id is not None and start_time is not None and end_time is not None, \
        'provide factor or all of (stock_id, start_time, end_time)'
unit = exch.get_amount_of_trade_unit(factor=factor, stock_id=stock_id, start_time=start_time, end_time=end_time)

Type guard

def factor_args_complete(factor, stock_id, start_time, end_time) -> bool:
    return factor is not None or all(x is not None for x in (stock_id, start_time, end_time))

Prevention

When it happens

Trigger: Calling get_amount_of_trade_unit / round_amount_by_trade_unit with factor=None and only a partial key set (e.g. stock_id without start_time/end_time).

Common situations: Custom strategies calling the trade-unit API with just a stock id; optional-argument chains where timestamps default to None and are forwarded silently.

Related errors


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