Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-ollama-crontab/llms.txt

Use this file to discover all available pages before exploring further.

In live mode, backtest-ollama-crontab operates as a continuously running signal-enrichment pipeline. A 15-minute crontab polls the configured Telegram channel on each tick, passes newly discovered messages through the parser and RiskOutline LLM filter, and writes the resulting screen-items rows to MongoDB. From that point, getSignal can query those enriched rows on every candle and return actionable signals to whatever execution layer is wired up downstream.
This repository is a demonstration and research tool. It does not place real orders. The strategy’s getSignal function returns signal objects, but live order execution requires additional integration with a broker or exchange API beyond what is implemented here. Always validate the full pipeline using paper trading before connecting any real capital.

Starting Live Mode

From the repository root, start the live pipeline with the January 2026 strategy entry point:
npm start -- --live --entry ./content/jan_2026.strategy/jan_2026.strategy.ts
The --live flag instructs backtest-kit to use the system clock instead of a simulated frame. The --entry flag loads the strategy module, which registers the exchange schema, the cron handlers, and getSignal.

The Live Crontab Loop

The live polling loop is declared in jan_2026.strategy.ts using Cron.register with a "15m" interval:
Cron.register({
  name: "live-fetch-data",
  handler: async (symbol, when, backtest) => {
    if (backtest) {
      return;
    }
    console.log(`Fetching live data symbol=${symbol} when=${when}`);
    await core.crawlerMainService.crawlLiveFrame(when);
  },
  interval: "15m",
});
Every 15 minutes, the following sequence executes:
1

Cron fires

Cron.register("live-fetch-data", ...) fires on the "15m" schedule. The backtest flag is false in live mode, so the handler proceeds.
2

Telegram channel polled

core.crawlerMainService.crawlLiveFrame(when) connects to the configured Telegram channel via MTProto and fetches all messages published today up to the current timestamp.
3

Parser items upserted

New messages are upserted into the parser-items MongoDB collection. The upsert is idempotent — messages seen in a previous poll cycle are silently skipped; only genuinely new rows are inserted.
4

Signal job triggered

signalJobSubject.next() fires, waking up SignalJobService.run().
5

RiskOutline processes unvisited rows

SignalJobService iterates every parser-items row that has not yet been processed. For each one, SignalLogicService invokes the RiskOutline: the LLM receives the parsed signal fields plus computed candle metrics and responds with a structured verdict.
6

Results written to screen-items

The LLM verdict — riskAction: "skip" | "follow", riskSureLevel, riskConfidence, riskDescription, riskReasoning — is written to the screen-items collection alongside the parsed trade parameters (direction, entryFrom, entryTo, targets, stoploss).
7

getSignal queries enriched rows

On each subsequent 1-minute candle tick, getSignal(symbol, when, currentPrice) queries screen-items for signals from the last 4 hours. Any signal with riskAction === "follow" whose entry zone brackets the current price is returned as an actionable trade signal.

Exchange Module

Live mode loads modules/live.module.ts, which registers the same ccxt.binance spot exchange schema used in the backtest — OHLCV fetching, order book access, and tick-aware price/quantity formatting — but without a frame or backtest configuration. There is no addFrameSchema call in the live module; backtest-kit drives execution from the system clock.
setConfig({
  CC_MAX_STOPLOSS_DISTANCE_PERCENT: 100,
});
The live module sets CC_MAX_STOPLOSS_DISTANCE_PERCENT: 100 (note: CC_BREAKEVEN_THRESHOLD is only set in the backtest module, so the default applies in live mode). All candle data is fetched from the Binance public API in real time on each tick.

Paper Trading Mode

Before committing to live signal monitoring, run the pipeline in paper trading mode. Paper trading simulates position open and close against real-time Binance prices without placing real orders, giving you a realistic view of signal quality and system behaviour:
npm start -- --paper --entry ./content/jan_2026.strategy/jan_2026.strategy.ts
Paper mode uses modules/paper.module.ts, which registers the identical ccxt Binance exchange schema. Positions are tracked internally by backtest-kit and PnL is computed from live candles, but no orders are sent to any exchange.
Always run a backtest first to confirm the full pipeline — crawler, parser, RiskOutline, and getSignal — is working correctly against historical data before switching to live or paper mode. See the Backtest guide for the step-by-step instructions and January 2026 reference results.

Session Requirements

The Telegram MTProto crawler requires an authenticated session file to connect to the channel. Before running in live or paper mode, ensure session.txt is present in the strategy’s working directory:
content/jan_2026.strategy/session.txt
To generate this file, authenticate once from the packages/main workspace:
cd ./packages/main
npm run auth
A QR code is printed in the terminal. Scan it with the Telegram mobile app (Settings → Devices → Link Desktop Device). On success, session.txt is written in that directory. Copy it into the strategy folder:
cp packages/main/session.txt content/jan_2026.strategy/session.txt
Without this file, crawlLiveFrame will fail to connect and no new signals will be ingested.

Monitoring Active Positions

listenActivePing fires on every candle tick for each open position, logging four runtime metrics through the backtest-kit log system:
listenActivePing(async ({ symbol, data, currentPrice }) => {
  const peakProfitDistance = await getPositionHighestProfitDistancePnlCost(symbol);
  const peakMaxDrawdown = await getPositionHighestMaxDrawdownPnlCost(symbol);
  const currentPnl = await getPositionPnlCost(symbol);
  Log.info("position active", {
    symbol,
    signalId: data.id,
    priceOpen: data.priceOpen,
    takeProfit: data.priceTakeProfit,
    stopLoss: data.priceStopLoss,
    currentPrice,
    peakProfitDistance,
    peakMaxDrawdown,
    currentPnl,
  });
});
  • peakProfitDistance — the highest favourable PnL cost reached since the position was opened.
  • peakMaxDrawdown — the deepest adverse move recorded against the position since open.
  • currentPnl — the current unrealised PnL cost at the close of the last candle.
These log entries are written via Log.info and are accessible through the backtest-kit log viewer. In live mode they provide a continuous per-position health check on every 1-minute candle.

Build docs developers (and LLMs) love