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.

SignalLogicService is the thin but critical layer that converts a structured IParserRow — a previously parsed Telegram trading signal stored in parser-items — into a fully enriched IScreenDto ready for screen-items storage. It does this by invoking the RiskOutline agent from agent-swarm-kit with the signal’s key parameters (symbol, direction, targets, stoploss) and merging the LLM’s risk assessment fields onto the passthrough signal fields. The service itself contains no prompt logic; that lives inside the outline definition. SignalLogicService is only responsible for calling the outline, validating the response, and assembling the final DTO.

SignalLogicService

SignalLogicService is registered under TYPES.signalLogicService in the DI container and is accessible as core.signalLogicService.
import { SignalLogicService } from "@core/lib/services/logic/SignalLogicService";
// const signalLogicService = inject<SignalLogicService>(TYPES.signalLogicService);

execute(row: IParserRow): Promise<IScreenDto>

The sole public method. Runs the RiskOutline LLM outline against the given parser row and returns a complete screen DTO.
public execute = async (row: IParserRow): Promise<IScreenDto>
row
IParserRow
required
A document from the parser-items MongoDB collection. Must carry all signal fields populated by CrawlerService. Key properties consumed by this method:
FieldTypeDescription
idstringMongoDB _id (used as parserItemId in the DTO)
channelstringSource channel identifier, e.g. "crypto_yoda_channel"
sourcestringOrigin source string (same as channel for Telegram)
publishedAtDateOriginal Telegram message timestamp
symbolstringTrading pair symbol, e.g. "BTCUSDT"
direction"long" | "short"Trade direction
entry{ from: number; to: number }Entry price zone
targetsnumber[]Ordered take-profit price levels
stoplossnumberStop-loss price level
notestringRaw message text stored as a note
contentunknownFull parsed signal data (Mixed)
Throws Error if the LLM outline returns isValid: false. The error message is the error string from the outline response.

Return value: IScreenDto

parserItemId
string
The id of the source parser-items row. Used as the unique key in the screen-items collection ({ parserItemId: 1 } unique index).
channel
string
Passed through from row.channel.
source
string
Passed through from row.source.
publishedAt
Date
Passed through from row.publishedAt.
symbol
string
Passed through from row.symbol (e.g. "ETHUSDT").
direction
"long" | "short"
Passed through from row.direction.
entryFrom
number
Flattened from row.entry.from — the lower bound of the entry price zone.
entryTo
number
Flattened from row.entry.to — the upper bound of the entry price zone.
targets
number[]
Passed through from row.targets.
stoploss
number
Passed through from row.stoploss.
riskSureLevel
"low" | "low_medium" | "medium" | "medium_high" | "high"
Mapped from outline.sure_level. Indicates the LLM’s confidence that market manipulation (candle anomalies, volume accumulation) is present.
ValueMeaning
lowNo signs of manipulation; organic volume structure
low_mediumOne recent anomaly without price displacement
mediumOne older anomaly or one with price displacement
medium_highRecurring accumulation pattern
highMultiple accumulation events with clear price displacement
riskConfidence
"reliable" | "not_reliable"
Mapped from outline.confidence. Indicates data quality for the assessment.
ValueMeaning
reliableData is unambiguous
not_reliableData is contradictory, weak, or missing
riskAction
"skip" | "follow"
Mapped from outline.action. The LLM’s trade recommendation.
ValueMeaning
followOpen a position in the signal’s direction
skipDo not open a position
riskDescription
string
Mapped from outline.description. A 2–3 sentence human-readable summary of the action decision, naming the applied rule and key figures. Example: "Action skip. SHORT on a dormant asset (avgRangePct 0.045% < 0.07%) — stop-hunt target. Sure_level high, confidence reliable."
riskReasoning
string
Mapped from outline.reasoning. A detailed step-by-step reasoning trace (single string, newline-separated steps):
  1. Raw metric values used
  2. Applied rule → action decision
  3. sure_level and confidence justification
note
string
Passed through from row.note (the raw Telegram message text).
content
unknown
Passed through from row.content (the full parsed data object stored as Mixed).

Full execute Implementation

import { inject }        from "@core/lib/core/di";
import { IParserRow }    from "@core/schema/Parser.schema";
import { IScreenDto }    from "@core/schema/Screen.schema";
import LoggerService     from "@core/lib/services/base/LoggerService";
import TYPES             from "@core/lib/core/types";
import { json }          from "agent-swarm-kit";
import OutlineName       from "@core/enum/OutlineName";
import { RiskOutlineContract } from "@core/contract/RiskOutline";

type Symbol    = string;
type Direction = "short" | "long";
type Targets   = number[];
type StopLoss  = number;

type Args = [Symbol, Direction, Targets, StopLoss];

const RUN_OUTLINE_FN = async (row: IParserRow) => {
  const { data, error, isValid } = await json<RiskOutlineContract, Args>(
    OutlineName.RiskOutline,   // "risk_outline"
    row.symbol,
    row.direction,
    row.targets,
    row.stoploss,
  );

  if (!isValid) {
    throw new Error(error);
  }

  return data;
};

export class SignalLogicService {
  readonly loggerService = inject<LoggerService>(TYPES.loggerService);

  public execute = async (row: IParserRow): Promise<IScreenDto> => {
    this.loggerService.log("signalLogicService execute", { rowId: row.id });

    const outline = await RUN_OUTLINE_FN(row);

    return {
      // ── Passthrough fields ──────────────────────────────────────────────
      parserItemId: row.id,
      channel:      row.channel,
      source:       row.source,
      publishedAt:  row.publishedAt,
      symbol:       row.symbol,
      direction:    row.direction,
      targets:      row.targets,
      stoploss:     row.stoploss,
      note:         row.note,
      content:      row.content,

      // ── Flattened entry zone ─────────────────────────────────────────────
      entryFrom: row.entry.from,
      entryTo:   row.entry.to,

      // ── LLM risk assessment ──────────────────────────────────────────────
      riskSureLevel:   outline.sure_level,
      riskConfidence:  outline.confidence,
      riskAction:      outline.action,
      riskDescription: outline.description,
      riskReasoning:   outline.reasoning,
    };
  };
}

json() from agent-swarm-kit

RUN_OUTLINE_FN uses the json<T, Args>() helper to invoke a named outline by its string identifier.
const { data, error, isValid } = await json<RiskOutlineContract, Args>(
  outlineName: string,
  ...args: Args
): Promise<{ data: T | null; error: string | null; isValid: boolean }>
outlineName
string
required
The registered outline identifier. For the risk filter this is OutlineName.RiskOutline which resolves to the string "risk_outline".
...args
Args tuple
required
Positional arguments forwarded to the outline, typed as [Symbol, Direction, Targets, StopLoss]:
  • row.symbol — e.g. "BTCUSDT"
  • row.direction"long" or "short"
  • row.targetsnumber[] of take-profit levels
  • row.stoploss — stop-loss price as number
data
RiskOutlineContract | null
The structured LLM response when isValid is true. Shape is validated against the RiskOutlineFormat Zod schema (see below).
error
string | null
An error message string when isValid is false, otherwise null.
isValid
boolean
true when the LLM produced a valid, schema-conformant response. SignalLogicService throws on false.

RiskOutlineContract Schema

Defined in contract/RiskOutline.ts using Zod. This is the exact shape of outline after RUN_OUTLINE_FN returns.

DTO Field Mapping Cheatsheet

The table below shows the complete mapping from IParserRow and RiskOutlineContract fields to IScreenDto fields.
IScreenDto fieldSourceOrigin field
parserItemIdIParserRowrow.id
channelIParserRowrow.channel
sourceIParserRowrow.source
publishedAtIParserRowrow.publishedAt
symbolIParserRowrow.symbol
directionIParserRowrow.direction
entryFromIParserRowrow.entry.from (flattened)
entryToIParserRowrow.entry.to (flattened)
targetsIParserRowrow.targets
stoplossIParserRowrow.stoploss
noteIParserRowrow.note
contentIParserRowrow.content
riskSureLevelRiskOutlineContractoutline.sure_level
riskConfidenceRiskOutlineContractoutline.confidence
riskActionRiskOutlineContractoutline.action
riskDescriptionRiskOutlineContractoutline.description
riskReasoningRiskOutlineContractoutline.reasoning
The entry object from IParserRow ({ from, to }) is flattened into two top-level fields (entryFrom, entryTo) in IScreenDto. The nested object is not stored in screen-items.

Error Handling

SignalLogicService.execute propagates errors in two scenarios:
  1. isValid: false — The LLM outline returned an invalid or unparseable response. RUN_OUTLINE_FN throws new Error(error). The caller (SignalJobService.RUN_IN_CONTEXT_FN) receives the exception.
  2. Network / timeout errors — Any exception thrown by json() itself (e.g. the Ollama instance is unreachable) propagates unmodified.
try {
  const dto = await signalLogicService.execute(row);
  await screenDbService.create(dto);
} catch (err) {
  // Log and skip — the parser-items row remains unvisited
  // and will be retried on the next signalJobSubject.next() tick
  logger.error("signalLogicService execute failed", { rowId: row.id, err });
}
If execute throws, SignalJobService does not catch the error internally — it will propagate out of the queued runner. Wrap the call site if you need per-row fault tolerance.

SignalJobService

Calls execute() inside RUN_IN_CONTEXT_FN for every unvisited parser-items row.

CrawlerService

Produces the IParserRow documents that execute() consumes.

Build docs developers (and LLMs) love