javascript

ИИ Агент для скоринга торговых рекомендаций

  • воскресенье, 9 августа 2026 г. в 00:00:10
https://habr.com/ru/articles/1068228/

Исходный код, разобранный в статье, опубликован по ссылке

В интернете полно людей, которые рекомендуют купить или продать актив, показывают win rate и красивые графики доходности. Проблема в том, что их статистика принадлежит им самим: неудачный сигнал можно удалить, удачный оставить, а результат посчитать от удобной точки входа. По итоговой ленте невозможно отличить талант от хорошо отредактированной истории

Торговые рекомендации

Непонятны две вещи.

1. Удаляет ли автор свои рекомендации задним числом

Высокий win rate сам по себе ничего не значит: он одинаково хорошо получается близким TP и удалением неудачных постов задним числом. По итоговой ленте эти случаи неотличимы. Считать можно только по ленте, снятой в момент публикации и сохранённой у себя.

2. Как это парсить

Не у всех есть жёстко заданный формат с заранее указанными TP/SL. Более интересен свободный формат: агент должен извлечь торговое намерение из того, что реально написал автор.

Автоматизируем процесс

Подключение источника данных через MCP
Подключение источника данных через MCP

Используя Model Context Protocol, агент подключается к внешнему источнику данных. По спецификации источник данных передаёт не только текст, но и картинки.

Постановка повторяющейся задачи через /loop
Постановка повторяющейся задачи через /loop

Команда /loop ставит ИИ-агенту задачу, которую нужно повторять с интервалом. Счёт при этом тестовый, сделки исполняются симуляцией внутри движка, и указание этого в промпте соответствует политикам безопасности.

Состояние активных позиций в контексте агента
Состояние активных позиций в контексте агента

Агент осведомлён, какие позиции активны, их peak profit, максимальная просадка, текущий PnL. Условие, что торгуем только альту и больше трёх позиций одновременно не держим, прописывается текстом.

Итерации агента по интервалу
Итерации агента по интервалу

Интервал работает. Условие выхода прописывается как часть промпта при откате от peak profit или программируется статично, об этом далее.

Стоимость токенов

Для оптимизации потребления токенов генерация summary по каналу выносится в сабагента. Таким образом тяжёлые изображения не будут попадать в глобальный контекст. Ниже приведены метрики, сколько тратит вариант без оптимизации при подписке Claude Max за 200 долларов с точкой отсчёта от недельного лимита. Окно 5 часов не достигается ни для одной модели.

  1. Fable 5 — 2 дня работы с интервалом опроса 15 минут, выбирает лимит целиком (на Max он ограничен половиной недельного)

  2. Opus 5 — 8 дней с интервалом опроса 15 минут, недельный лимит не выбирается

  3. Sonnet 5 — 20 дней с интервалом опроса 15 минут, недельный лимит не выбирается

  4. Haiku 4.5 — 40 дней с интервалом опроса 15 минут, недельный лимит не выбирается

Итого, Sonnet, выбранная в Claude Code по умолчанию, если сами не меняли на Opus, позволяет проверять чат раз в 15 минут не оптимизируя контекст вовсе. Добавив оптимизации промпта или увеличив интервал, можно охватить больше источников, открыв в tmux несколько консольных чатов.

Почему доходность считает не агент

Если просто попросить ИИ-чат провести бектест торговой стратегии, он подгонит результат под ожидаемый: у него нет внешней проверки, а закончить работу с плохим числом он не хочет. Но если доходность считает внешний инструмент, а у агента есть две кнопки, купить или продать, то обман с close-to-close бектестом не выйдет.

Методология оценки

Итоговый рейтинг автора собирается не из одной цифры, а из сводной таблицы по всем тикерам, которые он рекомендовал. Такой разрез сразу показывает то, что общая доходность прячет: автор может уверенно вести пару монет и наугад раздавать всё остальное. Задачу анализа исполняет не ИИ агент, а его движок исполнения MCP. Считается все: просадки позиций, sharpe ratio, calmar ratio. Отдельно от агента

Результат

Автор торговых рекомендаций всегда приходит со своей статистикой: win rate, серия профитов, скриншот эквити. Вместо того, чтобы верить наслово, можно проторговать его входы и на основе этого получить рейтинг. Не по регалиям, а по существу. И сделать это автоматизированно, поручив задачу ИИ так, что и он тебя тоже обмануть не сможет.

Локальный хостинг

С точки зрения архитектуры, ИИ-агент принимает бинарное решение и не передаёт точную сумму входа при открытии или закрытии позиции. Вся арифметика живёт в скрипте, поэтому слабая модель не ломает риск-менеджмент. Это позволяет использовать локальные LLM: ollama реализует для них вызов инструментов, а Claude Code подключается к нему через прокси на Anthropic-совместимый эндпоинт, без облака Anthropic.

Точный TP/SL и trailing take выставляются скриптом через события жизненного цикла. Они высчитываются исходя из актуальной волатильности рынка и всегда разные, стабильное окно неэффективно.

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

Минимальный код только для агента

import { addStrategySchema } from "backtest-kit";

addStrategySchema({
  strategyName: "main_strategy",
});

Всё остальное агент делает через MCP-инструменты: схема нужна только чтобы движок знал, под каким именем вести журнал сделок.

Досрочное закрытие мануально

import {
  listenActivePing,
  commitClosePending,
  getPositionHighestProfitDistancePnlPercentage,
  getPositionHighestPnlPercentage,
  getPositionPnlPercent,
  getPositionHighestProfitMinutes,
} from "backtest-kit";

import { str } from "functools-kit";

const TRAILING_TAKE_DISTANCE = 1.0;

const PEAK_STALENESS_SINCE_PROFIT = 1.0;
const PEAK_STALENESS_SINCE_MINUTES = 240;

listenActivePing(async ({ symbol }) => {
  const peakProfitDistance = await getPositionHighestProfitDistancePnlPercentage(symbol);
  const currentProfit = await getPositionPnlPercent(symbol);
  if (currentProfit < 0) {
    return;
  }
  if (peakProfitDistance < TRAILING_TAKE_DISTANCE) {
    return;
  }
  await commitClosePending(symbol, {
    id: "trailing_take",
    note: str.newline(
      "# Позиция закрыта по trailing take",
    ),
  });
});

listenActivePing(async ({ symbol }) => {
  const peakProfitCost = await getPositionHighestPnlPercentage(symbol);
  const peakProfitMinutes = await getPositionHighestProfitMinutes(symbol);
  if (peakProfitCost < PEAK_STALENESS_SINCE_PROFIT) {
    return;
  }
  if (peakProfitMinutes < PEAK_STALENESS_SINCE_MINUTES) {
    return;
  }
  await commitClosePending(symbol, {
    id: "peak_staleness",
    note: str.newline(
      "# Позиция закрыта по peak staleness",
    ),
  });
});

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

Дашборд

Библиотека backtest-kit сама обрабатывает покупку через limit-ордер, дожидаясь продавца и откатывая транзакцию, если он не нашёлся. Интерфейс агента при этом одинаковый в обоих режимах: он не знает, работает paper или полноценная торговля. Это не только удобство, это защита от подгонки поведения: safeguard не будет ломать такой flow. Дополнительно по всем позициям предоставляются графики и уведомления.

Графики и уведомления по активным позициям
Графики и уведомления по активным позициям

Если что-то идёт не так, агент документирует произошедшее. Например, трейдер может захотеть докупить позицию. Это учитывается в математике: средняя цена входа считается как взвешенная по объёму, и PnL пересчитывается от неё, а не от первой заявки. А агент пишет текстовый комментарий чем трейдер мотивировал это решение

Журнал событий по позиции с усреднением
Журнал событий по позиции с усреднением

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