nautechsystems/nautilus_trader · error

cannot add settlement order {client_order_id}: {e}

Error message

cannot add settlement order {client_order_id}: {e}

What it means

While applying an options settlement plan, the engine registers each settlement leg's order into the cache via add_order. If the cache rejects the order (duplicate client_order_id, missing instrument, inconsistent position_id, etc.), the engine wraps the underlying cache error into this context-rich anyhow error and aborts applying the settlement plan.

Source

Thrown at crates/execution/src/matching_engine/settlement.rs:183

        for leg in &plan.legs {
            self.generate_order_accepted(&leg.order, leg.fill.venue_order_id);
        }

        for leg in plan.legs {
            self.dispatch_order_event(OrderEventAny::Filled(leg.fill));
        }

        Ok(())
    }

    fn option_register_settlement_plan(&self, plan: &OptionSettlementPlan) -> anyhow::Result<()> {
        for leg in &plan.legs {
            let client_order_id = leg.order.client_order_id();
            let mut cache = self.cache.borrow_mut();
            cache
                .add_order(leg.order.clone(), leg.fill.position_id, None, false)
                .map_err(|e| {
                    anyhow::anyhow!("cannot add settlement order {client_order_id}: {e}")
                })?;
            cache
                .add_venue_order_id(&client_order_id, &leg.fill.venue_order_id, false)
                .map_err(|e| {
                    anyhow::anyhow!(
                        "cannot claim venue order ID {} for settlement order {client_order_id}: {e}",
                        leg.fill.venue_order_id
                    )
                })?;
        }
        Ok(())
    }

    fn option_should_exercise(&self, underlying_price: Price) -> bool {
        let strike = match self.instrument.strike_price() {
            Some(p) => p.as_decimal(),
            None => return false,
        };

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure settlement plans are applied exactly once per expiry — guard with an applied/expiry-set check.
  2. If replaying, clear previously registered settlement orders from the cache or resume from a post-settlement snapshot.
  3. Inspect the wrapped inner error ({e}) to see the precise cache rejection (duplicate ID vs missing instrument vs position mismatch) and fix that cause.
  4. Verify all instruments and positions referenced by plan.legs exist in the cache before applying the plan.

Example fix

// before: applying the same settlement plan twice
engine.option_apply_settlement_plan(&plan)?; // ok
engine.option_apply_settlement_plan(&plan)?; // duplicate client_order_id
// after: track applied expiries
if applied_expiries.insert(plan.expiry) {
    engine.option_apply_settlement_plan(&plan)?;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure every settlement leg is registrable before applying the plan
for leg in &plan.legs {
    assert!(!cache.order(&leg.order.client_order_id()).is_some(),
        "settlement order {} already registered", leg.order.client_order_id());
    assert!(cache.instrument(&leg.order.instrument_id()).is_some(),
        "instrument {} missing from cache", leg.order.instrument_id());
}

Type guard

fn plan_applicable(cache: &Cache, plan: &SettlementPlan) -> bool {
    plan.legs.iter().all(|leg| cache.order(&leg.order.client_order_id()).is_none())
}

Try / catch

match engine.option_apply_settlement_plan(&plan) {
    Err(e) if e.to_string().contains("cannot add settlement order") => {
        log::error!("{e:#}; skipping already-applied settlement plan for expiry {}", plan.expiry);
        // treat as applied-once violation; do not retry blindly
    }
    other => other,
}

Prevention

When it happens

Trigger: option_apply_settlement_plan called twice for the same expiry (duplicate client_order_id already in cache); a settlement leg references a position_id that conflicts with existing cache state; the instrument for the leg is not registered in the cache.

Common situations: Replaying settlement plans after a restart without clearing previously registered legs; expired-options handling firing more than once per expiry due to duplicated expiry events; cache snapshots restored mid-settlement.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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