Skip to main content

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.

Live mode keeps the pipeline running continuously. A 15-minute cron job polls the configured Telegram channel, pushes new messages through the Ollama LLM risk filter, and feeds the resulting verdicts into the strategy’s getSignal function. Positions are opened only when the LLM votes "follow" and the current market price falls inside the channel’s published entry zone — the same two-gate logic used in the backtest, now evaluated against live market data.

Prerequisites

Packages built

Run npm run build:x (macOS/Linux) or npm run build:win (Windows). Each workspace package must emit a build/index.cjs before the CLI can load the compiled core and main bundles.

session.txt in the strategy folder

Authenticate once and copy the session file into your strategy directory:
cd ./packages/main && npm run auth
cp packages/main/session.txt \
   content/jan_2026.strategy/session.txt
The strategy reads this file at runtime to subscribe to the channel without re-authenticating on every start.

MongoDB + Redis running

parser-items (raw Telegram messages) and screen-items (LLM verdicts) are both stored in MongoDB. Redis backs the distributed queue and caching layer. Both services must be reachable before npm start is invoked.

Ollama running with gpt-oss

The risk outline calls the Ollama HTTP API at CC_OLLAMA_URL (default http://localhost:11434). Pull the model if you have not already:
ollama pull gpt-oss

Live Mode Command

npm start -- --live --entry ./content/jan_2026.strategy/jan_2026.strategy.ts
FlagTypeDescription
--livebooleanActivates live mode. packages/main/src/main/live.ts checks this flag and exits early if it is absent.
--entryboolean (presence flag)Tells @backtest-kit/cli to load the path that follows it as the strategy entry point. The file is loaded at runtime and never bundled into the workspace packages.
There is no --cache flag in live mode — candles are fetched on demand from the exchange as each price tick arrives.

What Happens Step by Step

1

live.ts bootstraps and waits for readiness

@backtest-kit/cli resolves packages/main/src/main/live.ts and calls main(). The function checks that both --entry and --live flags are present, then calls:
await waitForReady(false);
Passing false puts the framework into live mode. waitForReady blocks until MongoDB, Redis, and the Ollama connection are all healthy.
2

Strategy and exchange schemas are resolved

After the infrastructure is ready, the runner reads the schemas registered by the strategy entry file:
const [strategySchema] = await listStrategySchema();
const [exchangeSchema] = await listExchangeSchema();
Live mode does not require a frame schema — there is no fixed date range. If either schema is missing the runner throws immediately with a descriptive error.
3

Live.background is called for each symbol

A live worker is started for every symbol in CC_SYMBOL_LIST:
for (const symbol of CC_SYMBOL_LIST) {
  Live.background(symbol, {
    exchangeName: exchangeSchema.exchangeName,
    strategyName: strategySchema.strategyName,
  });
}
Each worker streams live 1-minute candles from the exchange, calling getSignal(symbol, when, currentPrice) on each new close.
4

live-fetch-data cron fires every 15 minutes

The strategy registers a recurring Cron handler named "live-fetch-data":
Cron.register({
  name: "live-fetch-data",
  handler: async (symbol, when, backtest) => {
    if (backtest) {
      return;
    }
    await core.crawlerMainService.crawlLiveFrame(when);
  },
  interval: "15m",
});
interval: "15m" schedules the handler to fire every 15 minutes across all running symbol workers. crawlLiveFrame(when) checks the current mode (skips silently in backtest), then calls crawlerService.crawlDay(stamp) to scrape today’s messages from the Telegram channel and upsert them into parser-items.
5

signalJobSubject triggers LLM screening

After each crawl completes, crawlLiveFrame emits:
await signalJobSubject.next();
SignalJobService wakes up and processes every unvisited row in parser-items. For each row it runs the LLM risk outline in a live execution context (using the current wall-clock minute as when), producing a structured verdict stored in screen-items:
  • riskAction"follow" or "skip"
  • riskSureLevel — accumulation confidence ("low""high")
  • riskConfidence — data reliability ("reliable" | "not_reliable")
  • riskDescription — 2–3 sentence human-readable verdict
  • riskReasoning — step-by-step rule application log
Rows are marked visited after screening so they are never processed twice.
6

getSignal checks the latest verdict on each price tick

On every live price tick backtest-kit calls getSignal. The function queries the most recent LLM-screened record from screen-items for that symbol using a 4-hour look-back window:
const signal = await core.signalMainService.getLast4HourSignal(symbol, when);
Signals with riskAction === "skip" are rejected. For surviving signals the function additionally checks that the current 1m close price is between signal.entryFrom and signal.entryTo before returning a position:
return {
  id:                  signal.id,
  position:            signal.direction,    // "long" | "short"
  priceStopLoss:       signal.stoploss,
  priceTakeProfit:     signal.targets[2],   // third target (T3)
  minuteEstimatedTime: Infinity,
  note:                JSON.stringify(info, null, 2),
};

Monitoring Active Positions

The strategy registers a listenActivePing callback that fires on every tick while a position is open. It logs the symbol, open price, take-profit and stop-loss levels, peak profit distance, maximum drawdown, and the current unrealised PnL:
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,
  });
});
Strategy-level errors are captured by listenError and written to the backtest-kit log:
listenError((error) => {
  console.log(error);
  Log.debug("error", {
    error:   errorData(error),
    message: getErrorMessage(error),
  });
});
Log.info() and Log.debug() messages appear both in the terminal output and in the backtest-kit UI (if you add --ui to the start command). Use the UI’s log panel to monitor signal verdicts, position PnL ticks, and error details without tailing the terminal.
Live mode calls the real exchange API configured in your exchange schema. The default backtest.module.ts uses a ccxt.binance spot connection. Ensure your exchange schema is pointed at a paper-trading or sandbox account before running live with real funds. Order book fetching is explicitly disabled in backtest contexts — implement it according to your exchange’s requirements if you need it in live mode.

Build docs developers (and LLMs) love