> ## Documentation Index
> Fetch the complete documentation index at: https://mathematicalcompany.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Persistence

> SQLite persistence for crash recovery, position snapshots, and audit trail.

Horizon uses SQLite with WAL mode for crash recovery and an audit trail. All fills, order events, position snapshots, and risk events are persisted automatically.

## Quick Start

Persistence is **enabled by default**. When you call `hz.run(name="my_strategy")`, a database is created at `./my_strategy.db`.

```python theme={null}
# Default: creates ./my_strategy.db
hz.run(name="my_strategy", ...)

# Custom path
hz.run(name="my_strategy", db_path="/data/horizon.db", ...)

# Disable persistence
hz.run(name="my_strategy", db_path=None, ...)
```

## Configuration

| Method                  | Behavior                                    |
| ----------------------- | ------------------------------------------- |
| `db_path` not specified | Uses `HORIZON_DB` env var, or `./{name}.db` |
| `db_path="/path/to/db"` | Uses the specified path                     |
| `db_path=None`          | Disables persistence entirely               |
| `HORIZON_DB` env var    | Overrides the default path                  |

## SQLite Schema

The database contains five tables:

<AccordionGroup>
  <Accordion title="fills - Append-only fill journal">
    Source of truth for position reconstruction. Uses `INSERT OR IGNORE` on `fill_id` for idempotent dedup.

    ```sql theme={null}
    CREATE TABLE fills (
        fill_id TEXT PRIMARY KEY,
        order_id TEXT NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        order_side TEXT NOT NULL,
        price REAL NOT NULL,
        size REAL NOT NULL,
        fee REAL NOT NULL DEFAULT 0.0,
        timestamp REAL NOT NULL,
        token_id TEXT,
        exchange TEXT NOT NULL DEFAULT '',
        inserted_at REAL NOT NULL
    );
    ```
  </Accordion>

  <Accordion title="order_events - Order lifecycle log">
    One row per status change (append-only).

    ```sql theme={null}
    CREATE TABLE order_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        order_id TEXT NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        order_side TEXT NOT NULL,
        price REAL NOT NULL,
        size REAL NOT NULL,
        filled_size REAL NOT NULL DEFAULT 0.0,
        remaining_size REAL NOT NULL,
        order_type TEXT NOT NULL,
        time_in_force TEXT NOT NULL,
        status TEXT NOT NULL,
        status_reason TEXT,
        exchange TEXT NOT NULL DEFAULT '',
        created_at REAL NOT NULL,
        recorded_at REAL NOT NULL
    );
    ```
  </Accordion>

  <Accordion title="position_snapshots - Periodic snapshots">
    Periodic snapshots for fast recovery. Each snapshot is a batch of positions with a shared timestamp.

    ```sql theme={null}
    CREATE TABLE position_snapshots (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        snapshot_batch REAL NOT NULL,
        market_id TEXT NOT NULL,
        side TEXT NOT NULL,
        size REAL NOT NULL,
        avg_entry_price REAL NOT NULL,
        realized_pnl REAL NOT NULL,
        unrealized_pnl REAL NOT NULL,
        token_id TEXT,
        exchange TEXT NOT NULL DEFAULT ''
    );
    ```
  </Accordion>

  <Accordion title="risk_events - Risk event log">
    Kill switch activations, deactivations, and violations.

    ```sql theme={null}
    CREATE TABLE risk_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        event_type TEXT NOT NULL,
        details TEXT,
        timestamp REAL NOT NULL
    );
    ```
  </Accordion>

  <Accordion title="strategy_runs - Run audit trail">
    Records strategy run start/end with exchange info.

    ```sql theme={null}
    CREATE TABLE strategy_runs (
        run_id TEXT PRIMARY KEY,
        strategy_name TEXT NOT NULL,
        exchange TEXT NOT NULL,
        started_at REAL NOT NULL,
        ended_at REAL,
        config TEXT
    );
    ```
  </Accordion>
</AccordionGroup>

## Crash Recovery

Recovery happens automatically on startup when persistence is enabled:

<Steps>
  <Step title="Load latest snapshot">
    The engine loads the most recent position snapshot from `position_snapshots`.
  </Step>

  <Step title="Replay fills">
    Fills from the `fills` table with `timestamp >= snapshot_timestamp` are replayed to update positions.
  </Step>

  <Step title="Detect orphaned orders">
    Orders that were open when the previous run ended are detected and logged as warnings.
  </Step>
</Steps>

```python theme={null}
# Manual recovery (when using Engine directly)
engine = Engine(db_path="./my_strategy.db")
recovered = engine.recover_state()
print(f"Recovered {recovered} positions")

# Check for orphaned orders
orphaned = engine.db_open_order_ids()
```

## Engine Persistence Methods

```python theme={null}
# Snapshot positions to DB
count = engine.snapshot_positions()

# Recover state from DB
count = engine.recover_state()

# Record strategy run start/end
engine.start_run("my_strategy")
engine.end_run()

# Check persistence status
engine.has_persistence()  # True/False

# Get current run ID
engine.db_run_id()  # UUID string

# Get orphaned order IDs
engine.db_open_order_ids()  # list[str]
```

## Automatic Behavior

When `hz.run()` manages the engine:

| Event           | Action                                          |
| --------------- | ----------------------------------------------- |
| Startup         | `recover_state()`: load snapshot + replay fills |
| Every 50 cycles | `snapshot_positions()`: save current positions  |
| Shutdown        | `snapshot_positions()` + `end_run()`            |
| Engine drop     | Final snapshot + end run + cancel all           |

## Performance

The database is configured for low-latency trading workloads:

```sql theme={null}
PRAGMA journal_mode = WAL;       -- Write-ahead logging
PRAGMA synchronous = NORMAL;     -- Balanced durability/speed
PRAGMA cache_size = -8000;       -- 8MB cache
PRAGMA busy_timeout = 5000;      -- 5s busy retry
PRAGMA temp_store = MEMORY;      -- In-memory temp tables
PRAGMA mmap_size = 268435456;    -- 256MB memory-mapped I/O
```

Hot-path queries use `prepare_cached` for prepared statement reuse.

## CLI Database Commands

Query the database from the command line:

```bash theme={null}
# Show recent fills
python -m horizon fills --db ./my_strategy.db --limit 50

# Show fills for a specific market
python -m horizon fills --db ./my_strategy.db --market will-btc-hit-100k

# Show latest position snapshot
python -m horizon positions --db ./my_strategy.db

# Show orders (open only by default)
python -m horizon orders --db ./my_strategy.db
python -m horizon orders --db ./my_strategy.db --all
```
