Prompt ИИ агента фундаментального анализа рынка

от автора

Существующие решения на GitHub имеют фатальные изъяны. Разберём несколько примеров — плюсы и минусы.

TauricResearch/TradingAgents

Ссылка на исходный код

Пайплайн, который тянет посты из Reddit, X, Bloomberg, Reuters и Yahoo Finance. Два агента затем спорят друг с другом: один доказывает, что цена вырастет, другой — что упадёт. Вы задаёте количество раундов дебатов — в конце побеждают аргументы одного из агентов.

Эта программа создаёт иллюзию работы. Если залезть в код, видно, что рою агентов скармливается дамп сырых индикаторов. LLM технически не способны правильно обработать это в текстовом диалоге из-за проблемы приоритизации.

LLM учатся обосновывать ответ быстрее, чем рассуждать тщательно. Когда им скармливают сырые индикаторы — скажем, RSI и Stoch RSI одновременно — ответ агента определяется тем, какой из них он подхватит первым. При этом ситуация, когда один индикатор говорит о перекупленности, а другой — о перепроданности, крайне распространена. Цепочка рассуждений выглядит так:

  1. RSI — перепродан, теоретически цена должна вырасти

  2. Инструмента для проверки этой гипотезы через бэктест нет, так что мы об этом не думаем

  3. Stoch RSI перекуплен — я на это больше не смотрю, ответ уже есть

Если жёстко прописать веса приоритетов для индикаторов, система перестаёт быть адаптивной и начинает сливать деньги. В этой статье я показал, как построить среду, позволяющую агенту динамически выставлять приоритеты индикаторов, — но цена высока: 200k токенов на один бэктест ($1.20 для Haiku 4.5, $3.60 для Sonnet 4.6, $6.00 для Opus 4.6). Обновляйте редко — возвращаетесь к статичным приоритетам. Иными словами, TauricResearch — это просто подбрасывание монетки.

Уберите индикаторы совсем — получите эффект пылесоса. У вас есть посты с Reddit, но вы понятия не имеете, один ли это человек пишет своё мнение с 10 аккаунтов или 10 по-настоящему разных людей. Есть ещё проблема платных API: подключиться к X (Twitter) стоит $200/месяц. Если вы полагаетесь на бесплатные API (вроде Mastodon), блогер, чьи прогнозы отслеживал ваш агент, может выгореть и перестать постить.

node-ccxt-backtest

Ссылка на исходный код

Опираясь на их неудачный опыт, я пошёл другим путём: 8 агентов ищут целевые новости по разным темам.

  • Баланс — On-chain резервы. Отток с бирж, предложение LTH, доля неликвидного предложения, HODL-волны.

  • Движение денег — Потоки капитала. Чистые притоки в ETF, давление продаж майнеров, притоки стейблкоинов на биржи, OTC-объём.

  • Фундаментальные метрики — Здоровье сети. Хэшрейт, MVRV ratio, NVT ratio, модель Stock-to-Flow.

  • Доходы сети — Комиссии транзакций, пропускная способность Lightning, активность бондов, TVL DeFi.

  • Инсайдерские транзакции — Умные деньги. Покупки MicroStrategy, активы ETF BlackRock, Grayscale GBTC, государственные кошельки.

  • Новости актива — Рыночный сентимент. Регуляторные события, взломы бирж, институциональное принятие.

  • Глобальный макро — Макросреда. Решения ФРС по ставке, сюрпризы CPI, индекс DXY, индекс Fear & Greed, денежная масса M2.

  • История цены — Аномальные объёмы, подтверждения пробоев, закрытые сделки.

В идеальном мире это должно работать. Однако есть метрики портфеля, которые необходимо учитывать: max drawdown и sharpe ratio. Проблема в том, что подобный анализ слишком фундаментален и слишком оторван от реальных рыночных условий — где президент США шитпостит в twitter.

Почему оба подхода не работают

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

Паттерн «Рассуждение + Действие»

Это паттерн LLM-агента, при котором модель чередует мышление и действие. Упрощённо: между шагами рассуждения дайте агенту возможность искать в ленте новостей — тогда он будет знать, на чём сосредоточиться: на фундаментальном сломе или локальном отскоке.

Решение

Я написал следующий код. Повезло с тайминг: президент США решил вмешаться в ситуацию с Ираном и любезно предоставил мне живой тест-кейс. Код — в конце статьи, сначала посмотрим на сигналы и ценовой график.

Фундаментальный анализ за весь апрель 2026

PS C:\Users\User\Documents\GitHub\node-ccxt-backtest-final> bun .\scripts\run_signal.tsSearching Bitcoin breaking news April 6 2026Searching Bitcoin SEC enforcement action April 6 2026Searching Bitcoin flash crash April 6 2026Searching April 6 2026 Bitcoin price dip{  id: "5f7b9988-c41b-4000-a848-cf8712a84000",  reasoning: "В отчёте доминирует негативный драйвер: твит Дональда Трампа о военной эскалации с Ираном вызвал скачок нефти, укрепление доллара и резкое падение Bitcoin ниже $69 000. На этом фоне ставки финансирования ушли в глубокий минус — сигнализируя о росте коротких позиций, — а объём ликвидаций достиг $400M и может ускорить снижение, особенно при пробое уровня $66 000. Позитивные сигналы (план покупок Майкла Сэйлора, рост цены ETF Grayscale) ограничены и не способны компенсировать усилившийся risk-off сентимент и давление коротких позиций. В ближайшие часы ожидается дальнейшее снижение цены.",  signal: "SELL",}

5 апреля 2026

PS C:\Users\User\Documents\GitHub\node-ccxt-backtest-final> bun .\scripts\run_signal.tsSearching Bitcoin news April 5 2026Searching Bitcoin flash crash April 5 2026Searching April 5 2026 Bitcoin Trump tweet{  id: "4cc66bf6-4443-4800-aa43-28b6cb9f8800",  reasoning: "Острое событие — ультиматум США Ирану, что повышает геополитическую напряжённость и исторически ведёт к снижению цены в среднесрочной перспективе (медвежий риск-сценарий — вероятность ≈45%). Технические сигналы также указывают на слабость: цена торгуется ниже всех скользящих средних, RSI на 44, объём низкий, индекс страха на «экстремальном страхе» (12) — что могло бы спровоцировать быстрый шорт-сквиз, но без подтверждающего новостного катализатора это маловероятно. Поскольку сигналы противоречивы — возможен как резкий провал ниже $65 500, так и быстрое ралли при пробое $68 200 с подтверждением объёмом, — картина размытая. Лучше пока оставаться в стороне.\n\nВывод: сигнал WAIT.",  signal: "WAIT",}

8 апреля 2026

PS C:\Users\User\Documents\GitHub\backtest-kit\example> bun .\scripts\run_research.tsSearching April 8 2026 Bitcoin breaking newsSearching April 8 2026 Bitcoin hack withdrawal suspendedSearching April 8 2026 Trump tweet BitcoinSearching April 8 2026 Bitcoin flash crashSearching Decrypt April 8 2026 Bitcoin Trump ceasefire{  id: "c5e27ed0-4bba-4000-a7a1-879b822d6000",  signal: "BUY",  reasoning: "Острый событийный драйвер — объявление Трампа о двухнедельном перемирии с Ираном (04:30 UTC) — вызвал мгновенное ралли Bitcoin до $72 000, ликвидацию ~$425M коротких позиций и массовый всплеск объёма (>2M BTC/час). Эти факты указывают на резкий бычий импульс, который должен поддержать рост цены в ближайшие часы. Оптимальный ход: открыть лонг с жёстким стоп-лоссом около $70 000.",}

9 апреля 2026

PS C:\Users\User\Documents\GitHub\backtest-kit\example> bun .\scripts\run_research.tsSearching Bitcoin breaking news April 9 2026Searching Bitcoin Supply Shock: Long-Term Investors Now Control 21% Of Total BTC April 9 2026Searching Bitcoin breaking news April 8 2026 20:00 UTC{  id: "8708ddc6-57aa-4800-a114-787029fbd000",  reasoning: "Отчёт показывает противоречивую картину: с одной стороны — острое событие — пробой цены выше $71 000 на фоне новостей о перемирии, указывающий на краткосрочный рост; с другой — сильные медвежьи факторы: значительное давление продаж майнеров, снижение хэшрейта, доминирование пут-опционов (премия ~17%), риск пробоя поддержки $70 000 и потенциальный откат к $58–63k, плюс новости об уязвимости к квантовым вычислениям, нервирующие инвесторов. При наличии одновременно сильных бычьих и медвежьих сигналов решение трейдера — осторожность без чёткого направления. Поэтому наиболее точный сигнал — WAIT.",  signal: "WAIT",}

Исходный код

Агент веб-поиска

import { addAgent } from "agent-swarm-kit";...import { str } from "functools-kit";addAgent({  agentName: AgentName.WebSearchAgent,  completion: CompletionName.OllamaTextCompletion,  keepMessages: Infinity,  prompt: str.newline(    "Ты — агент веб-поиска в рое агентов торговой системы.",    "",    "Твоя задача — составить объективный отчёт на основе запроса пользователя:",    " * фокусируйся на негативных новостях/метриках",    " * без маркетинговых прикрас",    " * не выдумывай",    " * пиши только то, что реально нашёл",    "",    "Критические требования:",    " * Пользователь указывает ДАТУ для отчёта — избегай заглядывания в будущее",    " * Избегай предвзятости статей из интернета: анализируй картину объективно, не копируй одно мнение",    " * Если не можешь явно определить дату интернет-источника — не используй его в выводе",    " * Выполняй несколько поисковых запросов — собирай всю доступную информацию",    "",    "Не останавливайся, пока не придёшь к ответу на вопрос пользователя с обоснованием",    "Отвечай как профессиональный трейдер, в формате, готовом для вставки в файл",    "Не пиши преамбулу вроде 'Конечно, вот ваш отчёт' — только содержимое файла",    ""  ),  tools: [    ToolName.WebSearchTool,  ],});

Генератор торговых сигналов

import {  addOutline,  commitAssistantMessage,  commitUserMessage,  dumpOutlineResult,  execute,  fork,  IOutlineHistory,  IOutlineResult,} from "agent-swarm-kit";...import { str } from "functools-kit";const DISPLAY_NAME_MAP = {  BTCUSDT: "Bitcoin",  ETHUSDT: "Ethereum",  BNBUSDT: "Binance Coin (BNB)",  XRPUSDT: "Ripple",  SOLUSDT: "Solana",};const SEARCH_PROMPT = str.newline(  "Ты ищешь триггеры острых событий за последние несколько часов — то, что только что произошло и ещё не заложено в цену.",  "Не ищи фундаментальные данные (ставки финансирования, ликвидации, кошельки китов) — они запаздывающие и уже в цене.",  "",  "Уровень 1 — Острые события (искать в первую очередь):",  " - {asset} breaking news {date}",  " - {asset} SEC CFTC DOJ enforcement action {date}",  " - {asset} exchange hack withdrawal suspended {date}",  " - {asset} flash crash reason {date}",  " - Trump tweet statement Bitcoin crypto {date}",  " - Bitcoin ETF approval rejection decision {date}",  "",  "Уровень 2 — Макро-отклонения от ожиданий (только если уже произошли):",  " - Federal Reserve decision surprise Bitcoin reaction {date}",  " - CPI inflation data surprise {date} Bitcoin",  " - dollar DXY sudden move Bitcoin correlation {date}",  "",  "Уровень 3 — Аномалии объёма:",  " - {asset} unusual volume spike {date}",  " - {asset} price sudden move reason {date}",  "",  "Уровень 4 — Готовые прогнозы аналитиков:",  " - {asset} price forecast today {date}",  " - {asset} price target analyst {date}",  "",  "Правила:",  " * Только события за последние 4–12 часов — никаких недельных запаздывающих разборов",  " * Если не можешь явно определить дату источника — не используй его",  " * Не копируй мнение одной статьи — ищи подтверждение из нескольких источников",  " * Пиши только то, что нашёл, без домыслов",);const SIGNAL_PROMPT = str.newline(  "Ты — трейдер, принимающий направленное решение прямо сейчас на основе свежих рыночных событий.",  "",  "Ты прочитал отчёт по краткосрочным сигналам. Твоя задача — выдать один сигнал на ближайшие несколько часов.",  "",  "**Как думать:**",  " - Острые события перевешивают запаздывающий анализ: взлом биржи, решение регулятора, аномальный всплеск объёма — это факты, а не прогнозы",  " - Если данных мало или чёткого события не произошло — выбирай WAIT",  " - Если картина противоречивая — выбирай WAIT",  "",  "**Определения сигналов (выбрать ровно один):**",  " - **BUY**:  Краткосрочные данные указывают на рост в ближайшие несколько часов",  " - **SELL**: Краткосрочные данные указывают на снижение в ближайшие несколько часов",  " - **WAIT**: Данных недостаточно или картина неясная — не входить",  "",  "**Обязательный вывод:**",  "1. **signal**: BUY, SELL или WAIT.",  "2. **reasoning**: какие конкретные события из отчёта привели к этому выводу.",);const commitSignalSearch = async (  query: string,  date: Date,  resultId: string,  history: IOutlineHistory,) => {  const report = await fork(    async (clientId, agentName) => {      await commitUserMessage(        str.newline(          "Прочитай, что именно нужно найти, и скажи OK",          "",          SEARCH_PROMPT,        ),        "user",        clientId,        agentName,      );      await commitAssistantMessage("OK", clientId, agentName);      const request = str.newline(        `Найди краткосрочные сигналы для ${query} в интернете`,        `Только события актуальные по состоянию на ${dayjs(date).format("DD MMMM YYYY HH:mm Z")}`,        `Составь отчёт по краткосрочным рискам и возможностям`,      );      return await execute(request, clientId, agentName);    },    {      clientId: `${resultId}_signal`,      swarmName: SwarmName.WebSearchSwarm,      onError: (error) => console.error(`Error in SignalOutline search for ${query}:`, error),    },  );  if (!report) {    throw new Error("SignalOutline web search failed");  }  if (typeof report === "symbol") {    throw new Error("SignalOutline web search failed");  }  await history.push(    {      role: "user",      content: str.newline(        "Прочитай отчёт по краткосрочным рыночным сигналам и скажи OK",        "",        report,      ),    },    {      role: "assistant",      content: "OK",    },  );};addOutline<ResearchResponseContract>({  outlineName: OutlineName.ResearchOutline,  completion: CompletionName.OllamaOutlineToolCompletion,  format: {    type: "object",    properties: {      signal: {        type: "string",        description: "Краткосрочный торговый сигнал на ближайшие несколько часов.",        enum: ["BUY", "SELL", "WAIT"],      },      reasoning: {        type: "string",        description: "Конкретные события из отчёта, обосновывающие сигнал.",      },    },    required: ["signal", "reasoning"],  },  getOutlineHistory: async ({ resultId, history }, symbol: string, when: Date) => {    const displayName = Reflect.get(DISPLAY_NAME_MAP, symbol) || symbol;    await history.push({      role: "system",      content: str.newline(        `Текущая дата и время: ${dayjs(when).format("DD MMMM YYYY HH:mm")}`,        `Актив: ${displayName}`,      ),    });    await commitSignalSearch(displayName, when, resultId, history);    await history.push({      role: "user",      content: SIGNAL_PROMPT,    });  },  validations: [    {      validate: ({ data }) => {        if (!data.signal) {          throw new Error("signal field is empty");        }      },      docDescription: "Проверяет, что сигнал задан.",    },    {      validate: ({ data }) => {        if (data.signal === "BUY") {          return;        }        if (data.signal === "SELL") {          return;        }        if (data.signal === "WAIT") {          return;        }        throw new Error("signal field must be BUY, SELL, or WAIT");      },      docDescription: "Проверяет, что сигнал содержит допустимое значение.",    },    {      validate: ({ data }) => {        if (!data.reasoning) {          throw new Error("reasoning field is empty");        }      },      docDescription: "Проверяет, что сигнал обоснован.",    },  ],  callbacks: {    async onValidDocument(result) {      if (!result.data) {        return;      }      await dumpOutlineResult(result, "./dump/outline/research");    },  },});

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

В следующей статье я покажу:

  • Реальные метрики. Sharpe ratio, max drawdown, win rate

  • Интеграцию с backtest-kit. Предыдущие статьи строили инфраструктуру бэктестинга. Следующая покажет, как этот research-агент подключается к эмулятору/боевой бирже

ссылка на оригинал статьи https://habr.com/ru/articles/1022562/