During a backtest, backtest-kit issues thousands ofDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-redis-mongo-docker/llms.txt
Use this file to discover all available pages before exploring further.
readXData() calls per second—one for every adapter context touched by the strategy on each simulated tick. If every call hit MongoDB with a compound-field query, even a well-indexed collection would accumulate meaningful latency across millions of ticks. The Redis ID cache solves this by mapping each compound context (exchange + strategy + symbol) to its MongoDB _id string. A Redis GET is O(1) regardless of collection size, and the subsequent MongoDB findById lookup by primary key is O(log n) on the _id index rather than a full compound-index traversal. The net effect is that the vast majority of reads never touch MongoDB’s collection scan path at all.
Why an ID Cache Rather Than a Value Cache
Caching the full document value would create a two-write problem on every update: you would need to atomically update MongoDB and invalidate or replace the Redis value, with no easy way to guarantee the two stay in sync across process restarts or crashes. Caching only the immutable_id avoids this entirely. The _id never changes after insertion, so it can be cached indefinitely with no invalidation logic. On a cache hit, the system fetches the current document from MongoDB by primary key—guaranteeing it always reads the latest value from the source of truth.
BaseMap: Redis Abstraction for All 15 Cache Services
All 15 cache services extendBaseMap, a dependency-injection factory class that wraps ioredis with a Map-like async interface:
Key Namespacing
Every Redis key stored by a cache service is prefixed with the service’sconnectionKey:
connectionKey values can never collide in Redis, even if they store entries with identical sub-keys. For example, signal_cache:binance:macd:BTCUSDT and risk_cache:binance:macd:BTCUSDT are distinct Redis keys despite sharing the same context tuple.
TTL Behavior
BaseMap accepts a ttlExpireSeconds parameter in its constructor. When set to any positive integer, the set method applies a Redis EXPIRE command after writing. All 15 production cache services pass -1, which disables expiry entirely—cached IDs are valid for the lifetime of the process and persist across Redis reconnects:
The
DEFAULT_TTL_EXPIRE_SECONDS of 5 minutes applies only when BaseMap is used directly without a TTL argument. All 15 named cache services explicitly pass -1 to opt out of expiry.SCAN-Based Bulk Operations
Methods that operate on multiple keys—clear(), toArray(), iterate(), keys(), values(), size()—use Redis SCAN internally rather than KEYS. This avoids blocking the Redis event loop on large keyspaces. The cursor-based scan is batched at ITERATOR_BATCH_SIZE = 100 keys per round trip, matching the Redis recommendation for incremental iteration.
SignalCacheService: A Detailed Walk-Through
SignalCacheService is the canonical example of the cache pattern used across all 15 services:
_id string (e.g., "6627f3e1a2b3c4d5e6f70001").
The Complete Read Path
SignalDbService.findByContext illustrates the full two-tier lookup:
Redis lookup
Call
getSignalId(symbol, strategyName, exchangeName), which issues a single Redis GET on the namespaced key. This is O(1) regardless of how many documents exist in the MongoDB collection.Cache hit: MongoDB findById
If a cached
_id is found, call super.findById(cachedId). MongoDB resolves this with an index seek on the _id B-tree—the fastest possible document lookup path.Cache miss: compound query
If Redis returns nothing (or throws), fall back to
super.findByFilter({ symbol, strategyName, exchangeName }). This traverses the compound index on those three fields.The
try/catch around the cache lookup is intentional. A transient Redis error (e.g., during reconnect) should degrade gracefully to the MongoDB path rather than surfacing an error to the strategy.Redis Connection Keepalive and Timeout
The Redis client inredis.ts sends a 30-second keepalive ping to prevent the server from closing idle connections:
RedisService.waitForInit enforces a 15-second connection timeout. If the ready event is not received within 15 000 ms, waitForInit.clear() is called to reset the singleshot memoization, allowing the next caller to attempt initialization again:
MongooseService.waitForInit applies the same 15-second timeout, also calling waitForInit.clear() on expiry so that transient connection failures are recoverable.
All 15 Cache Services and Their Redis Keys
Each service owns oneconnectionKey, which becomes the namespace prefix for all keys it stores.
| Cache Service | Redis Key Prefix | Context Sub-key Format |
|---|---|---|
candleCacheService | candle_cache | exchangeName:symbol:interval:timestamp |
signalCacheService | signal_cache | exchangeName:strategyName:symbol |
scheduleCacheService | schedule_cache | exchangeName:strategyName:symbol |
riskCacheService | risk_cache | exchangeName:riskName |
partialCacheService | partial_cache | exchangeName:strategyName:symbol:signalId |
breakevenCacheService | breakeven_cache | exchangeName:strategyName:symbol:signalId |
storageCacheService | storage_cache | backtest|live:signalId |
notificationCacheService | notification_cache | backtest|live:notificationId |
logCacheService | log_cache | entryId |
measureCacheService | measure_cache | bucket:entryKey |
intervalCacheService | interval_cache | bucket:entryKey |
memoryCacheService | memory_cache | signalId:bucketName:memoryId |
recentCacheService | recent_cache | backtest|live:exchangeName:strategyName:frameName:symbol |
stateCacheService | state_cache | signalId:bucketName |
sessionCacheService | session_cache | exchangeName:strategyName:frameName |