Documentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-kit-minio-s3-docker/llms.txt
Use this file to discover all available pages before exploring further.
backtest-kit-minio-s3-docker replaces the default file-based ./dump/ persistence with a deliberate two-layer design: MinIO (S3) is the source of truth for every entity, while Redis acts as a fast time-ordered index for the three entities that need newest-first listings. Strategy code, runners, and the CLI entrypoint are completely unchanged — only the persistence layer is swapped via 16 custom IPersist*Instance adapters registered in src/config/setup.ts. The two layers have different jobs and are never interchangeable: MinIO owns durability, Redis owns order.
The Persistence Contract
backtest-kit imposes a strict write durability contract on every persistence adapter: after writeXData(...) returns, the very next readXData(...) call must observe the just-written value. No eventual consistency, no cache warm-up window.
S3 (and MinIO’s S3-compatible API) provides strong read-after-write consistency for individual objects: a successful PUT is immediately visible to any subsequent GET or HEAD on the same key. Because every entity’s object key is a deterministic function of its context fields, an upsert maps to a single idempotent PUT with no read-before-write or duplicate-key races. The contract therefore holds with plain object semantics — no distributed transactions required.
Write Durability
Four properties together provide database-level durability guarantees without a database:-
Deterministic keys. Every record’s object key is a pure function of its context (
symbol/strategy/exchange/…). An upsert is a single idempotentPUT— calling it twice produces exactly the same object with the same key. -
Immutable entities never rewrite. Candles use a
stat+PUTinsert-only pair: if the key already exists the write is skipped entirely, since the stored body is fully determined by the DTO. Log entries and notifications also skip thePUTwhen the key already exists, and an in-process FIFO-capped index of persisted keys (PERSISTED_KEYS_LIMIT = 10_000) prevents redundant networkstatcalls in steady state. - Write order: MinIO first, Redis second. Every service that touches both layers writes the S3 object before registering in the Redis index. A crash between the two leaves an object that is readable by key but temporarily invisible to listings — never a phantom Redis entry pointing at a missing object.
-
removedmeans absent. Soft-delete entities (Measure, Interval, Memory) physically delete the S3 object rather than writing a tombstone flag.listKeysis then a pure prefixLISTwith zero body reads, and reads of removed entries returnnullby construction.
Crash safety flows directly from write order: because MinIO is written first, a process killed between the MinIO write and the Redis
SADD leaves the system in a safe state. The object exists and is readable by key. The Redis index will be missing that one entry, but it will be recovered automatically the next time listNewest falls back to the cold-index path and re-warms Redis from the bucket listing.Data Flow
The following diagram traces a singlewriteLogData call through both layers:
readLogData call follows the reverse path:
All 16 Entities
Every adapter lives in one MinIO bucket calledbacktest-kit. Each entity occupies its own root folder. The three entities marked † also maintain a Redis time index.
| Adapter | Folder | Object Key Format | Purpose |
|---|---|---|---|
| Candle | candle-items/ | exchange/symbol/interval/timestamp | OHLCV cache; immutable inserts |
| Signal | signal-items/ | symbol/strategy/exchange | Live signal state per context |
| Schedule | schedule-items/ | symbol/strategy/exchange | Pending scheduled signal |
| Strategy | strategy-items/ | symbol/strategy/exchange | Deferred commit queue snapshot |
| Risk | risk-items/ | riskName/exchange | Active risk positions snapshot |
| Partial | partial-items/ | symbol/strategy/exchange/signalId | Partial profit/loss levels per signal |
| Breakeven | breakeven-items/ | symbol/strategy/exchange/signalId | Breakeven reached flag |
| Storage † | storage-items/ | backtest/signalId | Closed/opened signal log per mode |
| Notification † | notification-items/ | backtest/⟲ts_notificationId | Event notifications |
| Log † | log-items/ | ⟲ts_entryId | Strategy log entries |
| Measure | measure-items/ | bucket/entryKey | LLM/API response cache |
| Interval | interval-items/ | bucket/entryKey | Once-per-interval markers |
| Memory | memory-items/ | signalId/bucket/memoryId | Per-signal memory store |
| Recent | recent-items/ | symbol/strategy/exchange/frame/backtest | Last public signal per context |
| State | state-items/ | signalId/bucket | Per-signal state buckets |
| Session | session-items/ | strategy/exchange/frame/symbol/backtest | One session per running strategy |
⟲ts denotes an inverted timestamp (Number.MAX_SAFE_INTEGER − ms, zero-padded to a fixed width). Plain lexicographic S3 listing yields newest-first with no additional sorting, which is exactly what the cold-index fallback relies on.
Explore the Two Layers
MinIO Storage
Bucket layout, object key schemas,
BaseStorage factory operations, and the candle insert-only pattern.Redis Index
Minute-bucket design, the
listNewest backwards-walk algorithm, cold-index fallback, and write-order guarantees.