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.

backtest-ollama-crontab is organised as an npm workspace monorepo. The root package.json declares two member packages (packages/core and packages/main) and owns all shared dependencies. Self-contained strategy implementations live in content/ and are loaded at runtime by @backtest-kit/cli — they are never bundled into the packages/ artifacts, which means you can add or modify strategies without rebuilding the core library. Shared exchange schema definitions for backtest, live, and paper modes live in the top-level modules/ directory so they can be imported by any strategy without duplication.

Pipeline Layers

Each incoming Telegram message travels through eight discrete layers before it either becomes an open position or is silently discarded. The table below maps each layer to its source file and its single responsibility:
LayerFileResponsibility
Crawlpackages/core/src/lib/services/core/CrawlerService.tsCalls GramJS iterMessages on the configured channel; upserts raw messages into the parser-items MongoDB collection
Parsepackages/core/src/lib/services/screen/ChannelScreenService.tsApplies regex extraction to each raw message to pull out direction, entry, targets, and stoploss; stores structured rows in parser-items
Jobpackages/core/src/lib/services/job/SignalJobService.tsSubscribes to signalJobSubject; iterates every unvisited parser-items row and passes it to SignalLogicService
Outlinepackages/core/src/logic/outline/risk.outline.tsSends the signal plus pre-computed candle metrics (avgRangePct, momentum24hPct) to Ollama; returns a zod-validated riskAction: "skip" | "follow" verdict with confidence and reasoning fields
Logicpackages/core/src/lib/services/logic/SignalLogicService.tsPure passthrough: merges the LLM verdict into the screen-items DTO and persists it
Schemapackages/core/src/schema/Screen.schema.tsDefines the final Mongoose document shape: direction, entryFrom, entryTo, targets, stoploss, riskAction, riskSureLevel, riskConfidence, riskDescription, riskReasoning
Strategycontent/jan_2026.strategy/jan_2026.strategy.tsReads screen-items via core.signalMainService.getLast4HourSignal; opens a position only when riskAction === "follow" and the live close price falls inside entryFromentryTo
CronSame file — Cron.register(...) callsinterval: "15m" drives live polling via crawlLiveFrame; no interval means a one-shot crawlBacktestFrame at startup

Monorepo Package Layout

backtest-ollama-crontab/
├── packages/
│   ├── core/          # All services, schemas, outlines — rolls up to build/index.cjs
│   └── main/          # CLI entrypoint, session QR-auth helper
├── content/
│   └── jan_2026.strategy/   # Self-contained strategy + session.txt
├── modules/
│   ├── backtest.module.ts   # ccxt/Binance exchange schema for backtest mode
│   └── live.module.ts       # ccxt/Binance exchange schema for live mode
├── docker/            # Docker Compose for MongoDB and Redis
├── scripts/
│   ├── linux/build.sh       # Iterates packages/, runs npm install + build
│   └── win/build.bat        # Windows equivalent
└── package.json       # Root workspace: declares packages/*, shared deps

packages/core

This is the heart of the system. It contains every service, Mongoose schema, LLM outline, job processor, and logic adapter. Rollup bundles the entire package into a single build/index.cjs with an accompanying types.d.ts declaration file. The root tsconfig.json maps the package name to these build artifacts, which is what makes globalThis.core type-safe across the whole workspace.

packages/main

A thin CLI entrypoint that imports four side-effect modules (session, backtest, live, paper) to register the appropriate handlers with backtest-kit, then defers to @backtest-kit/cli for argument parsing and orchestration. It also contains the standalone npm run auth QR-code session helper that writes session.txt.

content/jan_2026.strategy/

A self-contained strategy directory. It holds the strategy TypeScript file (jan_2026.strategy.ts), the copied session.txt for Telegram auth, and any strategy-specific configuration. The @backtest-kit/cli loads this file at runtime via the --entry flag — the file is never compiled into a package, so edits take effect immediately on the next run.

modules/

Shared addExchangeSchema registrations for Binance via ccxt. backtest.module.ts and live.module.ts are functionally identical in this repository (both use Binance spot with enableRateLimit: true) but are kept separate so you can substitute different exchange connectors per mode without touching the other.

Data Flow

The following steps trace the lifecycle of a single Telegram message from its appearance in the channel to an open position in the backtest engine:
1

Channel ingestion

CrawlerService calls GramJS iterMessages on the configured public channel. Each message is hashed and upserted into the parser-items MongoDB collection. Duplicate messages are skipped via the upsert key, making the crawl idempotent.
2

Regex parsing

ChannelScreenService (also known as CryptoYodaScreenService in the README) reads every new parser-items document and applies regex patterns to extract the structured signal fields: direction (LONG/SHORT), entry price zone, targets array (up to three TP levels), and stoploss price. Successfully parsed rows are written back into the same collection with the extracted fields.
3

Job dispatch

SignalJobService subscribes to the signalJobSubject reactive subject. Whenever a new parsed row becomes available, the service checks whether it has already been processed (the visited flag). Unvisited rows are passed one-by-one to SignalLogicService.
4

LLM risk evaluation (outline)

risk.outline.ts is invoked by SignalLogicService. It assembles a prompt that includes:
  • The raw signal fields (direction, entryFrom, entryTo, targets, stoploss)
  • Pre-computed candle metrics: avgRangePct (average candle range as a percentage) and momentum24hPct (24-hour price momentum)
  • Two explicit empirical rules in the system prompt: skip SHORT signals where avgRangePct < 0.07%; skip LONG signals where momentum24hPct < -1%
The assembled prompt is sent to Ollama (gpt-oss:120b). The response is parsed and validated against a Zod schema that requires riskAction: "skip" | "follow", riskSureLevel, riskConfidence, riskDescription, and riskReasoning.
5

Screen-items persistence

SignalLogicService merges the zod-validated LLM response with the original signal fields and writes the final document to the screen-items collection using the shape defined in Screen.schema.ts. The row is marked visited so it is never re-evaluated.
6

Strategy signal check

On each backtest tick (or live poll), the strategy’s getSignal callback is invoked with (symbol, when, currentPrice). It calls core.signalMainService.getLast4HourSignal(symbol, when) to fetch the most recent screen-items document for that symbol within a four-hour window.Three conditions must all pass before a position is opened:
  1. A matching signal exists in the database.
  2. signal.riskAction === "follow" — the LLM did not veto this signal.
  3. The current close price (from getClosePrice(symbol, "1m")) falls within [signal.entryFrom, signal.entryTo].
7

Position open

When all three conditions pass, getSignal returns a position descriptor:
  • position: signal.direction (LONG or SHORT)
  • priceTakeProfit: signal.targets[2] (the third, most conservative TP level)
  • priceStopLoss: signal.stoploss
  • minuteEstimatedTime: Infinity (no time-based exit)
  • note: a JSON blob containing the full signal and LLM risk metadata for audit logging
The backtest-kit engine manages the position lifecycle from open to close, firing listenActivePing callbacks on each tick and recording the final P&L.

DI Container

All services are wired together via di-factory and exposed on globalThis.core. The type of globalThis.core is declared in the root tsconfig.json using path aliases that point to the rolled-up types.d.ts from packages/core. This means every file in the workspace — including runtime-loaded strategy files in content/ — gets full IntelliSense on core.* without any explicit import.
// Example access pattern inside a strategy file
const signal = await core.signalMainService.getLast4HourSignal(symbol, when);
await core.crawlerMainService.crawlBacktestFrame(when);
The di-factory pattern means each service is a singleton constructed lazily on first access. Services declare their own dependencies through constructor injection; the container resolves the dependency graph at startup. You never call new CrawlerService() directly — you access core.crawlerMainService and the container handles the rest.
Because globalThis.core is typed via the build artifacts rather than source imports, strategy files can reference services that are not part of their own compilation unit. This is what allows content/jan_2026.strategy/jan_2026.strategy.ts to call core.signalMainService and core.crawlerMainService without importing them.

Explore Further

Crawler Guide

How CrawlerService authenticates via MTProto, handles pagination, and deduplicates messages.

Risk Outline Guide

Anatomy of the LLM prompt, the metrics packet, and the Zod response schema.

Strategy Guide

Writing your own strategy file, using Cron.register, and accessing core services.

Services Reference

API reference for SignalMainService, CrawlerMainService, SignalJobService, and SignalLogicService.

Build docs developers (and LLMs) love