Skip to main content

Documentation Index

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

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

Risk, Partial, and Breakeven are the three adapters that carry a when column — a bigint (epoch milliseconds) that records the logical simulation timestamp at the moment of every write. This column is the mechanism by which backtest-kit’s internal look-ahead-bias filter verifies that no read returns a value written in the future relative to the current simulation clock. All three write* methods accept a when: Date argument, and both readPositionData (Risk) and the read*Data methods for Partial and Breakeven also receive when to support time-gated reads.
The when column uses a shared epochTransformer value transformer. The pg driver returns bigint columns as JavaScript strings; the transformer converts them to plain number values so the adapter layer never needs to parse timestamps manually.

Risk Adapter

The Risk adapter snapshots the list of currently active positions managed by a named risk controller. Its context key is (riskName, exchangeName) — intentionally narrower than the signal adapters, because a single risk controller can span multiple trading pairs.

Context Key and Table

FieldTypeRole
riskNamestringIdentifies the risk controller
exchangeNamestringExchange identifier
Table: risk-items — unique index risk_items_uq on (riskName, exchangeName)

Registration

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 receives a _when parameter (prefixed with _ because it is unused on the read path). The when argument is consumed only on the write path to stamp the row. Risk data is always read back as the latest snapshot regardless of simulation time.

Table Schema

const RiskModel = new EntitySchema<IRiskRow>({
  name: "risk-items",
  columns: {
    id:           { type: "uuid", primary: true, generated: "uuid" },
    riskName:     { type: String },
    exchangeName: { type: String },
    positions:    { type: "jsonb" },
    when:         { type: "bigint", transformer: epochTransformer },
    createDate:   { type: "timestamptz", createDate: true },
    updatedDate:  { type: "timestamptz", updateDate: true },
  },
  indices: [
    { name: "risk_items_uq", columns: ["riskName", "exchangeName"], unique: true },
  ],
});

Column Reference

id
uuid
required
Auto-generated UUID primary key.
riskName
string
required
Risk controller name. Part of the unique index.
exchangeName
string
required
Exchange identifier. Part of the unique index.
positions
RiskData (jsonb)
required
Array of active position objects. RiskData is the type exported by backtest-kit.
when
bigint / number
required
Logical simulation timestamp at write time, epoch milliseconds. Read back as number via epochTransformer.
createDate
timestamptz
required
Set by TypeORM on first insert.
updatedDate
timestamptz
required
Updated by TypeORM on every write.

Partial Adapter

The Partial adapter stores the profit/loss take-partial levels for a specific open signal. Its context key includes signalId, making it the first adapter in this group with a four-field unique index. One row exists per (symbol, strategyName, exchangeName, signalId) combination, and its payload is a PartialData JSONB object.

Context Key and Table

FieldTypeRole
symbolstringTrading pair
strategyNamestringStrategy identifier
exchangeNamestringExchange identifier
signalIdstringUnique signal ID — passed at read and write time
Table: partial-items — unique index partial_items_uq on (symbol, strategyName, exchangeName, signalId)

Registration

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
    );
  }
});

Table Schema

const PartialModel = new EntitySchema<IPartialRow>({
  name: "partial-items",
  columns: {
    id:           { type: "uuid", primary: true, generated: "uuid" },
    symbol:       { type: String },
    strategyName: { type: String },
    exchangeName: { type: String },
    signalId:     { type: String },
    payload:      { type: "jsonb" },
    when:         { type: "bigint", transformer: epochTransformer },
    createDate:   { type: "timestamptz", createDate: true },
    updatedDate:  { type: "timestamptz", updateDate: true },
  },
  indices: [
    {
      name: "partial_items_uq",
      columns: ["symbol", "strategyName", "exchangeName", "signalId"],
      unique: true,
    },
  ],
});

Column Reference

signalId
string
required
Unique signal identifier. Not stored in the adapter constructor — passed as an argument to readPartialData and writePartialData at call time. Part of the unique index.
payload
PartialData (jsonb)
required
Take-partial level configuration for this signal. PartialData is the type exported by backtest-kit. Returns {} (empty object) when no row exists.
when
bigint / number
required
Logical simulation timestamp at write time, epoch milliseconds.

Breakeven Adapter

The Breakeven adapter tracks whether the breakeven level has been reached for a specific signal. Like Partial, its context key includes signalId, and it shares an identical table shape — differing only in that its payload type is BreakevenData rather than PartialData.

Context Key and Table

FieldTypeRole
symbolstringTrading pair
strategyNamestringStrategy identifier
exchangeNamestringExchange identifier
signalIdstringUnique signal ID — passed at read and write time
Table: breakeven-items — unique index breakeven_items_uq on (symbol, strategyName, exchangeName, signalId)

Registration

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
    );
  }
});

Table Schema

const BreakevenModel = new EntitySchema<IBreakevenRow>({
  name: "breakeven-items",
  columns: {
    id:           { type: "uuid", primary: true, generated: "uuid" },
    symbol:       { type: String },
    strategyName: { type: String },
    exchangeName: { type: String },
    signalId:     { type: String },
    payload:      { type: "jsonb" },
    when:         { type: "bigint", transformer: epochTransformer },
    createDate:   { type: "timestamptz", createDate: true },
    updatedDate:  { type: "timestamptz", updateDate: true },
  },
  indices: [
    {
      name: "breakeven_items_uq",
      columns: ["symbol", "strategyName", "exchangeName", "signalId"],
      unique: true,
    },
  ],
});
Both Partial and Breakeven return an empty object {} (not null) when no row exists for the given signalId. This allows strategies to destructure the result directly without a null check.

Look-Ahead Bias: The when Column

All three adapters write the logical simulation clock value into the when column on every upsert. The DbService.upsert() call converts the Date to epoch milliseconds via when.getTime() before passing it to TypeORM, and the epochTransformer converts it back to a number on read:
// Inside RiskDbService.upsert (same pattern for Partial and Breakeven)
.values({ riskName, exchangeName, positions, when: when.getTime() })
.orUpdate(["positions", "when"], ["riskName", "exchangeName"])
The when argument to readPositionData / readPartialData / readBreakevenData is currently unused on the adapter read path (it is prefixed _when in setup.ts). Filtering is delegated to backtest-kit’s upstream look-ahead-bias layer, which compares the when value stored in the row against the current simulation time before returning the data to strategy code.

Build docs developers (and LLMs) love