nautechsystems/nautilus_trader · error · anyhow::Error

`synthetic` {synthetic.id} already exists

Error message

`synthetic` {synthetic.id} already exists

What it means

`DataActor::add_synthetic` checks the cache for an existing instrument with the same `synthetic.id` before adding. Because synthetic IDs are unique keys, adding a duplicate is rejected with this bail instead of silently overwriting the cached instrument.

Source

Thrown at crates/common/src/actor/data_actor.rs:4309

        );
        let data = CustomData::new(Arc::new(signal), data_type);
        let topic = get_custom_topic(&data.data_type);
        msgbus::publish_any(topic, &data);
    }

    /// Adds the `synthetic` instrument to the cache.
    ///
    /// # Errors
    ///
    /// Returns an error if a synthetic with the same ID already exists, or if the
    /// backing cache fails to persist it. Panics if the actor is not registered
    /// with a trader. // panics-doc-ok
    pub fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
        self.check_registered();

        let cache = self.cache_rc();
        if cache.borrow().synthetic(&synthetic.id).is_some() {
            anyhow::bail!("`synthetic` {} already exists", synthetic.id);
        }
        cache.borrow_mut().add_synthetic(synthetic)
    }

    /// Updates the `synthetic` instrument in the cache, replacing the existing entry.
    ///
    /// # Errors
    ///
    /// Returns an error if no synthetic with the same ID already exists, or if the
    /// backing cache fails to persist the replacement. Panics if the actor is not
    /// registered with a trader. // panics-doc-ok
    pub fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
        self.check_registered();

        let cache = self.cache_rc();
        if cache.borrow().synthetic(&synthetic.id).is_none() {
            anyhow::bail!("`synthetic` {} does not exist", synthetic.id);
        }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check `cache.synthetic(&id)` (or the actor's existing-synthetic state) before adding and skip if present
  2. Use `update_synthetic` if the intent is to replace the definition
  3. Generate a unique ID (e.g. include a run/venue suffix) when creating new synthetics

Example fix

// before
actor.add_synthetic(spread)?;
// after
if actor.cache_rc().borrow().synthetic(&spread.id).is_none() {
    actor.add_synthetic(spread)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if cache.borrow().synthetic(&id).is_some() { /* skip add or update instead */ }

Try / catch

match actor.add_synthetic(synthetic) {
    Err(e) if e.to_string().contains("already exists") => { /* already added; reuse existing */ }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `actor.add_synthetic(synthetic)` when `cache.synthetic(&synthetic.id)` already returns Some — i.e. the same synthetic ID was already added (by this actor or another component).

Common situations: Re-running a strategy setup that adds synthetics on each start; constructing a synthetic from config where IDs collide across runs; two actors adding the same synthetic definition.

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/55d08fe0130a494f. Report an issue: GitHub.