Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-kit-redis-mongo-docker/llms.txt

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

Risk, Partial, and Breakeven are the three signal-affecting adapters. They store data that directly influences open position management — live risk positions, partial profit/loss price levels, and the breakeven-reached flag. Because this data can change the outcome of a backtest, all three carry a when: Date argument on their write methods and a when: number field in MongoDB. The engine uses when to prevent look-ahead bias: when replaying history, writes that would have happened “in the future” relative to the current simulation timestamp are withheld.

Risk adapter

Stores a snapshot of all active positions for a named risk manager. Context key: (riskName, exchangeName)
Collection: risk-items

IPersistRiskInstance implementation

PersistRiskAdapter.usePersistRiskAdapter(class implements IPersistRiskInstance {
  constructor(
    readonly riskName: string,
    readonly exchangeName: string,
  ) {}

  async waitForInit(initial: boolean) {
    if (!initial) return;
    await waitForInfra();
  }

  async readPositionData(_when: Date): Promise<RiskData> {
    const row = await ioc.riskDbService.findByContext(
      this.riskName, this.exchangeName,
    );
    return row ? row.positions : [];
  }

  async writePositionData(positions: RiskData, when: Date): Promise<void> {
    await ioc.riskDbService.upsert(
      this.riskName, this.exchangeName, positions, when,
    );
  }
});
readPositionData returns [] (empty array) when no document exists for the context, so callers always receive a valid RiskData value.

Risk Mongoose schema

From src/schema/Risk.schema.ts:
import mongoose, { Document, Schema } from "mongoose";
import { RiskData } from "backtest-kit";

interface IRiskDto {
  riskName: string;
  exchangeName: string;
  positions: RiskData;
  when: number;
}

interface RiskDocument extends IRiskDto, Document {}

interface IRiskRow extends IRiskDto {
  id: string;
  createDate: Date;
  updatedDate: Date;
}

const RiskSchema: Schema<RiskDocument> = new Schema(
  {
    riskName:     { type: String, required: true, index: true },
    exchangeName: { type: String, required: true, index: true },
    positions:    { type: Schema.Types.Mixed, required: true, default: [] },
    when:         { type: Number, required: true, index: true },
  },
  { timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);

RiskSchema.index({ riskName: 1, exchangeName: 1 }, { unique: true });

const RiskModel = mongoose.model<RiskDocument>("risk-items", RiskSchema);
positions stores RiskData (an array of position objects) as Schema.Types.Mixed. The when field is stored as a Unix millisecond integer (Date.getTime()).

RiskDbService — upsert with when

public upsert = async (
  riskName: string,
  exchangeName: string,
  positions: RiskData,
  when: Date,
): Promise<void> => {
  const filter = { riskName, exchangeName };
  const document = await RiskModel.findOneAndUpdate(
    filter,
    { $set: { positions, when: when.getTime() } },
    { upsert: true, new: true, setDefaultsOnInsert: true },
  );
  const result = readTransform(document.toJSON()) as unknown as IRiskRow;
  await this.riskCacheService.setRiskId(result);
};
findByContext follows the same Redis-cache-then-MongoDB-fallback pattern as all other adapters.

Partial adapter

Stores the profit/loss price levels for a specific open signal, enabling partial take-profit logic to survive process restarts. Context key: (symbol, strategyName, exchangeName, signalId)
Collection: partial-items

IPersistPartialInstance implementation

PersistPartialAdapter.usePersistPartialAdapter(class implements IPersistPartialInstance {
  constructor(
    readonly symbol: string,
    readonly strategyName: string,
    readonly exchangeName: string,
  ) {}

  async waitForInit(initial: boolean) {
    if (!initial) return;
    await waitForInfra();
  }

  async readPartialData(signalId: string, _when: Date): Promise<PartialData> {
    const row = await ioc.partialDbService.findByContext(
      this.symbol, this.strategyName, this.exchangeName, signalId,
    );
    return row ? row.payload : {};
  }

  async writePartialData(
    data: PartialData,
    signalId: string,
    when: Date,
  ): Promise<void> {
    await ioc.partialDbService.upsert(
      this.symbol, this.strategyName, this.exchangeName, signalId, data, when,
    );
  }
});
Note that signalId is passed as a method argument — not a constructor field — because the constructor only captures the trading context. The same IPersistPartialInstance instance may serve multiple signal IDs over its lifetime.

Partial Mongoose schema

From src/schema/Partial.schema.ts:
import mongoose, { Document, Schema } from "mongoose";
import { PartialData } from "backtest-kit";

interface IPartialDto {
  symbol: string;
  strategyName: string;
  exchangeName: string;
  signalId: string;
  payload: PartialData;
  when: number;
}

interface PartialDocument extends IPartialDto, Document {}

interface IPartialRow extends IPartialDto {
  id: string;
  createDate: Date;
  updatedDate: Date;
}

const PartialSchema: Schema<PartialDocument> = new Schema(
  {
    symbol:       { type: String, required: true, index: true },
    strategyName: { type: String, required: true, index: true },
    exchangeName: { type: String, required: true, index: true },
    signalId:     { type: String, required: true, index: true },
    payload:      { type: Schema.Types.Mixed, required: true, default: {} },
    when:         { type: Number, required: true, index: true },
  },
  { timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);

PartialSchema.index(
  { symbol: 1, strategyName: 1, exchangeName: 1, signalId: 1 },
  { unique: true }
);

const PartialModel = mongoose.model<PartialDocument>("partial-items", PartialSchema);

PartialDbService — upsert with when

public upsert = async (
  symbol: string,
  strategyName: string,
  exchangeName: string,
  signalId: string,
  payload: PartialData,
  when: Date,
): Promise<void> => {
  const filter = { symbol, strategyName, exchangeName, signalId };
  const document = await PartialModel.findOneAndUpdate(
    filter,
    { $set: { payload, when: when.getTime() } },
    { upsert: true, new: true, setDefaultsOnInsert: true },
  );
  const result = readTransform(document.toJSON()) as unknown as IPartialRow;
  await this.partialCacheService.setPartialId(result);
};

Breakeven adapter

Stores the breakeven-reached flag for a specific signal. Once breakeven is hit, the stop-loss is moved to entry price; this adapter persists that state across restarts. Context key: (symbol, strategyName, exchangeName, signalId)
Collection: breakeven-items

IPersistBreakevenInstance implementation

PersistBreakevenAdapter.usePersistBreakevenAdapter(class implements IPersistBreakevenInstance {
  constructor(
    readonly symbol: string,
    readonly strategyName: string,
    readonly exchangeName: string,
  ) {}

  async waitForInit(initial: boolean) {
    if (!initial) return;
    await waitForInfra();
  }

  async readBreakevenData(signalId: string, _when: Date): Promise<BreakevenData> {
    const row = await ioc.breakevenDbService.findByContext(
      this.symbol, this.strategyName, this.exchangeName, signalId,
    );
    return row ? row.payload : {};
  }

  async writeBreakevenData(
    data: BreakevenData,
    signalId: string,
    when: Date,
  ): Promise<void> {
    await ioc.breakevenDbService.upsert(
      this.symbol, this.strategyName, this.exchangeName, signalId, data, when,
    );
  }
});

Breakeven Mongoose schema

From src/schema/Breakeven.schema.ts:
import mongoose, { Document, Schema } from "mongoose";
import { BreakevenData } from "backtest-kit";

interface IBreakevenDto {
  symbol: string;
  strategyName: string;
  exchangeName: string;
  signalId: string;
  payload: BreakevenData;
  when: number;
}

interface BreakevenDocument extends IBreakevenDto, Document {}

interface IBreakevenRow extends IBreakevenDto {
  id: string;
  createDate: Date;
  updatedDate: Date;
}

const BreakevenSchema: Schema<BreakevenDocument> = new Schema(
  {
    symbol:       { type: String, required: true, index: true },
    strategyName: { type: String, required: true, index: true },
    exchangeName: { type: String, required: true, index: true },
    signalId:     { type: String, required: true, index: true },
    payload:      { type: Schema.Types.Mixed, required: true, default: {} },
    when:         { type: Number, required: true, index: true },
  },
  { timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);

BreakevenSchema.index(
  { symbol: 1, strategyName: 1, exchangeName: 1, signalId: 1 },
  { unique: true }
);

const BreakevenModel = mongoose.model<BreakevenDocument>("breakeven-items", BreakevenSchema);
The schema is structurally identical to PartialSchema. Both use a four-field compound unique index that includes signalId, so each open signal gets its own isolated document.

BreakevenDbService — upsert with when

public upsert = async (
  symbol: string,
  strategyName: string,
  exchangeName: string,
  signalId: string,
  payload: BreakevenData,
  when: Date,
): Promise<void> => {
  const filter = { symbol, strategyName, exchangeName, signalId };
  const document = await BreakevenModel.findOneAndUpdate(
    filter,
    { $set: { payload, when: when.getTime() } },
    { upsert: true, new: true, setDefaultsOnInsert: true },
  );
  const result = readTransform(document.toJSON()) as unknown as IBreakevenRow;
  await this.breakevenCacheService.setBreakevenId(result);
};
All three adapters store when as Date.getTime() (Unix milliseconds) rather than a BSON Date. This allows the engine to compare timestamps with simple numeric comparison during backtesting without timezone or precision issues.

Build docs developers (and LLMs) love