Skip to main content

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 ships 16 custom persist adapters that implement the full IPersist*Instance contract on top of MinIO (as the source of truth) and Redis (as a time-ordered index). Every adapter is registered in src/config/setup.ts via a PersistXAdapter.usePersistXAdapter() call. Strategy code, runners, and the CLI entrypoint stay unchanged — only the persistence layer is swapped. All 16 adapters share the same MinIO bucket, backtest-kit, and follow an identical initialization skeleton.

Common Adapter Skeleton

Each adapter gates its first initialization on Redis being ready via a single shared singleshot function called waitForInfra. Because singleshot runs the inner function exactly once regardless of how many adapters call it concurrently, all 16 instances block together on the first waitForInit(true) call and then proceed in parallel thereafter. The MinIO client itself connects lazily per bucket — no explicit MinIO init is needed.
const waitForInfra = singleshot(
  async () => {
    await ioc.redisService.waitForInit();
  }
);

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);
  }
});
The waitForInfra function is a singleshot — it runs the Redis waitForInit() exactly once across all 16 adapters. Subsequent calls (whether from the same adapter or a different one) resolve immediately from a cached promise. This means all adapters share a single initialization barrier rather than each establishing their own Redis connection gate.

Bucket and Folder Naming Convention

Everything lives in one MinIO bucket named backtest-kit. Each entity type gets a dedicated prefix inside that bucket, defined by passing a "bucket/folder" string to BaseStorage:
// BaseStorage("backtest-kit/candle-items")
//   → bucket:  backtest-kit
//   → prefix:  candle-items/
export class CandleDataService extends BaseStorage("backtest-kit/candle-items") { ... }
The first path segment before the slash becomes the physical S3 bucket name; every subsequent segment becomes a transparent object key prefix. A name without a slash (BaseStorage("breakeven-items")) would still mean a dedicated bucket — fully backward compatible with older adapters.

Inverted Timestamp

Three entity types — Log, Notification, and Storage — need newest-first listings. Rather than sorting after retrieval, these adapters embed an inverted timestamp in the object key:
⟲ts = MAX_SAFE_INTEGER − ms  (zero-padded to a fixed width)
Plain lexicographic S3 listing then yields the newest entries first with zero extra work. This inverted key is computed as:
const TIMESTAMP_PAD = String(Number.MAX_SAFE_INTEGER).length;

const inverted = String(Number.MAX_SAFE_INTEGER - when.getTime())
  .padStart(TIMESTAMP_PAD, "0");

All 16 Adapters

AdapterFolderObject KeyPurpose
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
† Entity is also maintained in the Redis time index for O(limit) newest-first reads.

Adapter Categories

The 16 adapters fall into four functional groups: Market data — immutable OHLCV history, insert-only semantics, point reads by exact timestamp. Trading state — the live, mutable state of running strategies. All use deterministic keys so an upsert is a single idempotent PUT with no read-before-write. Event logs (Redis-indexed) — entities that need newest-first listing beyond what S3 lexicographic order can provide. These maintain a Redis minute-bucket index alongside MinIO objects, with a cold-index fallback to plain bucket listing when Redis is flushed. Utility/cache — soft-delete adapters that physically remove the MinIO object on remove(). listKeys() is a pure prefix LIST — zero body reads.

Candle Adapter

OHLCV persistence with insert-only semantics, deterministic keys, and stat-before-PUT deduplication. Covers ICandleDto, ICandleRow, and the interval-to-milliseconds mapping.

Trading State Adapters

All 9 adapters that persist live strategy state: Signal, Schedule, Strategy, Risk, Partial, Breakeven, Recent, State, and Session — each with constructor args, read/write signatures, and key schemas.

Redis-Indexed Entities & Utility Adapters

Log, Notification, and Storage adapters with their Redis minute-bucket index, cold-index fallback, and inverted timestamp keys. Plus Measure, Interval, and Memory utility adapters with soft-delete semantics.

Build docs developers (and LLMs) love