Исходный код торговой стратегии опубликован по ссылке
При торговле на рынке можно наблюдать ситуацию, когда скользящие средние резко меняют своё направление. Индикатор калибруется на исторических данных и предполагает стабильный режим. Технический анализ рекомендует использовать разный набор индикаторов при изменении режима рынка, но это не работает.

Однако возникает вопрос: как же они работали раньше? В чём дело?
Что изменилось?
Новостной сентимент определяет режим. Индикатор работает внутри режима.

Практическое руководство как определить сентимент
Нужен векторный поиск по новостям. Его легко реализовать используя Scrapy + PostgreSQL + PgVector или Scrapy + MongoDB Atlas Vector Search. См. VectorEmbeddings + Cosine Distance. Я использую SaaS-решение, которого хватает в рамках free tier — tavily.com. Также подойдёт Perplexity Search API. Ниже — руководство по построению поискового запроса для получения новостного сентимента.
1. Score как критерий сортировки, но не фильтра
Слово «Трамп» не равно «Биткоин», но по смыслу подразумевает спекуляцию на рынке. Результаты с максимальным score воспринимаются аудиторией как прямая реклама. Нулевой score означает отсутствие упоминания биткоина. Искать нужно околонулевой score — именно он формирует настроение рынка.
Это работает по принципу продакт-плейсмента: пиво Corona в «Форсаже» или смартфон Sony в фильме о Джеймсе Бонде.
2. Домен первичен по отношению к поисковому запросу
Искать «SEC crypto enforcement action lawsuit» — ошибка. Настроение рынка формируют конкретные домены и блоги — первопроходцы рынка. Если они не сделают репост нового документа SEC, его никто не увидит. SEC влияет на розничного инвестора только через посредников: публикация регулятора сама по себе не двигает рынок — его двигает пост авторитетного блогера.
3. Время первично по отношению к смыслу публикации
Усреднение убивает направленность — позитив утра компенсирует негатив вечера. Сентимент схлопывается в шум. Новость и предшествует импульсу, и поддерживает продолжение движения цены через каскад. Критерием наличия каскада является статистический рост количества публикаций в единицу времени. Каждая публикация по отдельности не отражает будущее целенаправленно — будущее это синергетический эффект совокупности публикаций.
4. Нужно искать тот фундаментал, который закладывают в будущее, а не придумывать свой
Критически важно использовать именно VectorEmbeddings + Cosine Distance, а не LLM для поиска сентимента. LLM увидит ключевое слово «Перекупленный RSI» и сформирует собственный вывод о движении рынка. Задача — найти не интерпретацию, а настроение, которое авторитетные участники закладывают в рынок. См. RAG Embedding Models.
5. Количество публикаций увеличивает шум
Можно грамотно построить систему определения рыночного настроения, но сами источники при этом могут не иметь аудитории. Калибровать нужно не промпт, а набор авторитетов, чья рекомендация реально влияет на розничного инвестора.
Важно
LLM и векторный поиск новостей нужно разделить физически. Векторный поиск следует осуществлять по смыслу слова «прогноз», тогда как LLM должна анализировать настроение рынка. В противном случае на выходе будет получена не закладываемая фундаментальная тенденция, которой предшествует изменение настроения участников рынка, а интерпретация индикаторов технического анализа.
Проверяем гипотезу
Кейс 1. Нейтрально-медвежий тренд
Используя упомянутые выше рекомендации, я сформировал поисковой запрос. В Tavily встроена LLM, которая делает краткое summary по новостям.
-
Поисковой запрос дал результат: нейтрально-медвежий сентимент Show Image

-
Реакция рынка

Кейс 2. Бычий сентимент
Повторим эксперимент на другой дате.
-
Поисковой запрос дал результат: бычий сентимент

-
Реакция рынка

Рекомендации по временному окну поиска
1. Не все новостные агентства указывают время публикации. Чтобы исключить look-ahead bias, их придётся убрать из выборки. Среди них:
В базе Tavily таким публикациям присваивается время Thu, ?? Jan ???? 00:00:00 GMT. Чтобы отфильтровать их, необходимо привести время к UTC — иначе смещение будет рассчитываться по вашему часовому поясу:
const hour = dayjs(publishedDate).utc().get("hour");
const minute = dayjs(publishedDate).utc().get("minute");
if (hour === 0 && minute === 0) {
console.warn(`fetchNews search invalid publishedDate query=${query} url=${url} from=${from} to=${to}`)
return false;
}
2. Запрашивайте данные за -2 дня и фильтруйте последние 24 часа на своей стороне.
При поиске Tavily использует дату без уточнения времени, поэтому на границе 23:59–00:00 публикации будут теряться. Даже при идеальном парсере сайтов возникает проблема распределённых CDN-баз данных: одна и та же новость поступает читателям не одновременно, а по мере высвобождения серверных ресурсов.
3. Не пытайтесь опередить рынок.
Усреднение убивает направленность: позитив утра компенсирует негатив вечера, и сентимент стремится к шуму. Но если взять окно менее 24 часов, интерпретация сентимента становится неоднозначной: Трамп наносит удары по Ирану — непонятно, упадёт ли биткоин до нуля или вырастет. Окно в 24 часа оптимально для понимания контекста и отсечения шума.
Бектест
Все вышеупомянутые рекомендации удалось автоматизировать. ИИ-агент находит новостной сигнал:

И удерживает позицию до исчерпания новостного сентимента:

При этом для риск-менеджмента выставляется статистически недостижимый hard stop и trailing take-profit.
Почему не работают индикаторы
Торговый режим пытаются отладить на горизонте месяца, тогда как сентимент чередуется каждый день: медвежий — бычий — бычий — медвежий. В итоге обе стратегии — и бычья, и медвежья — выходят на уровень 50/50.
Что можно улучшить
Если выходить из позиции на изменении сентимента, часть прибыли будет упущена из-за задержки парсера новостей. Решение: выход на откате 3% от максимального PnL удачной позиции.

Это означает недоимку в 3% PnL, умноженную на 10 позиций — потенциально +30% к существующим 16%. При этом на момент открытой позиции новостной сентимент предсказуем. Как думаете, стоит ли попробовать использовать индикатор для выхода из позиции?
Спасибо за внимание!
Автор: tripolskypetr


