nautechsystems/nautilus_trader · error · anyhow::Error

Order expiration (until) not set

Error message

Order expiration (until) not set

What it means

Every dYdX v4 order must carry a good-til oneof (block height or timestamp expiry). OrderBuilder::build throws this when the until field (OrderGoodUntil) was never set, so the required GoodTilOneof cannot be constructed.

Source

Thrown at crates/adapters/dydx/src/grpc/order.rs:446

        // Build order ID
        let order_id = Some(OrderId {
            subaccount_id: Some(SubaccountId {
                owner: self.subaccount_owner.clone(),
                number: self.subaccount_number,
            }),
            client_id: self.client_id,
            order_flags: match self.flags {
                OrderFlags::ShortTerm => 0,
                OrderFlags::LongTerm => 64,
                OrderFlags::Conditional => 32,
            },
            clob_pair_id: self.market_params.clob_pair_id,
        });

        // Set good til oneof - required for all orders
        let until = self
            .until
            .ok_or_else(|| anyhow::anyhow!("Order expiration (until) not set"))?;

        let good_til_oneof = match until {
            OrderGoodUntil::Block(height) => Some(GoodTilOneof::GoodTilBlock(height)),
            OrderGoodUntil::Time(time) => {
                Some(GoodTilOneof::GoodTilBlockTime(time.as_second().try_into()?))
            }
        };

        // Quantize price: use explicit price if set, otherwise compute worst-case for market orders
        let subticks = if let Some(price) = self.price {
            self.market_params.quantize_price(price)?
        } else if matches!(
            self.order_type,
            Some(OrderType::Market | OrderType::StopMarket | OrderType::MarketIfTouched)
        ) {
            let side = self
                .side
                .ok_or_else(|| anyhow::anyhow!("Order side not set"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Always call .until(OrderGoodUntil::Block(h)) or .until(OrderGoodUntil::Time(t)) before build()
  2. Derive expiry from the current chain height plus a buffer and set it unconditionally
  3. Centralize builder construction in a helper that enforces all required fields

Example fix

// before
let builder = OrderBuilder::new(params).side(side).size(size);
let order = builder.build()?;
// after
let expiry = OrderGoodUntil::Time(current_time + Duration::minutes(5));
let builder = OrderBuilder::new(params).side(side).size(size).until(expiry);
let order = builder.build()?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(until.is_some(), "good-til (until) must be set before build()");

Prevention

When it happens

Trigger: Calling build() (e.g. via build_conditional_order) without calling the until / good-til setter, or code that sets time-in-force but not the concrete expiry.

Common situations: Building a conditional order without computing an expiry block/time; refactor where expiry was set on one order path but not another; a helper that wraps the builder and omits until.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/7d63876e34bb844a. Report an issue: GitHub.