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
- Ensure settlement plans are applied exactly once per expiry — guard with an applied/expiry-set check.
- If replaying, clear previously registered settlement orders from the cache or resume from a post-settlement snapshot.
- Inspect the wrapped inner error ({e}) to see the precise cache rejection (duplicate ID vs missing instrument vs position mismatch) and fix that cause.
- 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
- Apply each settlement plan exactly once — persist applied expiry markers.
- On restart, resume from a post-settlement snapshot rather than replaying settlement events.
- Read the wrapped inner cache error to distinguish duplicate IDs from missing instruments.
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
- Order {client_order_id} not found in cache.
- Cannot cache futures spread: missing option price for {call_
- Cannot cache futures spread: missing option price for {put_i
- cannot claim venue order ID {} for settlement order {client_
- option_summary_family_subs mutex poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/657e4895752b409e.
Report an issue: GitHub.