Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-minio-s3-docker/llms.txt

Use this file to discover all available pages before exploring further.

This project ships 16 custom persist adapters registered in src/config/setup.ts. Each adapter implements the corresponding IPersist*Instance interface from backtest-kit and stores data as JSON objects inside a single MinIO bucket (backtest-kit). The persistence layer is a transparent drop-in replacement for backtest-kit’s default file-based ./dump/ store — strategy code, runners, and the CLI entrypoint remain unchanged.

Common Adapter Skeleton

Every adapter follows the same structural pattern. The waitForInfra gate ensures infrastructure is ready before any adapter first touches MinIO or Redis.
src/config/setup.ts (pattern)
PersistXAdapter.usePersistXAdapter(class implements IPersistXInstance {
  constructor(/* context fields from backtest-kit */) {}

  async waitForInit(initial: boolean) {
    if (!initial) return;
    await waitForInfra();        // gate first-touch on Redis ready (MinIO client is lazy)
  }

  async readXData(...) { return await ioc.xDataService.findByContext(...); }
  async writeXData(..., when: Date) { await ioc.xDataService.upsert(..., when); }
});

Adapter Reference

Everything lives in one MinIO bucket backtest-kit — each entity gets its own root folder. The BaseStorage("backtest-kit/<entity>-items") name format is parsed as bucket/parent-folder: the first path segment is the physical bucket name, the rest becomes a transparent key prefix. A name without a slash (e.g., BaseStorage("breakeven-items")) means a dedicated bucket — fully backward compatible.
AdapterFolder in MinIOObject key formatPurpose
Candlecandle-items/exchange/symbol/interval/timestampOHLCV cache; immutable inserts
Signalsignal-items/symbol/strategy/exchangeLive signal state per context
Scheduleschedule-items/symbol/strategy/exchangePending scheduled signal
Strategystrategy-items/symbol/strategy/exchangeDeferred commit queue snapshot
Riskrisk-items/riskName/exchangeActive risk positions snapshot
Partialpartial-items/symbol/strategy/exchange/signalIdPartial profit/loss levels per signal
Breakevenbreakeven-items/symbol/strategy/exchange/signalIdBreakeven reached flag
Storagestorage-items/backtest/signalIdClosed/opened signal log per mode †
Notificationnotification-items/backtest/⟲ts_notificationIdEvent notifications †
Loglog-items/⟲ts_entryIdStrategy log entries †
Measuremeasure-items/bucket/entryKeyLLM/API response cache
Intervalinterval-items/bucket/entryKeyOnce-per-interval markers
Memorymemory-items/signalId/bucket/memoryIdPer-signal memory store
Recentrecent-items/symbol/strategy/exchange/frame/backtestLast public signal per context
Statestate-items/signalId/bucketPer-signal state buckets
Sessionsession-items/strategy/exchange/frame/symbol/backtestOne session per running strategy
Storage, Notification, and Log are also maintained in the Redis time index (see Redis as a Time-Ordered Index below). ⟲ts denotes an inverted timestamp (Number.MAX_SAFE_INTEGER − ms, zero-padded): plain lexicographic S3 listing yields newest-first order with no sorting overhead.

waitForInit(initial)

waitForInit is called by backtest-kit exactly once per context with initial = true. On subsequent calls (e.g., hot reloads or re-attaches) it receives false and returns immediately. The initial = true path calls waitForInfra(), which gates the adapter on infrastructure readiness.
waitForInfra is created with singleshot from functools-kit, which means Redis initialisation runs exactly once regardless of how many adapters call it concurrently. Subsequent calls resolve immediately from the cached promise.
src/config/setup.ts
const waitForInfra = singleshot(
  async () => {
    await ioc.redisService.waitForInit();
  }
);
The MinIO client itself is lazy — it connects per bucket on first use — so waitForInfra only needs to await Redis.

Redis as a Time-Ordered Index

S3 can list keys only in lexicographic order and cannot natively answer “what was created last” without a full bucket walk. For the three entities that require newest-first listings — Log, Notification, Storage — a *ConnectionService maintains a Redis index alongside every write:
  • One Redis SET per minute: <entity>-connection:<aligned-minute> → object names. register() is a single pipeline (SADD + SETNX of the floor marker). Timestamps are minute-aligned so re-registering within a minute deduplicates by construction.
  • listNewest(limit, prefix) walks backwards from the current minute — direct key lookups, no SCAN over the full keyspace.
  • Cold-index fallback: if Redis was flushed, listings fall back to bucket LIST and re-warm the index. For Log and Notification, inverted-timestamp keys are already newest-first so the fallback read is ordered. For Storage, keys are backtest/signalId with no timestamp — the cold-path walk is unordered.

Adapter Pages

Candle

Immutable OHLCV candlestick data — insert-only semantics, deterministic key.

Signal, Schedule, Strategy

Live signal state, pending scheduled signals, and deferred commit queue snapshots.

Risk, Partial, Breakeven

Active risk positions, partial profit/loss levels, and breakeven reached flags.

Storage, Notification, Log

Redis-indexed newest-first listings for closed signals, event notifications, and strategy logs.

Measure, Interval, Memory

LLM/API response cache, once-per-interval markers, and per-signal memory store.

Recent, State, Session

Last public signal per context, per-signal state buckets, and running strategy sessions.

Build docs developers (and LLMs) love