nautechsystems/nautilus_trader · error
missing persistence type for order update
Error message
missing persistence type for order update {} What it means
When converting a Betfair streaming order update into a Nautilus order-status report, the parser maps the order's persistence type (LIMIT order persistence: LAPSE vs PERSIST) to a TimeInForce. If the UnmatchedOrder carries no persistence value, the mapping has nothing to translate and the parser bails with the order id. The adapter cannot represent the update faithfully without it.
Solutions
- Re-establish a clean stream snapshot (resubscribe to order changes) so the full order image with persistence is received.
- Check the order's type in the update: only LIMIT orders should reach this branch; ensure SP/IOC-style orders are handled by their own mappings.
- If the exchange legitimately omits persistence, default it explicitly (e.g. treat as LAPSE/Gtc) instead of failing, or update the adapter mapping for that order type.
- Verify the adapter's parse_stream_time_in_force covers all StreamingOrderType variants your account can place.
Example fix
// before
None => anyhow::bail!("missing persistence type for order update {}", uo.id),
// after
None => {
log::warn!("missing persistence type for order update {}, defaulting to Gtc", uo.id);
Ok(TimeInForce::Gtc)
} Defensive patterns
Strategy: fallback
Validate before calling
if uo.order_type == Limit && uo.persistence_type.is_none() {
log::warn!("order {} update missing persistence; snapshot may be stale", uo.id);
} Type guard
fn has_persistence(uo: &UnmatchedOrder) -> bool {
matches!(uo.order_type, StreamingOrderType::Limit) && uo.persistence_type.is_some()
} Try / catch
match parse_order_status_report(&uo) {
Err(e) if e.to_string().contains("missing persistence type") => {
log::warn!("resubscribing to get a full order image");
resubscribe_orders();
}
other => other,
} Prevention
- Reconnect with a fresh snapshot rather than resuming a conflated stream after gaps.
- Avoid long conflation windows on order streams if you rely on full order attributes.
- Track sequence/sequenceLag so you know when updates arrive without their base image.
When it happens
Trigger: Receiving an orderChangeMessage / unmatched order (type Limit) whose persistence field is absent — e.g. via an out-of-order change message (conflate/heartbeat loss of the initial image) or an exchange payload lacking the field for this order type.
Common situations: Connecting to the Betfair stream with conflation enabled so the initial full order image is missed; Betfair changing/omitting persistence on certain order types (e.g. SP or market-on-close orders reaching an unexpected branch); parsing order updates right after reconnect without a resubscribe snapshot.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- failed to resolve positive quantity for stream order update
- heartbeat_secs must be positive when set
- Order missing order_qty and cannot reconstruct
- Betfair execution startup teardown failed
- C string contains invalid JSON
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/85943741dd7f01e0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/betfair/src/stream/parse.rs:890
if let Some(lsrc) = uo.lsrc {
report.cancel_reason = Some(lsrc.to_string());
}
Ok(report)
}
fn parse_stream_time_in_force(uo: &UnmatchedOrder) -> anyhow::Result<TimeInForce> {
match uo.pt {
Some(persistence_type) => Ok(TimeInForce::from(persistence_type)),
None if matches!(
uo.ot,
crate::common::enums::StreamingOrderType::LimitOnClose
| crate::common::enums::StreamingOrderType::MarketOnClose
) =>
{
Ok(TimeInForce::AtTheClose)
}
None => anyhow::bail!("missing persistence type for order update {}", uo.id),
}
}
fn stream_order_quantity(uo: &UnmatchedOrder) -> Decimal {
if uo.s > Decimal::ZERO {
return uo.s;
}
let lifecycle_qty = uo.sm.unwrap_or(Decimal::ZERO)
+ uo.sr.unwrap_or(Decimal::ZERO)
+ uo.sc.unwrap_or(Decimal::ZERO)
+ uo.sl.unwrap_or(Decimal::ZERO)
+ uo.sv.unwrap_or(Decimal::ZERO);
if lifecycle_qty > Decimal::ZERO {
return lifecycle_qty;
}
View on GitHub (pinned to 18893faf8b)