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.

Recent, State, and Session are the three strategy lifecycle adapters. All carry a when column for look-ahead bias protection, and all follow the same atomic INSERT … ON CONFLICT … DO UPDATE … RETURNING * upsert pattern. State and Session both implement a dispose() no-op to satisfy the IPersist*Instance cleanup hook required by backtest-kit.

Recent Adapter

The Recent adapter stores the last publicly visible signal row for a given (symbol, strategyName, exchangeName, frameName, backtest) context. It is the widest context key of all 16 adapters — five fields — which ensures that two symbols running the same strategy on the same exchange but in different frames never share a recent-signal row.

Context Key and Table

FieldTypeRole
symbolstringTrading pair
strategyNamestringStrategy identifier
exchangeNamestringExchange identifier
frameNamestringFrame identifier (e.g. "Jan2026Frame")
backtestbooleanExecution mode flag
Table: recent-items — unique index recent_items_uq on (symbol, strategyName, exchangeName, frameName, backtest)

Registration

PersistRecentAdapter.usePersistRecentAdapter(class implements IPersistRecentInstance {
  constructor(
    readonly symbol: string,
    readonly strategyName: string,
    readonly exchangeName: string,
    readonly frameName: string,
    readonly backtest: boolean,
  ) {}

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

  async readRecentData(): Promise<RecentData> {
    const row = await ioc.recentDbService.findByContext(
      this.symbol,
      this.strategyName,
      this.exchangeName,
      this.frameName,
      this.backtest,
    );
    return row ? row.payload : null;
  }

  async writeRecentData(signalRow: NonNullable<RecentData>, when: Date): Promise<void> {
    await ioc.recentDbService.upsert(
      this.symbol,
      this.strategyName,
      this.exchangeName,
      this.frameName,
      this.backtest,
      signalRow,
      when,
    );
  }
});

Table Schema

const RecentModel = new EntitySchema<IRecentRow>({
  name: "recent-items",
  columns: {
    id:           { type: "uuid", primary: true, generated: "uuid" },
    symbol:       { type: String },
    strategyName: { type: String },
    exchangeName: { type: String },
    frameName:    { type: String },
    backtest:     { type: "boolean" },
    payload:      { type: "jsonb" },
    when:         { type: "bigint", transformer: epochTransformer },
    createDate:   { type: "timestamptz", createDate: true },
    updatedDate:  { type: "timestamptz", updateDate: true },
  },
  indices: [
    {
      name: "recent_items_uq",
      columns: ["symbol", "strategyName", "exchangeName", "frameName", "backtest"],
      unique: true,
    },
  ],
});

Column Reference

symbol
string
required
Trading pair. Part of the unique index.
strategyName
string
required
Strategy identifier. Part of the unique index.
exchangeName
string
required
Exchange identifier. Part of the unique index.
frameName
string
required
Frame identifier. Part of the unique index.
backtest
boolean
required
Execution mode flag. Part of the unique index. Isolates backtest rows from live/paper rows.
payload
IPublicSignalRow (jsonb)
required
The last public signal row. IPublicSignalRow is the type exported by backtest-kit. Returns null when no signal has been written yet.
when
bigint / number
required
Logical simulation timestamp at write time, epoch milliseconds.

State Adapter

The State adapter stores per-signal state buckets. A single signal can have multiple independent state buckets (e.g., one for entry logic, one for exit logic), each addressed by (signalId, bucketName). The dispose() method is a no-op — state entries persist until explicitly overwritten by a new writeStateData call.

Context Key and Table

FieldTypeRole
signalIdstringSignal scope — set in constructor
bucketNamestringSub-scope within the signal — set in constructor
Table: state-items — unique index state_items_uq on (signalId, bucketName)

Registration

PersistStateAdapter.usePersistStateAdapter(class implements IPersistStateInstance {
  constructor(
    readonly signalId: string,
    readonly bucketName: string,
  ) {}

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

  async readStateData(): Promise<StateData | null> {
    const row = await ioc.stateDbService.findByContext(this.signalId, this.bucketName);
    return row ? row.payload : null;
  }

  async writeStateData(data: StateData, when: Date): Promise<void> {
    await ioc.stateDbService.upsert(this.signalId, this.bucketName, data, when);
  }

  dispose(): void { void 0; }  // no-op lifecycle hook
});

Table Schema

The State.schema.ts definition is the canonical example of the when-column pattern used throughout this stack:
const StateModel = new EntitySchema<IStateRow>({
  name: "state-items",
  columns: {
    id:         { type: "uuid", primary: true, generated: "uuid" },
    signalId:   { type: String },
    bucketName: { type: String },
    payload:    { type: "jsonb" },
    when:       { type: "bigint", transformer: epochTransformer },
    createDate: { type: "timestamptz", createDate: true },
    updatedDate: { type: "timestamptz", updateDate: true },
  },
  indices: [
    { name: "state_items_uq", columns: ["signalId", "bucketName"], unique: true },
  ],
});

Column Reference

signalId
string
required
Signal identifier. Part of the unique index.
bucketName
string
required
State bucket name within the signal scope. Part of the unique index.
payload
StateData (jsonb)
required
Arbitrary state data blob. StateData is the opaque type exported by backtest-kit. Returns null when no state has been written yet.
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.

Session Adapter

The Session adapter stores exactly one session record per running strategy instance. Like Recent, it includes symbol and backtest in its key so that two symbols running the same strategy on the same exchange in the same frame never share a session row.
The Session schema comment in source code (Session.schema.ts) notes the historical reason for including symbol and backtest in the uniqueness key: “without them two symbols running the same strategy shared one record and restored each other’s session state after a restart.”

Context Key and Table

FieldTypeRole
strategyNamestringStrategy identifier
exchangeNamestringExchange identifier
frameNamestringFrame identifier
symbolstringTrading pair
backtestbooleanExecution mode flag
Table: session-items — unique index session_items_uq on (strategyName, exchangeName, frameName, symbol, backtest)

Registration

PersistSessionAdapter.usePersistSessionAdapter(class implements IPersistSessionInstance {
  constructor(
    readonly strategyName: string,
    readonly exchangeName: string,
    readonly frameName: string,
    readonly symbol: string,
    readonly backtest: boolean,
  ) {}

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

  async readSessionData(): Promise<SessionData | null> {
    const row = await ioc.sessionDbService.findByContext(
      this.strategyName, this.exchangeName, this.frameName, this.symbol, this.backtest
    );
    return row ? row.payload : null;
  }

  async writeSessionData(data: SessionData, when: Date): Promise<void> {
    await ioc.sessionDbService.upsert(
      this.strategyName, this.exchangeName, this.frameName,
      this.symbol, this.backtest, data, when
    );
  }

  dispose(): void { void 0; }  // no-op lifecycle hook
});

Table Schema

const SessionModel = new EntitySchema<ISessionRow>({
  name: "session-items",
  columns: {
    id:           { type: "uuid", primary: true, generated: "uuid" },
    strategyName: { type: String },
    exchangeName: { type: String },
    frameName:    { type: String },
    symbol:       { type: String },
    backtest:     { type: "boolean" },
    payload:      { type: "jsonb" },
    when:         { type: "bigint", transformer: epochTransformer },
    createDate:   { type: "timestamptz", createDate: true },
    updatedDate:  { type: "timestamptz", updateDate: true },
  },
  indices: [
    {
      name: "session_items_uq",
      columns: ["strategyName", "exchangeName", "frameName", "symbol", "backtest"],
      unique: true,
    },
  ],
});

SessionDbService.upsert — Atomic Write

public upsert = async (
  strategyName: string,
  exchangeName: string,
  frameName: string,
  symbol: string,
  backtest: boolean,
  payload: SessionData,
  when: Date,
): Promise<void> => {
  const repo = await this.repo<ISessionRow>();
  const { raw } = await repo
    .createQueryBuilder()
    .insert()
    .values({ strategyName, exchangeName, frameName, symbol, backtest, payload, when: when.getTime() })
    .orUpdate(["payload", "when"], ["strategyName", "exchangeName", "frameName", "symbol", "backtest"])
    .returning("*")
    .execute();
  const result = raw[0] as ISessionRow;
  await this.sessionCacheService.setSessionId(result);
};

Column Reference

strategyName
string
required
Strategy identifier. Part of the unique index.
exchangeName
string
required
Exchange identifier. Part of the unique index.
frameName
string
required
Frame identifier. Part of the unique index.
symbol
string
required
Trading pair. Part of the unique index — prevents cross-symbol state pollution.
backtest
boolean
required
Execution mode flag. Part of the unique index — isolates backtest sessions from live sessions.
payload
SessionData (jsonb)
required
Session data blob. SessionData is the opaque type exported by backtest-kit. Returns null when no session has been written yet.
when
bigint / number
required
Logical simulation timestamp at write time, epoch milliseconds.

Comparison: Recent vs. State vs. Session

  • Constructor fields: symbol, strategyName, exchangeName, frameName, backtest
  • Table: recent-items
  • Payload type: IPublicSignalRow
  • dispose(): n/a (not in interface)
  • Key field count: 5
Recent and Session have identical constructor field counts (5) and share the same backtest mode flag, but their key field order differs. Recent leads with symbol; Session leads with strategyName. Both exist to track one record per unique running context — Recent for the last published signal, Session for the running strategy instance’s internal state.

Build docs developers (and LLMs) love