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.

The packages/core library exposes several services injected via di-kit. They are accessed through the global core object (typed via globalThis.core in the root tsconfig.json paths). Each service is a singleton — instantiated once by the DI container and reused for the lifetime of the process. The container is initialised at startup when the packages/core/src/index.ts entry point is imported.

Dependency Injection

Services are registered and resolved using di-kit, exposed through the three functions exported by packages/core/src/lib/core/di.ts:
import { createActivator } from "di-kit";

export const { init, inject, provide } = createActivator("pro");
provide(token, factory)
function
Registers a service factory under a Symbol token. The factory is called lazily on first inject() call and the result is cached as a singleton.
inject<T>(token)
function
Resolves and returns the singleton instance registered under token. Throws if the token has not been registered. Type parameter T narrows the return type.
init()
function
Forces eager initialisation of all registered services. Called once at application startup.
All service tokens are defined in packages/core/src/lib/core/types.ts and grouped into the TYPES registry (see TYPES Registry below).

CrawlerMainService

CrawlerMainService is the entry point for the data-ingestion half of the pipeline. It is called by cron triggers and is responsible for driving CrawlerService to scrape Telegram channel data and then emitting signalJobSubject to kick off signal processing.
class CrawlerMainService {
  crawlLiveFrame(when: Date): Promise<void>
  crawlBacktestFrame(when: Date): Promise<void>
}

crawlLiveFrame

when
Date
required
The reference timestamp for the crawl, typically the current wall-clock time supplied by the cron trigger. Converted to a momentStamp integer via getMomentStamp(when) and passed to CrawlerService.crawlDay().
Crawls a single day’s worth of messages for the current moment, then emits signalJobSubject to trigger the signal job. If the runtime mode is "backtest", the method returns immediately without crawling — backtest data is sourced separately via crawlBacktestFrame.
public crawlLiveFrame = async (when: Date) => {
  const mode = await getMode();
  if (mode === "backtest") return;
  const stamp = getMomentStamp(when);
  await this.crawlerService.crawlDay(stamp);
  await signalJobSubject.next();
};

crawlBacktestFrame

when
Date
required
The timestamp provided by the backtest runner. The actual date range is read from the frame schema via listFrameSchema()when is used only for logging context.
Resolves the active backtest frame from listFrameSchema(), converts startDate and endDate to momentStamp integers, and calls CrawlerService.crawlRange(fromStamp, toStamp) to bulk-ingest the full frame. Then emits signalJobSubject to process the ingested rows.
public crawlBacktestFrame = async (when: Date) => {
  const { frameName } = await getContext();
  const frameList = await listFrameSchema();
  const { startDate, endDate } = frameList.find(
    (frame) => frame.frameName === frameName
  );
  const fromStamp = getMomentStamp(startDate);
  const toStamp   = getMomentStamp(endDate);
  await this.crawlerService.crawlRange(fromStamp, toStamp);
  await signalJobSubject.next();
};

SignalMainService

SignalMainService provides read-only query access to the pipeline’s two collections for use by downstream consumers (e.g., strategy evaluation code in backtest-kit).
class SignalMainService {
  getLast4HourSignal(symbol: string, when: Date): Promise<IScreenRow | null>
  getLast4HourScreen(symbol: string, when: Date): Promise<IParserRow | null>
}

getLast4HourSignal

symbol
string
required
Trading symbol with USDT suffix (e.g., "BTCUSDT").
when
Date
required
The reference timestamp. The method looks back exactly 4 hours from this point.
Delegates to ScreenDbService.findLast4HourRow(symbol, when). Returns the most recent screen-items document for the symbol published within the 4-hour window [when − 4h, when], sorted by publishedAt DESC. Returns null if no matching document exists.
public getLast4HourSignal = async (
  symbol: string,
  when: Date
): Promise<IScreenRow | null> => {
  return await this.screenDbService.findLast4HourRow(symbol, when);
};

getLast4HourScreen

symbol
string
required
Trading symbol with USDT suffix.
when
Date
required
The reference timestamp. The method looks back exactly 4 hours from this point.
Delegates to ParserDbService.findLast4HourRow(symbol, when). Returns the most recent parser-items document (regardless of visited state) for the symbol within the 4-hour window. Returns null if none found. Useful for checking whether a raw signal exists even before LLM processing has completed.
public getLast4HourScreen = async (
  symbol: string,
  when: Date
): Promise<IParserRow | null> => {
  return await this.parserDbService.findLast4HourRow(symbol, when);
};

SignalJobService

SignalJobService manages the lifecycle of the background signal-processing loop. It subscribes to the signalJobSubject event and processes all unvisited parser-items rows whenever the subject emits.
class SignalJobService {
  enable(): () => void   // singleshot — returns the unsubscribe function
  disable(): void
}

enable

Subscribes SignalJobService.run() to signalJobSubject. Decorated with singleshot from functools-kit, which means calling enable() a second time before calling disable() returns the same subscription handle without creating a second subscriber. Returns a composed unsubscribe function. Calling it unsubscribes from signalJobSubject and resets the singleshot cache so enable() can be called again later.
public enable = singleshot(() => {
  const unJob    = signalJobSubject.subscribe(this.run);
  const unEnable = () => this.enable.clear();
  return compose(unJob, unEnable);
});

disable

Calls the unsubscribe function returned by the last enable() invocation. Safe to call even if enable() was never called — the hasValue() guard prevents a no-op from throwing.
public disable = () => {
  if (this.enable.hasValue()) {
    const lastSubscription = this.enable();
    lastSubscription();
    return;
  }
};

Internal run (queue semantics)

The private run method is wrapped with queued from functools-kit, which serialises concurrent invocations into a FIFO queue. This prevents overlapping LLM calls when signalJobSubject emits faster than the LLM responds. In live mode run calls parserDbService.findAllByVisited(false) and processes each pending row sequentially. In backtest mode it resolves the current frame’s date range and uses parserDbService.findAllByPublishedAt(startDate, endDate) instead.
Both modes perform the same deduplication check before calling the LLM: screenDbService.findByParserItem(row.id). If a screen-items document already exists, the row is skipped even if visited is still false.

SignalLogicService

SignalLogicService contains the single business-logic step that bridges the raw parser data and the LLM output.
class SignalLogicService {
  execute(row: IParserRow): Promise<IScreenDto>
}

execute

row
IParserRow
required
A fully-populated parser-items document as returned by ParserDbService.
Calls json<RiskOutlineContract, Args>(OutlineName.RiskOutline, symbol, direction, targets, stoploss) from agent-swarm-kit. The json() function routes the call through the registered RiskOutline outline, runs the Ollama completion, and validates the response against RiskOutlineFormat. If isValid is false, execute throws with the validation error message. On success, maps the five RiskOutlineContract fields onto IScreenDto. The actual LLM call is delegated to the module-level RUN_OUTLINE_FN helper (which wraps json() from agent-swarm-kit):
public execute = async (row: IParserRow): Promise<IScreenDto> => {
  const outline = await RUN_OUTLINE_FN(row);
  return {
    parserItemId:    row.id,
    channel:         row.channel,
    source:          row.source,
    publishedAt:     row.publishedAt,
    symbol:          row.symbol,
    direction:       row.direction,
    entryFrom:       row.entry.from,
    entryTo:         row.entry.to,
    targets:         row.targets,
    stoploss:        row.stoploss,
    riskSureLevel:   outline.sure_level,
    riskConfidence:  outline.confidence,
    riskAction:      outline.action,
    riskDescription: outline.description,
    riskReasoning:   outline.reasoning,
    note:            row.note,
    content:         row.content,
  };
};

CrawlerService

CrawlerService is the lower-level crawling primitive that fetches raw Telegram channel data and persists it to parser-items. Both methods accept integer momentStamp values — a compact day-level timestamp produced by the get-moment-stamp library (getMomentStamp(date)number, fromMomentStamp(stamp)Date).
class CrawlerService {
  crawlDay(stamp: number): Promise<ScreenItem[]>
  crawlRange(fromStamp: number, toStamp: number): Promise<ScreenItem[]>
}

crawlDay

stamp
number
required
A momentStamp integer representing a single calendar day. Internally calls crawlRange(stamp, stamp).
Convenience wrapper around crawlRange for single-day crawls. Returns the flat array of raw screen items produced for that day.

crawlRange

fromStamp
number
required
Start of the date range (inclusive), as a momentStamp integer.
toStamp
number
required
End of the date range (inclusive), as a momentStamp integer.
Iterates day-by-day from fromStamp to toStamp, calls CryptoYodaScreenService.screenDay(day) for each date, and collects all results via Promise.all. For each returned message with a non-null data field and type === "crypto_yoda_channel", it calls parserDbService.create() to upsert a parser-items document. Messages with data === null are logged and skipped. Returns the full flat list of raw screen items (both stored and skipped).
// Crawl a backtest frame spanning multiple days
const items = await crawlerService.crawlRange(
  getMomentStamp(new Date("2024-01-01")),
  getMomentStamp(new Date("2024-01-31")),
);
crawlRange fans out all per-day scraping tasks concurrently via Promise.all, so a 30-day backtest frame is fetched in parallel rather than sequentially.

TYPES Registry

All DI tokens are defined in packages/core/src/lib/core/types.ts and exported as the TYPES object. Pass these Symbols to inject<T>() to resolve the corresponding singleton:
TokenService class
TYPES.loggerServiceLoggerService — structured console logger
TYPES.scraperServiceScraperService — low-level HTTP / Telegram scraper
TYPES.crawlerServiceCrawlerService — day/range crawl orchestrator
TYPES.parserServiceParserService — signal extraction from raw messages
TYPES.parserDbServiceParserDbService — CRUD for parser-items collection
TYPES.screenDbServiceScreenDbService — CRUD for screen-items collection
TYPES.cryptoYodaScreenServiceCryptoYodaScreenService — channel-specific screen adapter
TYPES.signalJobServiceSignalJobService — LLM processing job lifecycle
TYPES.signalLogicServiceSignalLogicService — single-row LLM enrichment logic
TYPES.crawlerMainServiceCrawlerMainService — top-level crawl entry point
TYPES.signalMainServiceSignalMainService — top-level signal query entry point
import { inject } from "packages/core/src/lib/core/di";
import { TYPES } from "packages/core/src/lib/core/types";
import SignalMainService from "packages/core/src/lib/services/main/SignalMainService";

// Resolve the singleton inside any service or handler
const signalMainService = inject<SignalMainService>(TYPES.signalMainService);
const screen = await signalMainService.getLast4HourSignal("BTCUSDT", new Date());

Build docs developers (and LLMs) love