Какая страховка нужна трейдеру? Бэктест, не просто риск менеджемент. metatrader.. metatrader. pinescript.. metatrader. pinescript. quant.. metatrader. pinescript. quant. trading.. metatrader. pinescript. quant. trading. заработок.. metatrader. pinescript. quant. trading. заработок. мосбиржа.. metatrader. pinescript. quant. trading. заработок. мосбиржа. торговые роботы.. metatrader. pinescript. quant. trading. заработок. мосбиржа. торговые роботы. торговые стратегии.. metatrader. pinescript. quant. trading. заработок. мосбиржа. торговые роботы. торговые стратегии. трейдинг.

Посмотреть код движка бектеста можно по ссылке

Какая страховка нужна трейдеру? Бэктест, не просто риск менеджемент - 1

Я взял два платных телеграм‑канала с сигналами, прочитал месяц их постов (август 2026) через LLM и проиграл каждый сигнал на минутных свечах с комиссиями и слиппеджем. Первый канал дал +10.3% за месяц, второй −18.1%. Обе цифры без плеча. Разница объясняется форматом постов, и её видно ещё до запуска бэктеста.

Зачем это всё

Представим трейдера, который собирается платить за сигналы из телеграма и следовать им. От чего ему нужна страховка?

Стоп‑лосс сам по себе не защищает. Можно поставить стоп, который выбивает в 100% случаев, и потерять депозит, формально соблюдая риск‑менеджмент. Работают правила автора или нет, показывает только прогон по истории: реальные посты, минутные свечи, комиссии, слиппедж. На выходе получается цифра, которую можно проверить.

Этим я и занялся. Каналы выбрал с противоположными форматами:

  • Канал A в каждом посте публикует полный торговый план.

  • Канал B строит подачу на личной дисциплине: «беру по 1% от депозита», «принимаю убытки и двигаюсь дальше».

Результаты

Канал A

Канал B

Формат сигнала

диапазон входа + 5 целей + стоп

«работаю с BTC в шорт» + скриншот

Сделок за месяц

21

24

Итог (без плеча, за вычетом комиссий)

+10.3%

−18.1%

Прибыльных сделок (win rate)

76% (16 из 21)

29% (7 из 24)

Profit factor

1.78

0.57

Средняя длительность позиции

~17 часов

~44 часа

Худшая сделка

−4.6% (стоп автора)

−7.9% (наш стоп, своих у автора нет)

Рынок, месяц и модель в пайплайне одинаковые, а разница составила 28 процентных пунктов. Дальше разберу, как это посчитано и откуда она взялась.

LLM разбирает посты, решения принимает код

Главное решение в проекте: модель не принимает торговых решений. Ей досталась задача, с которой LLM справляются хорошо, — чтение неструктурированных данных.

Пост вида «Открыть ЛОНГ в диапазоне $78600 — $79400, цели…, стоп $77400» — это текст с опечатками, эмодзи и рекламными вставками. Иногда сигнал вообще не текст, а скриншот терминала со словом «Шорт» на картинке. Превратить это в типизированный JSON должна модель. Решать, входить ли в позицию и где резать убыток, должен код.

import { generateObject, InferenceName, type FormatModel } from "json-inference";

const TradingPositionFormat = {
  type: "object",
  required: ["id", "symbol", "position", "entryRange", "targets", "stoploss", "reasoning"],
  properties: {
    id: {
      type: "number",
      description: "ID сообщения-источника сигнала. Если сигнала нет, верни -1",
    },
    position: {
      type: "string",
      enum: ["long", "short", "wait"],
      description: "Тип позиции. Если позиции нет, верни wait",
    },
    entryRange: {
      type: "object",
      required: ["from", "to"],
      properties: { from: { type: "number" }, to: { type: "number" } },
    },
    targets: {
      type: "array",
      description: "Цели позиции, 5 уровней. Если сигнала нет, верни []",
      items: { type: "number" },
    },
    stoploss: { type: "number" },
    reasoning: { type: "string", description: "Обоснование - для отладки" },
  },
} satisfies FormatModel;

Насколько это надёжно

Я сравнил два поколения кеша на одинаковых входах. Вердикты и все числовые поля совпали бит в бит. Отличался только свободный текст в reasoning, вплоть до языка ответа. Структуру держит грамматика, цены модель копирует из поста. Для парсера большего и не нужно.

Пайплайн: пост → сигнал → сделка

Стратегия тикает каждую минуту по каждому символу. Если на каждом тике звать LLM и Telegram, получим тысячи лишних запросов и флуд‑бан. Поэтому получение сигнала обёрнуто в Cache.file: для канала A канал проверяется раз в 5 минут на символ.

Кеш хранится в Mongo. Побочный эффект полезный: коллекция measure-items становится датасетом всех ответов модели за месяц.

import { Cache } from "backtest-kit";
import { scrapeLookback } from "telegram-reader";

const getSignal = Cache.file(
  async (symbol: string, when: Date) => {
    const coin = `#${symbol.replace("USDT", "")}`;

    // окно 15 минут назад от «сейчас» бэктеста.
    // Пост, опубликованный позже when, стратегия не увидит
    let messages = await scrapeLookback({
      channel: "crypto_yoda_channel",
      limit: 15,
      dimension: "minute",
      when,
    });

    messages = messages.filter(({ content }) => content.includes(coin));
    if (!messages.length) return { entry: null }; // LLM не вызываем

    const entry = await generateObject(
      InferenceName.OllamaInference,
      {
        format: TradingPositionFormat,
        messages: [
          { role: "user", content: PARSER_PROMPT }, // строка промпта с правилами разбора поста
          ...messages.map(({ id, date, content }) => ({
            role: "user" as const,
            content: `ID ${id}nn${content}nn[${date.toISOString()}]`,
          })),
        ],
      },
      "gemma4:31b-cloud",
    );

    return { entry, message };
  },
  { interval: "5m", name: "crypto_yoda_entry_v3" },
);

Канал A: дисциплина в структуре поста

Каждый сигнальный пост — законченный торговый план:

СИГНАЛ #BTC/USDT
🔑 Открыть ЛОНГ в диапазоне $78600 - $79400 - 6% депозита, плечо X15
🍒 Цели: $80300 / $80900 / $82000 / $83200 / $84500
❗️ СТОП ЛОСС: $77400

Диапазон, пять целей и стоп есть в каждом посте весь месяц. Такой сигнал можно исполнить кодом, ничего не додумывая. Это и есть дисциплина в машиночитаемом виде.

Как стратегия его исполняет

  1. Вход — по рынку на любом посте не старше 15 минут. Преимущество сигнала живёт рядом с моментом публикации.

  2. Тейк‑профит — на самую дальнюю цель.

  3. Стоп‑лосс — из поста.

  4. Сопровождение — «достижение уровней». Каждая взятая цель становится полом. Если цена откатилась за последний взятый уровень больше чем на 30% шага между целями, позиция закрывается.

Шаг считается по сетке целей конкретного поста, поэтому допуск подстраивается сам: у PENGU шаги около 0.3–0.9%, у BTC — сотни долларов.

listenActivePing(async ({ data, currentPrice }) => {
  const levels = data.payload.levels as number[]; // цели из поста
  const currentLevel = getCurrentLevel(data.position, levels, currentPrice);
  const { lastLevel } = await LEVEL_STATE.getState(); // состояние конкретной позиции

  if (currentLevel > lastLevel) {
    await LEVEL_STATE.setState({ lastLevel: currentLevel }); // новый пол
    return;
  }
  if (!lastLevel) return; // ни одной цели не взяли - ждём стопа или тейка

  const drift = getLevelDrift(data.position, levels, lastLevel, currentPrice);
  const step = getLevelStep(levels, lastLevel); // шаг между соседними целями
  if (drift > step * LEVEL_DRIFT_RATIO) {
    await commitClosePending(data.symbol); // откатились за взятый уровень - выходим
  }
});

Итог

21 сделка, +10.3% без плеча за вычетом комиссий, 16 из 21 в плюсе.

  • 3 раза сработал стоп автора: от −3.8% до −4.6%, разумный размер.

  • 1 раз цена дошла до дальнего тейка.

  • Остальные сделки закрыл рэтчет.

Откуда взялся плюс

Собственный манименеджмент автора я не копировал. Его план — разгружаться по пяти целям с плечом X15–25 — на этих же данных убыточен. Дальние цели почти не достигаются, а один стоп при таком плече сжигает 60–100% маржи.

Плюс получился на стыке: структурированный сигнал автора и наш риск‑менеджмент. Направление автора приносит деньги, его плечо сжигает депозит.

Канал B: дисциплина на словах

У Вершинина формат противоположный. Диапазонов, целей и стопов нет. Есть посты:

Вход в позицию

Работаю с BTC - в шорт, взял с учетом возможного добора

Выход из позиции

И так, берем BTC, ETH, SOL в шорт по 1% от депозита,
биток и эфир 100 плечо, Солана 50 плечо.

Направление и тикер часто видны только на картинке: скриншоте позиции, графике TradingView или карточке сделки с биржи. Поэтому модель получает не только текст поста, но и изображение:

const signal = await generateObject(
  InferenceName.OllamaInference,
  {
    format: PositionOpenFormat, // { id, symbol, position: long|short|wait, reasoning }
    messages: [
      { role: "user", content: promptOpen },
      ...messages.map(({ id, date, content, photo }) => ({
        role: "user" as const,
        content: `ID ${id}nn${content}nn[${date.toISOString()}]`,
        ...(photo && { images: [photo] }), // картинка поста в base64 - часть сигнала
      })),
    ],
  },
  "gemma4:31b-cloud",
);

Правила для распознавания картинок я писал только после того, как просмотрел реальные скриншоты канала. На одной ловушке модель ошибалась регулярно: зелёная цифра P&L у шорта означает прибыль, а не «лонг». Направление определяется только по слову «Лонг/Шорт» или по положению зон тейка и стопа на графике. Теперь это правило прописано в промпте явно.

Как стратегия его исполняет

Автор не публикует стопов, поэтому стоп свой: 7.5% от цены входа.

Вход и выход у этого канала — отдельные решения, принятые в разное время. Поэтому пайплайнов два:

  • пока позиции нет, модель ищет в постах вход;

  • пока позиция открыта, модель ищет пост, где автор её закрывает.

addStrategySchema({ strategyName: "main_strategy" }); // схема пустая, без getSignal

listenIdlePing(async ({ symbol, when, currentPrice }) => { // тикает, пока позиции нет
  const { signal } = await getOpenSignal(symbol, when); // Cache.file, 4h
  if (!signal || signal.position === "wait") return;

  await commitCreateSignal(symbol, {
    id: `${signal.id}-${signal.symbol.toLowerCase()}`,
    symbol: signal.symbol,
    ...Position.moonbag({
      position: signal.position,
      currentPrice,
      percentStopLoss: 7.5, // свой стоп - автор своих не публикует
    }),
  });
});

listenActivePing(async ({ symbol, when }) => { // тикает, пока позиция открыта
  const { signal } = await getCloseSignal(symbol, when);
  if (signal?.action === "close") await commitClosePending(symbol);
});

Итог: 24 сделки, −18.1%, в плюсе 7 из 24.

Выводы

Считайте сами!

Прежде чем платить за сигналы и тем более следовать им, проверьте автора на истории. Это дешевле любой страховки. Весь анализ выше построен на двух файлах backtest.jsonl. В каждой строке — одно событие жизненного цикла позиции. Базовые метрики считаются десятком строк:

import { readFileSync } from "node:fs";

const trades = readFileSync("backtest_yoda.jsonl", "utf8")
  .split("n").filter(Boolean).map(JSON.parse)
  .filter((e) => e.data.action === "closed");

const pnl = trades.reduce((sum, e) => sum + e.data.pnl, 0);
const wins = trades.filter((e) => e.data.pnl > 0).length;
const grossProfit = trades.filter((e) => e.data.pnl > 0).reduce((s, e) => s + e.data.pnl, 0);
const grossLoss = trades.filter((e) => e.data.pnl <= 0).reduce((s, e) => s + e.data.pnl, 0);

console.log({
  trades: trades.length,
  totalPnl: pnl.toFixed(2),                                // +10.26
  winRate: (wins / trades.length * 100).toFixed(0) + "%",  // 76%
  profitFactor: (grossProfit / -grossLoss).toFixed(2),     // 1.78
});

В каждом событии closed есть:

  • цены входа и выхода;

  • причина закрытия: take_profit, stop_loss, closed или time_expired;

  • максимальная прибыль и максимальная просадка по позиции;

  • в поле note — полный ответ модели и пост‑источник.

Любую сделку можно отмотать до конкретного телеграм‑поста и прочитать reasoning модели. Метод переносится на любой другой канал за вечер.

Инструменты

  • backtest‑kit — движок бэктеста и live‑торговли с защитой от look‑ahead.

  • telegram‑reader — чтение телеграм‑каналов через MTProto.

  • json‑inference — structured output для ollama.

  • backtest‑ollama‑casual — репозиторий с обеими стратегиями и скриптами анализа.

Спасибо за внимание

Автор: tripolskypetr

Источник