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.

screen-items is the final output collection of the pipeline. Each document represents a parsed trading signal that has been evaluated by the LLM risk filter. The collection is written exclusively by ScreenDbService.create() after SignalLogicService successfully runs the RiskOutline outline and all five Zod validators pass. Documents are immutable after creation — re-running the pipeline for the same parserItemId is a no-op due to the unique index.

Schema Definition

Full IScreenDto interface and ScreenSchema from packages/core/src/schema/Screen.schema.ts:
import mongoose, { Document, Schema } from "mongoose";

interface IScreenDto {
  parserItemId: string;
  channel:      string;
  source:       string;
  publishedAt:  Date;
  symbol:       string;

  direction: "long" | "short";
  entryFrom: number;
  entryTo:   number;
  targets:   number[];
  stoploss:  number;

  riskSureLevel:   "low" | "low_medium" | "medium" | "medium_high" | "high";
  riskConfidence:  "reliable" | "not_reliable";
  riskAction:      "skip" | "follow";
  riskDescription: string;
  riskReasoning:   string;

  note:    string;
  content: unknown;
}

interface ScreenDocument extends IScreenDto, Document {}

interface IScreenRow extends IScreenDto {
  id:          string;
  createDate:  Date;
  updatedDate: Date;
}

const DIRECTION_ENUM        = ["long", "short"] as const;
const RISK_SURE_LEVEL_ENUM  = ["low", "low_medium", "medium", "medium_high", "high"] as const;
const RISK_CONFIDENCE_ENUM  = ["reliable", "not_reliable"] as const;
const RISK_ACTION_ENUM      = ["skip", "follow"] as const;

const ScreenSchema: Schema<ScreenDocument> = new Schema(
  {
    parserItemId: { type: String, required: true },
    channel:      { type: String, required: true, index: true },
    source:       { type: String, required: true, index: true },
    publishedAt:  { type: Date,   required: true, index: true },
    symbol:       { type: String, required: true, index: true },

    direction: { type: String, required: true, enum: DIRECTION_ENUM },
    entryFrom: { type: Number, required: true },
    entryTo:   { type: Number, required: true },
    targets:   { type: [Number], required: true },
    stoploss:  { type: Number, required: true },

    riskSureLevel:   { type: String, required: true, enum: RISK_SURE_LEVEL_ENUM, index: true },
    riskConfidence:  { type: String, required: true, enum: RISK_CONFIDENCE_ENUM, index: true },
    riskAction:      { type: String, required: true, enum: RISK_ACTION_ENUM, index: true },
    riskDescription: { type: String, required: true },
    riskReasoning:   { type: String, required: true },

    note:    { type: String, required: true },
    content: { type: Schema.Types.Mixed, required: true },
  },
  { timestamps: { createdAt: "createDate", updatedAt: "updatedDate" }, minimize: false }
);

ScreenSchema.index({ parserItemId: 1 }, { unique: true });
ScreenSchema.index({ symbol: 1, publishedAt: -1 });

const ScreenModel = mongoose.model<ScreenDocument>("screen-items", ScreenSchema);

export { ScreenModel, IScreenDto, IScreenRow };

Fields

Identity

parserItemId
string
required
Foreign-key reference to the originating parser-items document (_id serialised as a string). Protected by a unique index — inserting a second screen-items document for the same parserItemId is silently rejected via $setOnInsert upsert semantics.
channel
string
required
Telegram channel identifier from which the signal was originally scraped (e.g., "crypto_yoda_channel"). Indexed for channel-level filtering.
source
string
required
Source channel name. In the current crawler implementation source is set equal to channel. Indexed separately to allow future multi-source fan-out.
publishedAt
Date
required
The timestamp at which the signal was published on Telegram. Used as the time axis for all 4-hour window queries. Indexed.
symbol
string
required
Trading symbol with USDT suffix (e.g., "BTCUSDT", "SOLUSDT"). Indexed and used as the primary key in compound time-range queries.

Signal

direction
"long" | "short"
required
Trade direction as parsed from the original Telegram message. Maps directly from IParserRow.direction.
entryFrom
number
required
Lower bound of the entry price zone. Sourced from IParserRow.entry.from.
entryTo
number
required
Upper bound of the entry price zone. Sourced from IParserRow.entry.to.
targets
number[]
required
Array of take-profit target prices in ascending order. At least one element is always present.
stoploss
number
required
Stop-loss price level. For long signals this is below entryFrom; for short signals it is above entryTo.

Risk Assessment (LLM output)

riskAction
"skip" | "follow"
required
The LLM’s trading verdict. follow means the risk filter approved the signal; skip means it was rejected. Indexed to allow fast filtering by verdict. Sourced from RiskOutlineContract.action.
riskSureLevel
"low" | "low_medium" | "medium" | "medium_high" | "high"
required
LLM’s confidence in the presence of market manipulation in the candle data. Audit field only. Indexed for analytical queries. Sourced from RiskOutlineContract.sure_level.
riskConfidence
"reliable" | "not_reliable"
required
LLM’s assessment of the input data quality. reliable indicates ≥ 60 one-minute candles with unambiguous metrics. Audit field only. Indexed. Sourced from RiskOutlineContract.confidence.
riskDescription
string
required
Human-readable 2–3 sentence verdict for the trader. Always names the applied rule with specific numbers. Minimum 30 characters. Sourced from RiskOutlineContract.description.
riskReasoning
string
required
Single flat string with newline-separated reasoning steps produced by the LLM. Not a JSON object or array. Minimum 80 characters. Sourced from RiskOutlineContract.reasoning.

Raw Data

note
string
required
The full original Telegram message text, preserved verbatim for audit and replay purposes.
content
Mixed
required
The raw parsed content object produced by the parser (type unknown in TypeScript / Schema.Types.Mixed in Mongoose). Stores the structured signal data before field-level extraction.
createDate
Date
Auto-managed by Mongoose timestamps. Set once at document creation, never updated. Mapped from the Mongoose default createdAt via { createdAt: "createDate" }.
updatedDate
Date
Auto-managed by Mongoose timestamps. Mapped from the Mongoose default updatedAt via { updatedAt: "updatedDate" }.

Indexes

The collection carries the following indexes:
IndexTypePurpose
{ parserItemId: 1 }UniquePrevents duplicate processing of the same parser row
{ symbol: 1, publishedAt: -1 }CompoundEfficient findLast4HourRow queries sorted by recency
{ channel: 1 }Single-fieldFilter by Telegram channel
{ source: 1 }Single-fieldFilter by source identifier
{ publishedAt: 1 }Single-fieldTime-range scans
{ symbol: 1 }Single-fieldSymbol equality filter
{ riskSureLevel: 1 }Single-fieldAnalytical grouping by manipulation confidence
{ riskConfidence: 1 }Single-fieldAnalytical grouping by data quality
{ riskAction: 1 }Single-fieldFast filter of follow/skip verdicts
The unique index on { parserItemId: 1 } is the primary idempotency guard. ScreenDbService.create() uses findOneAndUpdate with $setOnInsert, so calling create() twice for the same parser row returns the existing document without writing any new data.

Querying

ScreenDbService exposes two read methods used by the rest of the system:
// Returns the screen-items document for a specific parser row,
// or null if the signal has not been processed yet.
const existing = await screenDbService.findByParserItem(parserRow.id);
if (existing) {
  // already processed — skip
}
Both queries are served by the compound index { symbol: 1, publishedAt: -1 }, making them efficient even for high-frequency symbols.

Build docs developers (and LLMs) love