microsoft/qlib · error · ValueError

Unexpected order direction: {direction}

Error message

Unexpected order direction: {direction}

What it means

ValueError from `calculate_trade_value` in qlib/rl/order_execution/utils.py:40 (the shared util version of the same function also present in simulator_simple.py). `direction` must be `OrderDir.BUY` or `OrderDir.SELL` (or their int values 0/1); any other value falls into the final else and raises.

Source

Thrown at qlib/rl/order_execution/utils.py:40

    return res


def price_advantage(
    exec_price: float_or_ndarray,
    baseline_price: float,
    direction: OrderDir | int,
) -> float_or_ndarray:
    if baseline_price == 0:  # something is wrong with data. Should be nan here
        if isinstance(exec_price, float):
            return 0.0
        else:
            return np.zeros_like(exec_price)
    if direction == OrderDir.BUY:
        res = (1 - exec_price / baseline_price) * 10000
    elif direction == OrderDir.SELL:
        res = (exec_price / baseline_price - 1) * 10000
    else:
        raise ValueError(f"Unexpected order direction: {direction}")
    res_wo_nan: np.ndarray = np.nan_to_num(res, nan=0.0)
    if res_wo_nan.size == 1:
        return res_wo_nan.item()
    else:
        return cast(float_or_ndarray, res_wo_nan)


def get_simulator_executor(executor: BaseExecutor) -> SimulatorExecutor:
    while isinstance(executor, NestedExecutor):
        executor = executor.inner_executor
    assert isinstance(executor, SimulatorExecutor)
    return executor

View on GitHub (pinned to 79633dd950)

Solutions

  1. Convert direction at the boundary: `direction = OrderDir(int(direction))` and catch ValueError to reject bad data immediately.
  2. Audit the data source producing direction values; fix the encoding so only 0/1 ever appear.
  3. Prefer importing and using the enum members directly instead of numeric literals in your code.

Example fix

// before
value = calculate_trade_value(p, b, d)  # d loaded from CSV, sometimes 'buy'
// after
from qlib.rl.utils.enum import OrderDir
d = OrderDir.BUY if str(d).upper().startswith("B") else OrderDir.SELL
value = calculate_trade_value(p, b, d)
Defensive patterns

Strategy: type-guard

Validate before calling

from qlib.utils.enum import OrderDir
valid = {0, 1, OrderDir.BUY, OrderDir.SELL}
assert direction in valid, f"direction {direction!r} not in {valid}"

Type guard

def is_order_direction(d) -> bool:
    return d in (0, 1) or str(d) in ("OrderDir.BUY", "OrderDir.SELL")

Try / catch

try:
    res = calculate_trade_value(exec_price, baseline, direction)
except ValueError as e:
    if "Unexpected order direction" in str(e):
        direction = int(direction)
        if direction not in (0, 1):
            raise
        res = calculate_trade_value(exec_price, baseline, direction)
    else:
        raise

Prevention

When it happens

Trigger: Calling `calculate_trade_value(..., direction=dir_val)` with `dir_val` outside {0, 1, OrderDir.BUY, OrderDir.SELL}: e.g. 2, -1, None, or a string like 'buy' that was never converted to the enum.

Common situations: Reward/metric code iterating over trade records from a dataframe where direction came back as a generic int or object dtype; custom executors that store direction as a string; pickle-roundtrips that lose the IntEnum type and leave a plain int outside the valid range.

Related errors


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