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.
The Signal adapter stores the active signal state for each trading context. A signal state is a snapshot of what the strategy most recently decided for a given (symbol, strategyName, exchangeName) triplet — open position direction, entry price, stop levels, and any other fields your ISignalRow carries. The adapter ensures that after a process restart the engine can reload exactly the signal it left off with, without re-running the full strategy history.
The closely related Schedule adapter covers the same context key but holds a pending scheduled signal — a deferred action the engine plans to execute on the next bar tick — rather than the current live state.
Signal adapter
IPersistSignalInstance implementation
PersistSignalAdapter.usePersistSignalAdapter(class implements IPersistSignalInstance {
constructor(
readonly symbol: string,
readonly strategyName: string,
readonly exchangeName: string,
) {}
async waitForInit(initial: boolean) {
if (!initial) return;
await waitForInfra();
}
async readSignalData(): Promise<ISignalRow | null> {
const row = await ioc.signalDbService.findByContext(
this.symbol, this.strategyName, this.exchangeName,
);
return row ? row.payload : null;
}
async writeSignalData(signalRow: ISignalRow | null): Promise<void> {
await ioc.signalDbService.upsert(
this.symbol, this.strategyName, this.exchangeName, signalRow,
);
}
});
Context key: (symbol, strategyName, exchangeName).
Signal Mongoose schema
From src/schema/Signal.schema.ts:
import mongoose, { Document, Schema } from "mongoose";
import { ISignalRow } from "backtest-kit";
interface ISignalDto {
symbol: string;
strategyName: string;
exchangeName: string;
payload: ISignalRow;
}
interface SignalDocument extends ISignalDto, Document {}
interface ISignalRowDoc extends ISignalDto {
id: string;
createDate: Date;
updatedDate: Date;
}
const SignalSchema: Schema<SignalDocument> = new Schema(
{
symbol: { type: String, required: true, index: true },
strategyName: { type: String, required: true, index: true },
exchangeName: { type: String, required: true, index: true },
payload: { type: Schema.Types.Mixed, required: true },
},
{ timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);
SignalSchema.index(
{ symbol: 1, strategyName: 1, exchangeName: 1 },
{ unique: true }
);
const SignalModel = mongoose.model<SignalDocument>("signal-items", SignalSchema);
minimize: false is required so that Mongoose does not strip empty objects from payload, preserving the full shape of sparse signal objects.
SignalDbService — atomic upsert and Redis-cached read
upsert uses $set with upsert: true so that any payload value — including null (signal cleared) — is accepted:
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()) as unknown as ISignalRowDoc;
await this.signalCacheService.setSignalId(result);
};
findByContext checks the Redis cache first. On a hit it resolves the _id and calls findById (an O(1) indexed lookup). On a miss — or if Redis is temporarily unavailable — it falls back to a full MongoDB filter query, then primes the cache:
public findByContext = async (
symbol: string,
strategyName: string,
exchangeName: string,
): Promise<ISignalRowDoc | null> => {
try {
const cachedId = await this.signalCacheService.getSignalId(
symbol, strategyName, exchangeName,
);
if (cachedId) {
return await super.findById(cachedId) as ISignalRowDoc;
}
} catch {
void 0;
}
const result = await super.findByFilter({
symbol, strategyName, exchangeName,
}) as ISignalRowDoc | null;
if (result) {
await this.signalCacheService.setSignalId(result);
}
return result;
};
The Signal adapter has no when: Date column. Signal state represents the current live state of the strategy — it is not filtered by time during backtests and carries no look-ahead bias concern.
Schedule adapter
The Schedule adapter shares the exact same context key and Redis-cache pattern as Signal, but stores IScheduledSignalRow | null — the deferred action the engine will execute on the next tick — in the schedule-items collection.
IPersistScheduleInstance implementation
PersistScheduleAdapter.usePersistScheduleAdapter(class implements IPersistScheduleInstance {
constructor(
readonly symbol: string,
readonly strategyName: string,
readonly exchangeName: string,
) {}
async waitForInit(initial: boolean) {
if (!initial) return;
await waitForInfra();
}
async readScheduleData(): Promise<IScheduledSignalRow | null> {
const row = await ioc.scheduleDbService.findByContext(
this.symbol, this.strategyName, this.exchangeName,
);
return row ? row.payload : null;
}
async writeScheduleData(scheduleRow: IScheduledSignalRow | null): Promise<void> {
await ioc.scheduleDbService.upsert(
this.symbol, this.strategyName, this.exchangeName, scheduleRow,
);
}
});
Schedule Mongoose schema
From src/schema/Schedule.schema.ts:
import mongoose, { Document, Schema } from "mongoose";
import { IScheduledSignalRow } from "backtest-kit";
interface IScheduleDto {
symbol: string;
strategyName: string;
exchangeName: string;
payload: IScheduledSignalRow;
}
interface ScheduleDocument extends IScheduleDto, Document {}
interface IScheduleRow extends IScheduleDto {
id: string;
createDate: Date;
updatedDate: Date;
}
const ScheduleSchema: Schema<ScheduleDocument> = new Schema(
{
symbol: { type: String, required: true, index: true },
strategyName: { type: String, required: true, index: true },
exchangeName: { type: String, required: true, index: true },
payload: { type: Schema.Types.Mixed, required: true },
},
{ timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);
ScheduleSchema.index(
{ symbol: 1, strategyName: 1, exchangeName: 1 },
{ unique: true }
);
const ScheduleModel = mongoose.model<ScheduleDocument>("schedule-items", ScheduleSchema);
The schema is structurally identical to SignalSchema — only the Mongoose model name and the payload type differ. Setting payload to null via writeScheduleData(null) is the standard way to cancel a pending schedule.