Storage, Notification, and Log are the three entities backed by both MinIO as the source of truth and a Redis time-ordered index. The Redis index enables O(limit) reads at startup — only the most recentDocumentation 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.
limit objects are fetched, completely independent of how many objects the bucket holds in total.
Notification and Log embed an inverted millisecond timestamp in every object key so that plain S3 lexicographic listing is already newest-first with no runtime sorting. Storage uses a stable backtest/signalId key with no timestamp component — each signal is rewritten in place, and the Redis index is the only mechanism that provides time-ordered listings for Storage.
Inverted timestamps
Notification and Log embed an inverted millisecond timestamp in every object key:MAX_SAFE_INTEGER − t decreases as real time t increases, lexicographically smaller keys are newer. A plain LIST call to MinIO therefore delivers the most recent Notification and Log objects first with zero sort overhead — useful as the cold-index fallback for those two adapters when Redis has been flushed. Storage does not use inverted timestamps; its cold-index fallback performs an unordered full prefix walk of the bucket before re-warming the Redis index.
The write path always registers the object name in the Redis minute-SET index
after the MinIO PUT completes. A crash between the two leaves the object
readable by key but invisible to listings — never a phantom entry pointing at
nothing.
Storage adapter
Purpose
The Storage adapter archives the closed (and open) signal log for a given run mode. Each signal is stored as a JSON object under a stablebacktest/signalId key. Because the key is a pure function of context, an upsert is a single idempotent PUT — no read-before-write and no duplicate-key races even when the library replays the whole accumulated list on every write cycle.
Constructor
backtest: boolean is injected by backtest-kit and selects between live and backtest namespaces inside the bucket.
Object key pattern
Interface methods
| Method | Signature | Description |
|---|---|---|
waitForInit | (initial: boolean) => Promise<void> | Gates first-touch on Redis readiness. No-op on subsequent calls. |
readStorageData | () => Promise<StorageData> | Returns all stored signal rows for the current backtest mode. |
writeStorageData | (signals: StorageData) => Promise<void> | Upserts each signal; registers key in Redis index on first write. |
Redis connection service — StorageConnectionService
StorageConnectionService (Redis key prefix: storage-items-connection) maintains one Redis SET per wall-clock minute. On each register(objectName) call it pipelines:
SADDdeduplicates repeated names within a minute by construction.SETNXwrites the floor marker only once (the earliest minute ever seen), which bounds the backwards walk inlistNewest.
Read path
listByMode uses the Redis index for O(limit) access, falling back to MinIO lexicographic listing when the index is cold:
In-process key deduplication
In-process key deduplication
StorageDataService maintains a FIFO-capped Set<string> (REGISTERED_KEYS_LIMIT = 10_000) of already-registered object names. Because backtest-kit re-sends the full accumulated signal list on every write cycle, this set prevents a redundant SADD pipeline call for every signal on every tick — the key is registered only once per process lifetime.Notification adapter
Purpose
The Notification adapter persists event notifications emitted by strategies (trade opens, closes, alerts, and similar domain events). Each notification is an immutable event: the samenotificationId + same createdAt timestamp always maps to identical content, so an existing object is never rewritten.
Constructor
backtest: boolean namespaces notifications by run mode — backtest replays and live runs share the same bucket but never mix entries.
Object key pattern
⟲ts is the inverted millisecond timestamp of notification.createdAt. Lexicographic S3 listing within a backtest/ prefix therefore yields newest notifications first.
The key is computed as:
Interface methods
| Method | Signature | Description |
|---|---|---|
waitForInit | (initial: boolean) => Promise<void> | Gates first-touch on Redis readiness. |
readNotificationData | () => Promise<NotificationData> | Returns the newest 200 notifications for the current mode, oldest-first. |
writeNotificationData | (notifications: NotificationData) => Promise<void> | Upserts each notification; skips already-persisted keys. |
Redis connection service — NotificationConnectionService
NotificationConnectionService (Redis key prefix: notification-items-connection) follows the same minute-SET pattern as StorageConnectionService. The listNewest(limit, prefix) call walks backwards from the current wall-clock minute to the floor, pipelining 1000 minute probes per round trip:
Read path
In-process skip of already-persisted keys
In-process skip of already-persisted keys
NotificationDataService maintains a FIFO-capped Set<string> (PERSISTED_KEYS_LIMIT = 10_000) of object names that have already been written. Because backtest-kit re-sends the full accumulated notification list on every write cycle, this in-process index prevents redundant has() stat calls to MinIO for notifications already known to be persisted.Log adapter
Purpose
The Log adapter persists strategy log entries — structured messages produced by the framework during signal evaluation, order routing, and risk checks. Log entries are immutable: once written, a(entryId, priority-timestamp) pair never changes content.
Constructor
The Log adapter has no constructor parameters. It is a process-global singleton — all strategies, all run modes, and all threads share one log stream:Object key pattern
⟲ts is the inverted millisecond timestamp of entry.priority (the log entry’s wall-clock time). There is no mode prefix — all entries share the same top-level namespace.
The key is computed as:
Interface methods
| Method | Signature | Description |
|---|---|---|
waitForInit | (initial: boolean) => Promise<void> | Gates first-touch on Redis readiness. |
readLogData | () => Promise<LogData> | Returns the newest 200 log entries, oldest-first. |
writeLogData | (entries: LogData) => Promise<void> | Upserts each entry; skips already-persisted keys. |
Redis connection service — LogConnectionService
LogConnectionService (Redis key prefix: log-items-connection) is the reference implementation of the minute-SET pattern. It exposes the same register / listNewest surface as the other two connection services:
listNewest walks backwards from the current minute in batches of 1000, uses SCARD to skip empty minutes without transferring members, and pages hot minutes (where a fast backtest replay packs many entries into one wall-clock minute) via SSCAN with early exit at limit.
Read path
LIST_LIMIT (200) point GETs for bodies — independent of total bucket size.
In-process skip of already-persisted keys
In-process skip of already-persisted keys
LogDataService maintains a FIFO-capped Set<string> (PERSISTED_KEYS_LIMIT = 10_000) of already-written object names. Because backtest-kit re-sends the full accumulated log list on every write cycle, the in-process index prevents a redundant has() stat call to MinIO for every entry that is already known to be stored.Shared Redis index mechanics
All three*ConnectionService implementations follow an identical pattern:
- Write (register)
- Read (listNewest)
- Cold fallback