Cron is a periodic and fire-once scheduler that runs in virtual time — the same time stream your strategies see in backtest mode. Rather than relying on wall-clock intervals, handlers fire on candle-interval boundaries (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit/llms.txt
Use this file to discover all available pages before exploring further.
1m, 5m, 1h, 1d, …). When multiple Backtest.background() calls run in parallel (e.g. five symbols in one process), the first symbol to reach a given boundary opens a shared slot; the others await the same promise and are released together when it settles. This means your handler is called exactly once per boundary regardless of how many symbols are running — no duplicate Telegram fetches, no double cache warm-ups.
Public API
Register jobs
Use
Cron.register() to create periodic or fire-once entries. The return value is a disposer — call it to unregister later.Enable the lifecycle bridge
Call
Cron.enable() once at startup to subscribe Cron to the engine’s four lifecycle subjects (beforeStart, idlePing, activePing, schedulePing). After this every strategy tick is automatically forwarded into Cron.Full API reference
Unique entry name. Re-registering the same name replaces the previous entry and bumps an internal generation counter — in-flight handlers from old registrations cannot pollute new ones.
When provided (
"1m", "5m", "1h", etc.), the handler fires once per interval boundary — periodic mode. When omitted, the handler fires on the very first matching tick and never again until clear() or re-register — fire-once mode.When empty or omitted, the handler fires once globally across all parallel backtests (first symbol to reach the boundary wins; others await). When non-empty, the handler fires once per whitelisted symbol per boundary — fan-out mode.
async (symbol, when, backtest) => void. symbol is the winning symbol in global mode, or the current symbol in fan-out mode. when is the aligned virtual time. backtest mirrors the execution mode of the tick that opened the slot.| Method | Description |
|---|---|
Cron.enable() | Subscribe to lifecycle subjects. Wrapped in singleshot — call once. |
Cron.disable() | Tear down subscriptions. Safe to call before enable(). |
Cron.unregister(name) | Remove a registered job by name. |
Cron.clear(symbol?) | Clear fire-once marks. With symbol → fan-out marks for that symbol only. Without → all marks. |
Cron.dispose() | Hard reset: disables + clears all entries and fire-once marks. |
Two modes: periodic vs fire-once
- Periodic
- Fire-once (warm-up)
Periodic jobs fire exactly once every time the virtual clock crosses an interval boundary. The handler is skipped for ticks that fall mid-interval.
Two scopes: global vs fan-out
- Global
- Fan-out
Without a
symbols array the handler fires once per boundary across all parallel backtests. The first symbol to arrive opens the slot; the rest wait on the same promise.Complete example
The following snippet wires three common Cron patterns — a global periodic job, a per-symbol fan-out, and a fire-once warm-up — before launching five parallel backtests.Internal coordination — singlerun semantics
Cron.enable() subscribes a single singlerun-wrapped handler to four lifecycle subjects. singlerun serialises all four streams into one queue: at most one _tick runs at a time. This prevents concurrent ticks on the same (symbol, virtual-minute) from racing to open the same slot.
Coordination keys are built as ${name}:${alignedMs}:${symbol?}:g${generation}. Parallel backtests that reach the same key share a single in-flight promise — the first opens the slot, the others await the same promise and release together. After .finally() the slot is removed so the next boundary gets a fresh promise.
A failed fire-once handler is not marked as fired. It retries on the next tick automatically. A failed periodic handler is silently logged and does not affect the next boundary — the slot clears in
.finally() regardless of success or failure.