nautechsystems/nautilus_trader · error

Failed to insert instrument close: {e}

Error message

Failed to insert instrument close: {e}

What it means

Raised by `add_instrument_close` when the INSERT (or upsert) of an `InstrumentClose` record into the `instrument_close` table fails. The library maps the sqlx `execute` error into an `anyhow::Error` with this message. It indicates the close record was not persisted, so downstream replay/backtest data will be incomplete for that instrument.

Source

Thrown at crates/infrastructure/src/sql/queries.rs:271

            INSERT INTO "instrument_close" (
                instrument_id, close_price, close_type, ts_event, ts_init, created_at
            ) VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
            ON CONFLICT (instrument_id) DO UPDATE
            SET close_price = EXCLUDED.close_price,
                close_type = EXCLUDED.close_type,
                ts_event = EXCLUDED.ts_event,
                ts_init = EXCLUDED.ts_init
            "#,
        )
        .bind(close.instrument_id.to_string())
        .bind(close.close_price.to_string())
        .bind(close.close_type.to_string())
        .bind(close.ts_event.to_string())
        .bind(close.ts_init.to_string())
        .execute(pool)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to insert instrument close: {e}"))
    }

    /// Loads all `InstrumentClose` entries.
    ///
    /// # Errors
    ///
    /// Returns an error if the SQL SELECT or row decoding fails.
    pub async fn load_instrument_closes(pool: &PgPool) -> anyhow::Result<Vec<InstrumentClose>> {
        sqlx::query_as::<_, InstrumentCloseRow>(
            "SELECT * FROM instrument_close ORDER BY instrument_id ASC",
        )
        .fetch_all(pool)
        .await
        .map(|rows| rows.into_iter().map(|row| row.0).collect())
        .map_err(|e| anyhow::anyhow!("Failed to load instrument closes: {e}"))
    }

    /// Inserts an `OrderInitialized` event via the provided `pool`.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the wrapped `{e}` for a constraint violation or missing-table message.
  2. Apply the schema migrations to ensure `instrument_close` exists with expected columns.
  3. Deduplicate or use upsert semantics if re-ingesting the same instrument close data.
  4. Verify the connection pool is healthy and retry transient connection failures.

Example fix

// before: ignores failure mode, keeps going
add_instrument_close(&pool, close).await?;

// after: detect duplicate-key errors specifically and retry/skip
if let Err(e) = add_instrument_close(&pool, close).await {
    let msg = format!("{e:#}");
    if msg.contains("duplicate key") {
        tracing::warn!("instrument close already exists, skipping");
    } else {
        return Err(e);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the table exists and this close is not already persisted
let exists = sqlx::query_scalar::<_, i64>(
    "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'instrument_close'")
    .fetch_one(pool).await? > 0;
let dup = exists && sqlx::query_scalar::<_, i64>(
    "SELECT COUNT(*) FROM instrument_close WHERE instrument_id = $1 AND ts_init = $2")
    .bind(close.instrument_id.to_string())
    .bind(close.ts_init.to_string())
    .fetch_one(pool).await? > 0;
if dup { return Ok(()); }

Try / catch

if let Err(e) = add_instrument_close(&pool, &close).await {
    let msg = format!("{e:#}");
    if msg.contains("duplicate key") { tracing::debug!("skipping duplicate close"); }
    else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling `add_instrument_close(pool, close)` when: the `instrument_close` table does not exist; a bound value violates a column constraint (NOT NULL, unique/PK conflict on instrument_id+ts); a column type mismatches the bound string representation; or the connection drops mid-execution.

Common situations: Writing to a database where migrations were not applied; duplicate instrument-close rows on re-running an ingestion job without ON CONFLICT semantics; timestamps or close_type formatting rejected by column types; connection pool closed during long batch writes.

Related errors


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