The backtest mode replays every Telegram signal published inside a configured date range, passes each one through the local Ollama LLM risk filter, and simulates position entries and exits against real historical OHLCV candles. Because the LLM is called with market context anchored at the signal’s original publication time, there is no look-ahead bias — the verdict is identical to what a live run would have produced on that date.Documentation 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.
Prerequisites
Before running a backtest, make sure all of the following are in place.Packages built
Run
npm run build:x (macOS/Linux) or npm run build:win (Windows) from the
repo root. Each workspace package emits build/index.cjs and a rolled-up
types.d.ts that the strategy file imports at runtime.Telegram session authenticated
Run
cd ./packages/main && npm run auth, scan the QR code with the Telegram
app, then copy the resulting session.txt into your strategy folder:MongoDB + Redis running
The crawler writes raw messages to the
parser-items MongoDB collection and
the LLM job service reads from it. Redis is used for caching and distributed
locking. Both must be reachable at their default ports before npm start is
invoked.Ollama running with gpt-oss
The risk outline calls the local Ollama HTTP API at
CC_OLLAMA_URL
(default http://localhost:11434). Pull the model before your first run:Backtest Command
- Flag reference
- Environment variables
| Flag | Type | Description |
|---|---|---|
--backtest | boolean | Activates backtest mode. packages/main/src/main/backtest.ts checks this flag and exits early if it is absent. |
--entry | boolean (presence flag) | Tells @backtest-kit/cli to load the file path that follows it as the strategy entry point. The file is never bundled — it is loaded at runtime by the CLI. |
--ui | boolean | Opens the backtest-kit web UI so you can inspect the trade log, equity curve, and per-trade details in a browser. |
--cache | boolean | Pre-caches OHLCV candles for every symbol in CC_SYMBOL_LIST before the simulation starts. Uses the exchange schema’s getCandles implementation and the frame schema’s startDate / endDate to determine the download window. |
--entry value must be the path to a strategy file that calls
addStrategySchema(...), registers Cron handlers, and (optionally) imports
an exchange + frame schema module.What Happens Step by Step
backtest.ts bootstraps and waits for readiness
@backtest-kit/cli resolves packages/main/src/main/backtest.ts and calls
main(). The function first checks that both --entry and --backtest flags
are present, then calls:true puts the framework into backtest mode. The function waits
for MongoDB, Redis, and the Ollama connection to all report healthy before
returning.Strategy, exchange, and frame schemas are read
After For If any schema is missing the runner throws immediately with a descriptive
error.
waitForReady resolves, the runner reads the schemas that were
registered by the strategy entry file and the module it imports:jan_2026.strategy.ts the frame schema is registered by
content/jan_2026.strategy/modules/backtest.module.ts:Candles are pre-cached (if --cache is set)
When Candles are stored locally so that subsequent runs and the per-tick price
feed never make live exchange requests during the simulation.
--cache is present, cacheCandles() is called once for every symbol
in CC_SYMBOL_LIST using the exchange schema’s getCandles implementation
and the frame date range:Backtest.background is called for each symbol
With schemas resolved and candles cached, the runner launches a simulation
worker for each symbol:Each worker replays 1-minute candles from
startDate to endDate,
calling getSignal(symbol, when, currentPrice) on every tick.backtest-prepare-data cron fires on the first tick
The strategy file registers a one-shot Because no
Cron handler named
"backtest-prepare-data":interval is set, this handler fires once at the start of the
simulation. crawlBacktestFrame(when) reads the frame’s startDate and
endDate from the schema, calls crawlerService.crawlRange(fromStamp, toStamp)
to pull all Telegram messages for the full month, and upserts them into the
parser-items MongoDB collection.signalJobSubject triggers LLM screening
After the crawl completes, This wakes The outline fetches 1m/15m candles at that historical timestamp, computes
crawlBacktestFrame calls:SignalJobService, which iterates every unvisited parser-items
row whose publishedAt falls within the frame date range. For each row it
runs the LLM risk outline inside an execution context aligned to the signal’s
publication minute:avgRangePct and momentum24hPct, sends them to gpt-oss via Ollama,
and stores the structured verdict (riskAction, riskSureLevel,
riskConfidence, riskDescription, riskReasoning) in the screen-items
collection. Rows are marked as visited after screening.getSignal reads screen-items and returns positions
On every subsequent price tick, Signals with
backtest-kit calls the strategy’s
getSignal function. The function queries the most recent LLM-screened record
from screen-items for that symbol using a 4-hour look-back window:riskAction === "skip" are rejected immediately. For surviving
signals the function additionally confirms that the current 1m close price
lies within the channel’s published entry zone before returning a position
object:The backtest replays at historical candle prices — Ollama is called with
the 1m/15m candle context that existed at the signal’s original publication
time. Computed metrics (
avgRangePct, momentum24hPct) reflect conditions
24 hours before publication, so there is no forward-looking information in
the LLM prompt.Controlling Which Symbols Are Processed
TheCC_SYMBOL_LIST environment variable controls every symbol processed by
the crawler, the LLM screener, and the simulation workers. Set it in your .env
file as a comma-separated list of exchange pair identifiers:
packages/main/src/config/params.ts) is:
gpt-oss:120b locally.
January 2026 Results
The table below shows the improvement the LLM gate produced on the same parsed-signal set:| Metric | Without Ollama | With Ollama | Δ |
|---|---|---|---|
| Total trades | 22 | 17 | −5 skipped |
| Total PNL | +52.22% | +68.90% | +16.68 pp |
| Winrate | 68% | 82% | +14 pp |
| Sharpe Ratio | +0.309 | +0.512 | +0.203 |
| Profit factor | 2.73 | 6.37 | +3.64 |
| Expectancy / trade | +$2.37 | +$4.05 | +$1.68 |