Skip to main content

Documentation 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.

Every DbService in the backtest-kit library is a thin domain-specific class that extends BaseCRUD — a factory-generated base class that provides the full suite of MongoDB CRUD operations against a given Mongoose model. On top of BaseCRUD, each DbService injects its paired CacheService and adds two methods: upsert (write-through) and findByContext (cache-first read). This means every write to MongoDB is immediately reflected in Redis, and every read checks Redis before touching the database.

BaseCRUD

Factory-generated base with 7 CRUD methods, a 1,000-document findAll limit, and a readTransform normalization step on every result.

DbService pattern

Each service extends BaseCRUD, injects the matching CacheService, and implements write-through upsert and cache-first findByContext.

BaseCRUD

BaseCRUD is produced by the factory() helper, which accepts a Mongoose Model as its constructor argument and returns a class with all CRUD methods pre-wired to that model. Every DbService passes its corresponding Mongoose model to the factory when declaring extends BaseCRUD(SomeModel).
const FIND_ALL_LIMIT = 1_000;

export const BaseCRUD = factory(
  class {
    readonly loggerService = inject<LoggerService>(TYPES.loggerService);
    constructor(public readonly TargetModel: Model<any>) {}

    public async create(dto: object): Promise<any>
    public async update(id: string, dto: object): Promise<any>
    public async findById(id: string): Promise<any>
    public async findByFilter(filterData: object, sort?: object): Promise<any | null>
    public async findAll(filterData: object = {}, limit = FIND_ALL_LIMIT): Promise<any[]>
    public async *iterate(filterData: object = {}, sort?: object): AsyncGenerator
    public async paginate(
      filterData: object,
      pagination: { limit: number; offset: number },
      sort?: object
    ): Promise<{ rows: any[]; total: number }>
  }
);

Methods

create
(dto: object) => Promise<any>
Inserts a new document using the provided data-transfer object. Returns the created document after applying readTransform.
update
(id: string, dto: object) => Promise<any>
Finds a document by its _id and applies a partial update with the supplied fields. Returns the updated document after readTransform.
findById
(id: string) => Promise<any>
Fetches a single document by its MongoDB _id. Returns null if no document is found.
findByFilter
(filterData: object, sort?: object) => Promise<any | null>
Fetches the first document matching the given filter. An optional sort object controls the ordering used to pick the first match. Returns null when no document matches.
findAll
(filterData?: object, limit?: number) => Promise<any[]>
Fetches all documents matching the filter, capped at FIND_ALL_LIMIT = 1_000 by default. Pass a custom limit to override. Returns an empty array when no documents match.
iterate
(filterData?: object, sort?: object) => AsyncGenerator
Returns an async generator that streams matching documents one at a time. Suitable for large result sets where loading everything into memory with findAll is not practical.
paginate
(filterData: object, pagination: { limit: number; offset: number }, sort?: object) => Promise<{ rows: any[]; total: number }>
Returns a page of results alongside the total count of matching documents. Use pagination.offset for cursor-based paging and pagination.limit to control page size.

readTransform

Every method that returns a document applies the readTransform() utility to the raw Mongoose toJSON() output before returning. readTransform normalises the document by converting the Mongoose _id ObjectId to a plain string id field and stripping internal Mongoose metadata (__v, etc.), producing a clean plain object suitable for application code.
FIND_ALL_LIMIT = 1_000 is a safety cap to prevent accidentally loading unbounded collections into memory. For collections that may exceed this size, use iterate() or paginate() instead of findAll().

DbService pattern

Every one of the 15 DbServices follows the same structural pattern: extend BaseCRUD with the appropriate Mongoose model, inject the paired CacheService, and implement upsert and findByContext.
export class SignalDbService extends BaseCRUD(SignalModel) {
  readonly signalCacheService = inject<SignalCacheService>(TYPES.signalCacheService);

  public upsert = async (
    symbol: string,
    strategyName: string,
    exchangeName: string,
    payload: ISignalRow | null
  ): Promise<void> => {
    const filter = { symbol, strategyName, exchangeName };
    const document = await SignalModel.findOneAndUpdate(
      filter,
      { $set: { payload } },
      { upsert: true, new: true, setDefaultsOnInsert: true },
    );
    const result = readTransform(document.toJSON());
    await this.signalCacheService.setSignalId(result);
  };

  public findByContext = async (
    symbol: string,
    strategyName: string,
    exchangeName: string
  ) => {
    try {
      const cachedId = await this.signalCacheService.getSignalId(
        symbol, strategyName, exchangeName
      );
      if (cachedId) return await super.findById(cachedId);
    } catch { void 0; }
    const result = await super.findByFilter({ symbol, strategyName, exchangeName });
    if (result) await this.signalCacheService.setSignalId(result);
    return result;
  };
}

upsert

upsert uses Mongoose’s findOneAndUpdate with { upsert: true, new: true, setDefaultsOnInsert: true } to either create or update the document that matches the composite context key (symbol + strategyName + exchangeName). After the database operation completes, readTransform is applied and the resulting document ID is immediately written to the corresponding CacheService via setXxxId. This guarantees the cache is always consistent with the latest write.

findByContext

findByContext is a cache-first read:
  1. It asks the CacheService for the document ID corresponding to the compound key.
  2. If a cached ID is found, it calls super.findById(cachedId) and returns early.
  3. If the cache lookup fails or returns nothing, it falls back to super.findByFilter(...) against MongoDB.
  4. On a cache miss that results in a database hit, the result is written back to the cache for future requests.
The try / catch around the cache lookup is intentional — a Redis failure during a read should never block the database fallback. Errors from the cache are silently swallowed so findByContext always degrades gracefully.

Service variants

CandleDbService — insert-only

CandleDbService.create uses $setOnInsert rather than $set, making it an insert-only operation. If a candle document for a given context already exists, the call is a no-op. This prevents accidental overwriting of historical OHLCV data.

Soft-delete services

MeasureDbService, IntervalDbService, and MemoryDbService extend the standard pattern with additional soft-delete and listing methods. Rather than deleting a document from MongoDB, softRemove sets a removed: true flag so the record is retained for auditing but excluded from active queries. MeasureDbService extra methods:
MethodSignatureDescription
softRemove(bucket: string, entryKey: string) => Promise<void>Sets removed: true on the matching measure document and syncs the cache.
listKeys(bucket: string) => Promise<string[]>Returns all non-removed entryKey strings for the given bucket.
IntervalDbService extra methods:
MethodSignatureDescription
softRemove(bucket: string, entryKey: string) => Promise<void>Sets removed: true on the matching interval document and syncs the cache.
listKeys(bucket: string) => Promise<string[]>Returns all non-removed entryKey strings for the given bucket.
clearBucket(bucket: string) => Promise<void>Permanently deletes all interval documents for a bucket and clears their cache entries.
MemoryDbService extra methods:
MethodSignatureDescription
softRemove(signalId: string, bucketName: string, memoryId: string) => Promise<void>Sets removed: true on the matching memory entry and syncs the cache.
listEntries(signalId: string, bucketName: string) => Promise<IMemoryRow[]>Returns all non-removed memory entries for the given signal and bucket.
hasMemoryEntry(signalId: string, bucketName: string, memoryId: string) => Promise<boolean>Cache-first existence check for a memory entry.
Documents soft-removed via softRemove() have removed: true set in the database. The listing methods (listKeys, listEntries) filter on { removed: false } so they exclude soft-deleted records automatically. However, findByFilter and findAll do not automatically apply this filter — callers must pass { removed: false } explicitly if they want to exclude soft-deleted records.

All 15 DbServices

DbServiceMongoose ModelPaired CacheService
candleDbServiceCandleModelcandleCacheService
signalDbServiceSignalModelsignalCacheService
scheduleDbServiceScheduleModelscheduleCacheService
riskDbServiceRiskModelriskCacheService
partialDbServicePartialModelpartialCacheService
breakevenDbServiceBreakevenModelbreakevenCacheService
storageDbServiceStorageModelstorageCacheService
notificationDbServiceNotificationModelnotificationCacheService
logDbServiceLogModellogCacheService
measureDbServiceMeasureModelmeasureCacheService
intervalDbServiceIntervalModelintervalCacheService
memoryDbServiceMemoryModelmemoryCacheService
recentDbServiceRecentModelrecentCacheService
stateDbServiceStateModelstateCacheService
sessionDbServiceSessionModelsessionCacheService

Accessing DbServices

All 15 services are available directly from the ioc object exported by the package:
import { ioc } from "backtest-kit";

// Upsert a signal document and update the Redis cache atomically
await ioc.signalDbService.upsert(
  "TRXUSDT",
  "jan_2026_strategy",
  "ccxt-exchange",
  signalPayload
);

// Cache-first read — hits Redis first, falls back to MongoDB
const signal = await ioc.signalDbService.findByContext(
  "TRXUSDT",
  "jan_2026_strategy",
  "ccxt-exchange"
);

Build docs developers (and LLMs) love