nautechsystems/nautilus_trader · error
Cannot persist position with no events: {}
Error message
Cannot persist position with no events: {} What it means
When persisting a Position to the Redis cache, the adapter serializes the position's last event. This error means the Position has no event at all (`position.last_event()` returned None), so there is nothing to serialize. The library refuses to write a stateless position because a persisted position without events could not be reconstructed on load.
Source
Thrown at crates/infrastructure/src/redis/cache.rs:1264
self.send_command(DatabaseOperation::Update, key, Some(vec![payload]))
}
fn serialize_account_event(&self, account: &AccountAny) -> anyhow::Result<Bytes> {
let event: AccountState = account.last_event().ok_or_else(|| {
anyhow::anyhow!("Cannot persist account with no events: {}", account.id())
})?;
let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
Ok(Bytes::from(payload))
}
fn serialize_order_event(&self, order_event: &OrderEventAny) -> anyhow::Result<Bytes> {
let payload = DatabaseQueries::serialize_payload(self.encoding(), order_event)?;
Ok(Bytes::from(payload))
}
fn serialize_position_event(&self, position: &Position) -> anyhow::Result<Bytes> {
let event: OrderFilled = position.last_event().ok_or_else(|| {
anyhow::anyhow!("Cannot persist position with no events: {}", position.id)
})?;
let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
Ok(Bytes::from(payload))
}
fn load_state(&self, key: String) -> anyhow::Result<AHashMap<String, Bytes>> {
let mut con = self.database.con.clone();
let trader_key = self.database.trader_key.clone();
let encoding = self.encoding();
let (tx, rx) = mpsc::channel();
get_runtime().spawn(async move {
let result = async {
let full_key = format!("{trader_key}{REDIS_DELIMITER}{key}");
let value: Option<Bytes> = con.get(&full_key).await?;
let Some(value) = value else {
return Ok(AHashMap::new());
};View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure the Position has been updated via `position.update(event)` (which sets `last_event`) before calling any cache persist/update API
- Check that order fill events are actually flowing to the position (event generation ordering in the execution engine or custom adapter)
- If migrating state, replay the original position events from the journal before persisting
- Log the position id to find which position is eventless and inspect how it was created
Example fix
// before let position = Position::new(&event); // if created without events and persisted immediately cache.update_position(&position)?; // after let mut position = Position::new(&event); position.update(&fill_event); // ensures last_event is Some cache.update_position(&position)?;
Defensive patterns
Strategy: validation
Validate before calling
// Rust
if position.last_event().is_none() {
return Err(anyhow::anyhow!("refusing to persist position {} with no events", position.id));
}
cache.update_position(&position)?; Type guard
fn has_events(position: &Position) -> bool {
position.last_event().is_some()
} Prevention
- Always drive positions through the execution engine so events are recorded before persistence
- Assert `last_event().is_some()` in debug builds before persisting
- When importing/migrating positions, replay their events first
- Log position id at creation sites to trace eventless positions quickly
When it happens
Trigger: Calling `update_position(position)` (via the cache `update_actor`/position persist path) with a Position instance that was constructed but never received an event (e.g. a position initialized from a fill-less event or a manually built Position passed to `Position::update` was never invoked).
Common situations: Restoring or migrating positions from external data where events were not replayed; custom backtest/live code that constructs a Position directly and pushes it into the cache before processing any order-filled events; bugs in custom position handling in a strategy or custom adapter.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Duplicate fill event for position {position_id}: {trade_id}
- Cannot update position {}: not found in cache
- Cannot update position {position_id}: not found in cache
- Failed to send to channel: {e}
- Unsupported operation: `insert` for collection '{collection}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/6b77ecf205f3dd1d.
Report an issue: GitHub.