nautechsystems/nautilus_trader · error

OTO parent not found

Error message

OTO parent not found

What it means

During order validation the matching engine found that an order has a parent_order_id, but the parent order either is missing from the cache or is not an OTO (One-Triggers-Other) contingent order. Because contingent-order bookkeeping depends on a valid OTO parent, the engine panics — the child order's contingency chain is broken. This is a defensive invariant protecting OTO lifecycle handling.

Source

Thrown at crates/execution/src/matching_engine/mod.rs:2840

                if let Some(expiration_ns) = self.instrument.expiration_ns()
                    && self.clock.borrow().timestamp_ns() >= expiration_ns
                {
                    break 'validate Some(
                        format!(
                            "Contract {} has expired, expiration {expiration_ns}",
                            self.instrument.id(),
                        )
                        .into(),
                    );
                }
            }

            // Contingent orders checks
            if self.config.support_contingent_orders {
                if let Some(parent_order_id) = order.parent_order_id() {
                    let parent_order = match self.order_snapshot(parent_order_id) {
                        Some(o) if o.contingency_type() == Some(ContingencyType::Oto) => o,
                        _ => panic!("OTO parent not found"),
                    };
                    let parent_filled_qty = parent_order.filled_qty();

                    if parent_order.status() == OrderStatus::Rejected && order.is_open() {
                        break 'validate Some(
                            format!("Rejected OTO order from {parent_order_id}").into(),
                        );
                    } else if parent_filled_qty.is_zero()
                        || (self.config.oto_full_trigger
                            && parent_filled_qty < parent_order.quantity())
                    {
                        log::info!(
                            "Pending OTO order {} triggers from {parent_order_id}",
                            order.client_order_id(),
                        );
                        return;
                    }
                }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the OTO parent order exists in the matching engine cache before submitting the child (submit parent first).
  2. Verify the parent's contingency_type is actually Oto; fix order construction so parent_order_id is only set on genuine OTO children.
  3. Enable cache persistence or rebuild contingent chains on startup so parents survive restarts.
  4. Reject/fix inbound orders whose parent_order_id cannot be resolved instead of forwarding them to the engine.

Example fix

// before
child.parent_order_id = Some(random_id);
// after
assert_eq!(parent.contingency_type(), Some(ContingencyType::Oto));
child.parent_order_id = Some(parent.client_order_id()); // genuine OTO parent, cached first
Defensive patterns

Strategy: validation

Validate before calling

// before submitting an OTO child
let parent = cache.order(&parent_id);
let valid = matches!(parent, Some(o) if o.contingency_type() == Some(ContingencyType::Oto));
if !valid { return Err(anyhow!("OTO parent {parent_id} missing or not OTO")); }

Type guard

fn is_cached_oto_parent(cache: &Cache, id: &ClientOrderId) -> bool {
    matches!(cache.order(id), Some(o) if o.contingency_type() == Some(ContingencyType::Oto))
}

Prevention

When it happens

Trigger: Submitting or validating an order carrying parent_order_id where order_snapshot(parent_order_id) returns None or a parent whose contingency_type != ContingencyType::Oto, with config.support_contingent_orders enabled.

Common situations: Resubmitting/repairing an OTO child order after the parent was purged from the cache; manually constructing an order with parent_order_id set to an unrelated order; mixing contingently-linked order state across node restarts with a non-persistent cache.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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