Skip to content

Snapshots

Snapshots let you checkpoint the VM state after a historical replay and restore it instantly on the next start, avoiding expensive full replays.

Why Use Snapshots

In a server deployment, each running script typically processes thousands of historical bars before reaching the live feed. Replaying all historical bars on every restart is expensive. Snapshots solve this by serializing the complete VM state — variables, chart data, strategy state, and configuration — into a binary blob that can be persisted and restored later.

text
Cold start (first run)
  ├─ Build Instance
  ├─ Pull all historical bars from DataProvider  ← slow (thousands of bars)
  ├─ save_state() → Vec<u8>
  └─ Persist bytes to storage

Warm restart (subsequent runs)
  ├─ Load persisted bytes
  ├─ restore_state(&bytes) → Instance    ← fast (no replay)
  └─ run() — automatically resumes from the next bar

Saving State

Call save_state() after the last confirmed bar to produce a serializable byte blob.

Simple: save after full replay

rust
use navi_vm::{Instance, TimeFrame, snapshot::SnapshotError};

let mut instance = Instance::builder(provider, source, TimeFrame::days(1), "NASDAQ:AAPL")
    .build().await?
    .run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;

// Save — the current bar must be confirmed
let snapshot: Vec<u8> = instance.save_state()?;

// Persist to database, disk, object storage, etc.
persist_to_storage(&snapshot);

Advanced: checkpoint mid-stream via RunHandle

Instance::run() returns a RunHandle that implements DerefMut<Target = Instance>, so you can call save_state() directly through the handle without dropping the run. Use current_bar_confirmed() to guard the checkpoint so you only save at confirmed-bar boundaries:

rust
use navi_vm::{Event, Instance, TimeFrame};

let mut handle = instance.run("NASDAQ:AAPL", TimeFrame::days(1));
while let Some(result) = handle.next_event().await {
    match result? {
        Event::BarEnd => {
            if handle.current_bar_confirmed() {
                let snapshot = handle.save_state()?;
                persist_to_storage(&snapshot);
            }
        }
        _ => {}
    }
}

WARNING

The current bar must be confirmed before saving. If you are in the middle of a realtime bar, confirm it first. save_state() returns SnapshotError::UnconfirmedBar if the bar has not been confirmed.

When a new realtime timestamp arrives, Navi first emits the previous realtime bar's auto-confirm events and only then starts the new realtime bar. That auto-confirm BarEnd is a safe checkpoint boundary for save_state(), so current_bar_confirmed() will be true there.

Restoring State

Use Instance::restore_state() to decode the snapshot bytes into a RestoreBuilder, then configure it and call build():

rust
let bytes: Vec<u8> = load_from_storage();
let instance = Instance::restore_state(&bytes)?
    .with_data_provider(provider)   // required: supply a data provider
    .build()?
    .run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;

// run() automatically resumes from the next bar after the snapshot

Candlestick Data

Candlestick data is not included in the snapshot. When run() is called on a restored instance, the VM automatically passes last_bar_time + 1 to the data provider so that only new bars are fetched.

RestoreBuilder Options

MethodDescription
with_data_provider(provider)Inject a DataProvider for request.security() and bar data
build()Finish restoring and return the Instance

Invalidation

Snapshots embed the compiled program and a format version. A snapshot is only valid for the exact binary and script that created it. You must discard the snapshot and do a full cold replay when:

  • Navi is upgraded — the snapshot format may have changed
  • The Navi script source changes — a different compiled program
  • Input parameter values change — inputs affect execution state

A simple invalidation strategy: store the snapshot alongside a hash of the Navi source. On startup, compare the hash; if it differs, delete the snapshot and replay from scratch.

rust
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

fn source_hash(source: &str) -> u64 {
    let mut hasher = DefaultHasher::new();
    source.hash(&mut hasher);
    hasher.finish()
}

// Save
let hash = source_hash(source);
persist_snapshot(&snapshot, hash);

// Restore
let (bytes, saved_hash) = load_snapshot();
if source_hash(source) != saved_hash {
    // Script changed — discard snapshot and cold replay
} else {
    let instance = Instance::restore_state(&bytes)?.build()?;
    // ...
}

Error Handling

ErrorCause
SnapshotError::UnconfirmedBarsave_state() called before confirming the current bar
SnapshotError::Encode(msg)Bincode serialization failed
SnapshotError::Decode(msg)Bincode deserialization failed (corrupt or truncated data)
SnapshotError::VersionMismatch { expected, got }Snapshot was created with a different Navi version

Snapshots vs. reset()

Both snapshots and instance.reset() let you re-run a script, but they serve different purposes:

Snapshotreset()
Retains accumulated state
Skips historical replay on next start
Suitable for server warm restart
Suitable for parameter sweeps
Re-runs from bar 0

Use reset() when you want to replay the same bars from scratch (e.g. comparing different input settings). Use snapshots when you want to resume execution from where you left off.

Next Steps

  • JIT Compilation — enabling ahead-of-time compilation for faster execution

Released under the MIT License.