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 risk outline is the core analytical innovation of the project. Rather than applying hard-coded if statements in application code, it passes each parsed trading signal to a locally-running Ollama LLM together with pre-computed market metrics and recent candle data. The LLM is instructed to apply exactly two empirically-derived rules — nothing more — and return a structured verdict. This design keeps the decision logic auditable: every call is logged with a description and step-by-step reasoning field, and the full response is dumped to ./dump/outline/risk for post-analysis.

The Two Rules

The system prompt (RISK_PROMPT) instructs the LLM to evaluate two skip rules and one default case, in order:
1

Rule 1 — Sleeping-coin SHORT (skip)

Condition: direction = SHORT AND avgRangePct < 0.07%Action: "skip"Rationale: A “sleeping” asset has thin liquidity and an unusually narrow 1-minute candle range. A single large market-maker candle can sweep all stop-loss orders above the entry zone — exactly where channel SHORT subscribers are positioned. The channel’s signal becomes a stop-hunt trigger rather than an edge.
2

Rule 2 — Knife-catching LONG (skip)

Condition: direction = LONG AND momentum24hPct < -1%Action: "skip"Rationale: Buying into a market that has already dropped more than 1% over the past 24 hours puts subscribers on the wrong side of momentum. The market continues downward, the stop-loss is triggered, and the channel has effectively sold the top to its audience.
3

Rule 3 — Default (follow)

Condition: Neither Rule 1 nor Rule 2 triggered.Action: "follow"The position is opened in the direction signalled by the channel.
action is determined strictly by the two rules applied to the pre-computed metrics avgRangePct and momentum24hPct. The sure_level and confidence fields are audit fields recorded for post-analysis — they never affect the trading decision. The strategy in jan_2026.strategy.ts follows or skips solely based on riskAction.

Pre-computed Metrics

commitMetricsHistory() fetches the last 1440 1-minute candles (24 hours of history) via getCandles(symbol, "1m", 1440) and computes two scalar metrics that are passed to the LLM as a user message:
const avgRangePct =
  candles.reduce((acc, c) => acc + ((c.high - c.low) / c.close) * 100, 0) /
  candles.length;

const momentum24hPct =
  ((candles[candles.length - 1].close - candles[0].open) / candles[0].open) * 100;
MetricFormulaMeaning
avgRangePctmean((high − low) / close × 100) over 1440 1m candlesAverage intra-candle volatility as a percentage of close price — a proxy for liquidity depth.
momentum24hPct(last close − first open) / first open × 100Net 24-hour price change — a directional momentum indicator.
These values are sent to the model in plaintext so the rules can be applied without any floating-point recomputation by the LLM:
avgRangePct: 0.0452%
momentum24hPct: -1.83%
candlesCount: 1440

Candle Context

Two advisors supply additional candle tables that the LLM uses to populate the audit fields (sure_level, confidence). Both are fetched via ask(symbol, advisorName) and pushed into the conversation history before the metrics message.

StockData1mAdvisor (AdvisorName.StockData1mAdvisor)

Fetches the last 240 1-minute candles (4 hours of history) and formats them as a markdown table with columns: #, Time, Open, High, Low, Close, Volume, Change %, Volatility %, Body %.
const CANDLES_LIMIT = 240; // 240 x 1m = 4 hours

StockData15mAdvisor (AdvisorName.StockData15mAdvisor)

Fetches the last 32 fifteen-minute candles (8 hours of history) and formats them in the same markdown table structure.
const CANDLES_LIMIT = 32; // 32 x 15m = 8 hours
The candle tables are provided to help the LLM assess sure_level (evidence of stop-hunt accumulation in the order book structure) and confidence (data quality). They do not influence action. The action is determined solely by avgRangePct and momentum24hPct from the metrics message.

Outline Definition

The outline is registered with addOutline() from agent-swarm-kit. The full call structure is:
addOutline<RiskOutlineContract>({
  outlineName: OutlineName.RiskOutline,           // "risk_outline"
  completion: CompletionName.OllamaOutlineToolCompletion,
  format: zodResponseFormat(RiskOutlineFormat, "risk_assessment"),
  getOutlineHistory: async (
    { history },
    symbol: string,
    direction: "short" | "long",
    targets: number[],
    stoploss: number,
  ) => {
    await history.push({ role: "system", content: RISK_PROMPT });
    await commitOneMinuteHistory(symbol, history);     // 1m candle table
    await commitFifteenMinuteHistory(symbol, history); // 15m candle table
    await commitMetricsHistory(symbol, history);       // avgRangePct + momentum24hPct
    await commitDraftSignal({ symbol, direction, targets, stoploss }, history);
    await history.push({
      role: "user",
      content: str.newline(
        "Применяй правила из system prompt буквально по pre-computed метрикам.",
        "Верни action, sure_level, confidence, description, reasoning.",
      ),
    });
  },
  validations: [ /* 5 validators — see below */ ],
  callbacks: {
    async onValidDocument(result) {
      if (!result.data) return;
      await dumpOutlineResult(result, "./dump/outline/risk");
    },
  },
});

Conversation history order

The history is assembled in this exact sequence before the model is called:
  1. SystemRISK_PROMPT (role description, rules, output requirements)
  2. UserAssistant OK — 1-minute candle markdown table
  3. UserAssistant OK — 15-minute candle markdown table
  4. UserAssistant OKavgRangePct, momentum24hPct, candlesCount
  5. UserAssistant — draft signal (symbol, direction, targets, stop-loss)
  6. User — final instruction to apply rules and return structured output

Validations

Five validators run against every LLM response before it is accepted:
#FieldRule
1actionMust be "skip" or "follow"
2sure_levelMust be one of "low", "low_medium", "medium", "medium_high", "high"
3confidenceMust be "reliable" or "not_reliable"
4descriptionMust be a string with at least 30 characters
5reasoningMust be a string with at least 80 characters
If any validator throws, the completion backend retries according to its retry policy.

LLM System Prompt

RISK_PROMPT is written in Russian (the channel language) and instructs the model in the following key points:
  • Role: Scam signal detector for Telegram trading channels. The sole task is to return action: "follow" or action: "skip".
  • Available data: Pre-computed metrics (avgRangePct, momentum24hPct), 1-minute candles (4 h), 15-minute candles (8 h), and the draft signal.
  • Decision rules: Apply Rules 1 and 2 literally using the pre-computed metric values. No intuition, no trend analysis, no news. If neither rule fires, return "follow".
  • Audit fields: sure_level (evidence of manipulation in candle structure) and confidence (reliability of available data) are filled honestly but do not affect action.
  • Output format: action, sure_level, confidence, description (2–3 sentences citing the rule and numbers), reasoning (a single flat string with steps separated by \n — not a JSON object or array).

Response Contract

The Zod schema that enforces the LLM’s response structure:
import { z } from "zod";

export const RiskOutlineFormat = z.object({
  action: z.enum(["skip", "follow"]),
  sure_level: z.enum(["low", "low_medium", "medium", "medium_high", "high"]),
  confidence: z.enum(["reliable", "not_reliable"]),
  description: z.string(),
  reasoning: z.string(),
});

export type RiskOutlineContract = z.infer<typeof RiskOutlineFormat>;
The zodResponseFormat(RiskOutlineFormat, "risk_assessment") call converts this schema into the structured-output format expected by the Ollama API.

Completion Backends

Two completion backends are available. The risk outline uses OllamaOutlineToolCompletion (tool-calling mode):
// Uses Ollama tool-calling: defines a "provide_answer" function tool
// and requires the model to call it with the structured arguments.
// Adds a system instruction forcing tool use, and appends a user nudge
// message on attempts where the model replies in plain text instead.

addCompletion({
  completionName: CompletionName.OllamaOutlineToolCompletion,
  getCompletion: async (params) => fetchCompletion(params),
  json: true,
  flags: ["Всегда пиши ответ на русском языке", "Reasoning: high"],
});
Both backends share the same configuration constants:
SettingValue
Modelgpt-oss:120b
Max inner attempts per call3
Outer retry count5
Retry delay5 000 ms
Request timeout300 000 ms (5 minutes)
Both backends use jsonrepair to fix common JSON formatting errors (trailing commas, unquoted keys, truncated strings) before passing the response to Zod validation. This significantly reduces the rate of retries caused by minor model output formatting issues.

Build docs developers (and LLMs) love