Strategy files in this monorepo are intentionally kept outside the compiled workspace packages. They live underDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-ollama-crontab/llms.txt
Use this file to discover all available pages before exploring further.
./content/ and are loaded at runtime by @backtest-kit/cli via the --entry flag, so you can iterate on strategy logic without rebuilding anything. The only contract a strategy file must satisfy is calling addStrategySchema(...) and, optionally, registering Cron handlers and lifecycle callbacks.
File Layout
A strategy directory typically contains three files:--entry path points directly at the *.strategy.ts file. The module file
(backtest.module.ts) is imported from inside the strategy and registers the
exchange and frame schemas needed for candle fetching and date-range bounding.
Registering a Strategy
addStrategySchema is the main entrypoint. Pass it a strategyName and a
getSignal function:
strategyName must match the value returned by listStrategySchema() in the
runner files — backtest and live runners both pick [0] from the list, so only
one schema should be registered per process.
Implementing getSignal
getSignal(symbol, when, currentPrice) is called on every price tick. It must
return a position descriptor or null to skip the tick. The complete
implementation from jan_2026.strategy.ts illustrates the recommended pattern:
Position Fields
| Field | Source | Description |
|---|---|---|
id | signal.id | Unique identifier of the screen-items document — used to correlate a live position back to the original Telegram signal |
position | signal.direction | "long" or "short" as parsed from the channel message |
priceStopLoss | signal.stoploss | Hard stop-loss price published in the channel signal |
priceTakeProfit | signal.targets[2] | Third take-profit target (T3) — the channel typically publishes three targets; T3 offers the widest reward-to-risk ratio |
minuteEstimatedTime | Infinity | Setting this to Infinity disables the time-based exit so the position stays open until TP or SL is hit |
note | JSON.stringify(info) | Stringified JSON blob written to the trade log for post-analysis; includes the full signal data and LLM risk fields |
How getLast4HourSignal Works
core.signalMainService.getLast4HourSignal(symbol, when) delegates to
ScreenDbService.findLast4HourRow(symbol, when), which queries screen-items
for the most recent document matching symbol whose publishedAt falls within
the four hours preceding when. In backtest mode when is the simulated
candle timestamp; in live mode it is the current wall-clock minute. If no
screened signal exists in that window the method returns null and getSignal
skips the tick.
Lifecycle Callbacks
listenActivePing
listenActivePing fires on every price tick while at least one position is
open. Use it for monitoring, trailing-stop logic, or dynamic exit decisions.
The callback receives symbol, data (the original position descriptor), and
currentPrice:
listenError
listenError captures any uncaught error thrown inside the strategy. Forward
it to Log.debug so it appears in the UI log panel alongside position events:
Registering Cron Handlers
Cron.register schedules data-fetching work so that the strategy is self-
contained. The jan_2026.strategy.ts registers two complementary handlers —
one for backtest, one for live — that share the same cron infrastructure but
branch on the backtest flag:
interval makes the handler a one-shot that fires on the first
tick of each symbol worker. Adding interval: "15m" schedules recurring
execution at that cadence. Both handlers receive the same three arguments:
symbol (current worker symbol), when (current candle timestamp), and
backtest (boolean mode flag).
Registering an Exchange Schema
The exchange schema is typically registered in amodules/backtest.module.ts
file imported by the strategy. The globalThis.addExchangeSchema function
(defined in packages/core/src/func/exchange.function.ts) is a thin wrapper
around backtest-kit’s addExchangeSchema:
ccxt adapter or any custom
implementation that satisfies IExchangeSchema:
Manually Invoking the Risk Outline
globalThis.runRiskOutline (defined in packages/core/src/func/risk.function.ts)
lets you call the LLM risk filter directly — useful for one-off signal
evaluation in a REPL or a test script — without going through the full crawl
and job-queue pipeline:
runRiskOutline calls runInMockContext with backtest: true, so
candles are fetched from the local cache at the publication timestamp rather
than from the live exchange.
Signal Schema Reference
Thescreen-items document returned by getLast4HourSignal exposes the
following fields used by the strategy:
| Field | Type | Description |
|---|---|---|
id | string | MongoDB document ID |
direction | "long" | "short" | Trade direction parsed from the channel message |
entryFrom | number | Lower bound of the published entry zone |
entryTo | number | Upper bound of the published entry zone |
targets | number[] | Array of take-profit levels; [0] = T1, [1] = T2, [2] = T3 |
stoploss | number | Hard stop-loss price |
publishedAt | Date | Original Telegram message timestamp |
riskAction | "follow" | "skip" | LLM verdict |
riskSureLevel | string | Accumulation confidence: "low", "low_medium", "medium", "medium_high", or "high" |
riskConfidence | string | Data reliability: "reliable" or "not_reliable" |
riskDescription | string | 2–3 sentence verdict citing the rule applied and the metric values |
riskReasoning | string | Step-by-step plain-text log of rule evaluation |
note | string | Raw parsed signal text stored by the crawler |