nautechsystems/nautilus_trader · error · anyhow::Error

missing persistence type for order update {}

Error message

missing persistence type for order update {}

What it means

Thrown while converting a Betfair Stream API unmatched-order update (`oc` entry) into a NautilusTrader TimeInForce. Betfair carries order persistence in the `pt` (persistenceType) field; a missing `pt` is only tolerated for 'Limit on close' and 'Market on close' order types, which map to TimeInForce::AtTheClose. For any other order type the adapter cannot decide the TIF and bails, failing the parse of that order update.

Source

Thrown at crates/adapters/betfair/src/stream/parse.rs:903

    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 a4b06ed870)

Solutions

  1. Reconnect or re-request the stream - a missing pt on an otherwise normal limit order usually indicates a malformed/lost field in that message
  2. Dump the raw OCM message and verify `ot`/`pt`; if `pt` is genuinely absent for a plain LIMIT order, capture the payload and report it to the adapter maintainers as an upstream data issue
  3. If Betfair now legitimately omits pt for a new order type, extend parse_stream_time_in_force in crates/adapters/betfair/src/stream/parse.rs with an explicit mapping for that type

Example fix

// before
let tif = parse_stream_time_in_force(&uo)?;

// after: skip malformed per-order updates instead of failing the stream
let tif = match parse_stream_time_in_force(&uo) {
    Ok(tif) => tif,
    Err(e) => {
        log::warn!("skipping unmatched order {}: {e}", uo.id);
        continue;
    }
};
Defensive patterns

Strategy: try-catch

Validate before calling

fn has_resolvable_tif(uo: &UnmatchedOrder) -> bool {
    uo.pt.is_some()
        || matches!(
            uo.ot,
            StreamingOrderType::LimitOnClose | StreamingOrderType::MarketOnClose
        )
}
// before parsing: if !has_resolvable_tif(&uo) { skip or alert }

Type guard

fn is_betfair_order_parseable(uo: &UnmatchedOrder) -> bool {
    uo.pt.is_some() || matches!(uo.ot, StreamingOrderType::LimitOnClose | StreamingOrderType::MarketOnClose)
}

Try / catch

match parse_stream_time_in_force(&uo) {
    Ok(tif) => { /* apply update */ }
    Err(e) => log::warn!("skipping order {} with unparseable TIF: {e}", uo.id),
}

Prevention

When it happens

Trigger: Receiving an OCM stream message whose unmatched order has `pt` null/absent while `ot` is a type other than LimitOnClose or MarketOnClose (e.g. a plain LIMIT order without persistenceType).

Common situations: Malformed or stale Betfair stream messages; replaying captured stream data from an older API-NG version; test fixtures that omit persistenceType; Betfair schema changes on new order types.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/85943741dd7f01e0. Report an issue: GitHub.