← К списку статей
June 25, 2026
5 мин. чтения

researcher: архив количественных исследований с поиском для людей и ИИ-агентов

researcher: архив количественных исследований с поиском для людей и ИИ-агентов
#researcher
#quant
#arxiv
#meilisearch
#mcp
#ai-agents
#research
#algotrading

Количественные исследования есть везде и нигде одновременно. Нужная статья лежит на arXiv. Эталонная реализация — на GitHub. Интуиция спрятана в посте блога, который кто-то написал в 2019 году. А сама логика входов и выходов — это Pine-скрипт на TradingView с 400 лайками и без всякой документации. Четыре корпуса, четыре строки поиска, четыре набора соглашений и ноль перекрестных ссылок. Когда вы пытаетесь решить, стоит ли идея целой недели бэктестинга, именно эта фрагментация и есть настоящая цена — не чтение, а поиск.

Поэтому мы построили собственное решение. researcher.marketmaker.cc — это курируемый архив и поисковая система для исследований в области количественной торговли. Он собирает воедино материалы, которые обычно разбросаны по arXiv, GitHub, квант-блогам и TradingView, индексирует все это для полнотекстового поиска и — а вот это для нас важнее всего — открывает весь корпус ИИ-агентам через эндпоинт Model Context Protocol (MCP) и публичный REST API. Это исследовательский субстрат, по которому человек может листать с клавиатуры, а агент — обращаться через вызов инструмента, причем оба опираются на одни и те же индексы.

Этот пост — экскурсия по тому, что находится внутри, как это устроено и почему оно занимает именно такое место в нашем стеке ИИ-агентов.

Что внутри корпуса

Четыре изолированных хранилища данных — статьи, репозитории кода, статьи блогов и скрипты графиков — сходятся в единый индекс

researcher объединяет четыре основных набора данных, у каждого свой полнотекстовый индекс. Цифры ниже актуальны на 2026-06-12, и они меняются — конвейер arXiv работает ежедневно, а индекс пересобирается из исходников, так что числа растут.

Набор данных Источник Документы По чему ищете
Статьи arXiv q-fin (1997–2026) ~18 647 заголовок, аннотация, авторы (фильтр по категории)
Код репозитории GitHub ~12 957 имя, описание, темы (фильтр по языку, звездам)
Статьи блогов квант-блоги ~4 633 заголовок, описание (фильтр по источнику, дате)
Стратегии Pine-скрипты TradingView ~15 180 заголовок, описание, теги (фильтр по категории)

Это чуть больше 51 000 документов по четырем поисковым индексам. Индекс статей — самый крупный отдельный корпус, и именно в него мы вложили больше всего труда: это полный поток количественных финансов с arXiv (q-fin.*), уходящий вглубь до 1997 года, а не отобранное вручную подмножество. Раньше сайт поставлялся всего с несколькими сотнями курируемых статей; нынешний индекс — это полный корпус q-fin, поверх которого подмешано курируемое происхождение, так что статья, на которую также ссылался конкретный квант-блог, несет эту атрибуцию.

Помимо четырех поисковых индексов, сайт для людей наслаивает сверху и многое другое: исследовательские заметки и ежедневные дайджесты, которые мы пишем сами, каталог квант-сайтов и авторов, раздел видео с индексацией релевантных YouTube-каналов и каталог фондов. Четыре индекса — это поисковый хребет; все остальное — это курирование вокруг него.

Единый источник истины

Одно каноническое ядро данных параллельно питает поисковый индекс и представление приложения — единый источник истины

То, что незаметно ломалось у нас на раннем этапе — и от чего мы теперь жестко защищаемся проектированием, — это рассогласование счетчиков. Главная страница показывала одно число, поиск возвращал другое, API — третье. В какой-то момент на главной значилось 719 статей, а поиск возвращал больше 18 000. Ничто так не разъедает доверие к исследовательскому инструменту, как корпус, который не может договориться сам с собой о собственном размере.

Решение было в том, чтобы заставить каждую поверхность читать из одного места. Для корпуса статей Meilisearch — это источник истины. Никакой второй копии статей в бандле приложения нет; счетчик на главной, счетчик на /papers, счетчик, возвращаемый API, и документы, по которым вы реально ищете, — это все один и тот же индекс. Сам корпус собирается офлайн на шаге загрузки, который берет курируемый набор статей, объединяет его с потоком arXiv (дедупликация по arXiv id, со слиянием массивов происхождения), сортирует по убыванию даты и записывает единый файл размером ~25 МБ, который потребляет индексатор. Этот файл содержит примерно 15 000–18 000 записей и намеренно не зашит в клиент — корпусу такого размера нечего делать в браузере.

Остальные три набора данных (код, статьи блогов, Pine-скрипты) читаются на стороне сервера из своих JSON-файлов и индексируются из тех же файлов, а миграция на Meili как источник истины — запланированный следующий шаг. Правило сквозное: читать данные на сервере, никогда не делать import многомегабайтного набора данных в клиентский бандл и брать индекс и отображаемые счетчики из одного источника. Рассинхрон становится структурно невозможным.

Поиск: полнотекстовый, устойчивый к опечаткам, фасетный

Полнотекстовый, устойчивый к опечаткам, фасетный поиск: луч запроса входит в индекс и возвращает ранжированный список результатов с подсветкой

Поисковый движок — это Meilisearch, работающий на том же сервере, что и приложение, и привязанный к localhost — публично он не выставлен. Мы используем его как полнотекстовый поисковый движок, а не как векторное хранилище. Никаких эмбеддингов, никакой магии семантической близости. Для запроса в духе "найди мне статьи и репозитории, упоминающие эту концепцию" устойчивый к опечаткам лексический поиск по заголовкам, аннотациям, описаниям, авторам и тегам быстр, предсказуем и поддается отладке так, как индекс эмбеддингов не поддается. Каждый индекс хранит полный исходный документ плюс добавленный _id, так что поиск возвращает полностью наполненные записи, которые приложение может отрисовать напрямую — без второго запроса для регидрации попаданий.

Несколько деталей, которые важны на практике:

  • Устойчивость к опечаткам и ранжирование по релевантности достаются от Meilisearch бесплатно. Поиск по momentm все равно находит статьи про momentum; результаты ранжируются, а не просто фильтруются.

  • Фасетная фильтрация. Статьи фильтруются по arXiv category (q-fin.PM, q-fin.TR, …), репозитории — по language и stars, статьи блогов — по source и date, Pine-скрипты — по category. Страница /papers строит свой выпадающий список категорий из живого распределения фасетов индекса, так что варианты фильтра всегда отражают то, что действительно есть в корпусе.

  • Разбиение camelCase. Meilisearch токенизирует по пробелам и пунктуации, но не по camelCase. Это значит, что репозиторий, буквально названный TradingAgents, был бы единым токеном, недостижимым по естественному запросу "trading agents". Во время индексации мы выводим поле name_splitTradingAgentsTradingAgents Trading Agents, ai-hedge-fundai-hedge-fund ai hedge fund — и добавляем его в поисковые атрибуты. Исходный токен сохраняется первым, чтобы точные совпадения по имени по-прежнему ранжировались выше всего, а производное поле клиентам никогда не возвращается. Мелочь, которая определяет разницу между тем, нашелся флагманский репозиторий или нет.

  • Просмотр = сначала самое новое. Пустой запрос — это не ошибка; это путь просмотра. На /papers пустой запрос сортирует по published по убыванию, так что страница заодно работает как обратно-хронологическая лента свежих исследований q-fin.

  • Боковой индекс для агрегатов. Некоторые числа Meilisearch не может дешево вычислить во время запроса — суммарное число звезд по всем репозиториям, общее количество файлов Python, общее число ноутбуков. Вместо того чтобы сканировать весь корпус при каждой загрузке страницы, индексатор записывает эти суммы один раз, во время индексации, в маленький индекс researcher_meta, хранящий по одному документу на набор данных. Эндпоинт stats читает их обратно напрямую. Счетчики, меняющиеся только при переиндексации, вычисляются только при переиндексации.

  • Потолок пагинации, которым реально можно пользоваться. Индекс статей поднимает maxTotalHits Meilisearch до 50 000 и помечает published как сортируемое, так что вы можете уходить вглубь по корпусу из ~18 тыс. документов и сортировать его целиком по убыванию даты — а не только первую страницу попаданий по релевантности.

Индексатор идемпотентен: он создает каждый индекс, если его нет, заново применяет настройки и делает upsert каждого документа пакетами по 2000, ключуя по _id (статьи выводят свой из arXiv id, репозитории и статьи блогов — из хеша URL). Поскольку весь индекс реконструируется из исходного файла, нет резервной копии, которой надо управлять — откат — это просто переиндексация. Повторный запуск безопасен по своей природе.

Доступ для агентов: MCP и публичный API

Узлы автономных ИИ-агентов подключаются через MCP и поверхность публичного API к центральному исследовательскому корпусу

Вот часть, которая связывает researcher со всем остальным, чем мы занимаемся. Корпус — это не просто сайт со строкой поиска, это инструмент, который может вызвать ИИ-агент.

Эндпоинт MCP

researcher выставляет сервер Model Context Protocol по адресу /api/mcp поверх Streamable HTTP. Любой MCP-совместимый агент — Claude, кастомный агент в нашем собственном стеке, что угодно, что говорит на протоколе — может подключиться и вызывать инструменты только для чтения против живого корпуса. Инструментов 13, сгруппированных по наборам данных, в едином виде search / get / list:

Группа Инструменты
Статьи search_papers, get_paper, list_papers
Код search_repos, get_repo, list_repos
Статьи блогов search_articles, get_article, list_articles_by_site
Стратегии search_pine, get_pine_script, list_pine
Знания knowledge_query (мягкая заглушка, зарезервирована под будущий слой графа)

Схемы инструментов написаны в интересах агента, а не человека. search_papers, например, представляет себя как устойчивый к опечаткам, ранжированный по релевантности поиск по заголовку, аннотации и авторам, с опциональным фильтром category (например, q-fin.PM) и лимитом результатов — и подсказывает агенту вызвать get_paper за полной аннотацией, когда он сузит выборку. search возвращает компактные попадания размером со сниппет, чтобы агент мог дешево просмотреть много результатов; get возвращает полную запись, когда он выбрал одну. Этот двухшаговый вид не дает контекстному окну агента утонуть в аннотациях, которые ему не нужны.

Конкретно: агент, исследующий, скажем, оптимальное исполнение, может запустить search_papers("optimal execution", category: "q-fin.TR"), чтобы получить ранжированный короткий список заголовков и сниппетов, search_repos("optimal execution", language: "Python"), чтобы найти реализации, отсортированные по релевантности и фильтруемые по звездам, и search_pine("VWAP"), чтобы увидеть, как та же идея проявляется в виде опубликованной стратегии TradingView — три вызова инструментов против трех корпусов, которые еще час назад были тремя разными сайтами. Затем единственный get_paper подтягивает полную аннотацию для той статьи, что показалась перспективной. Агент ни разу не покидает протокол, и каждый результат — это реальная, наполненная запись, а не заглушка результата поиска, которую надо идти запрашивать заново.

Публичный REST API

Для не-MCP-потребителей есть параллельная REST-поверхность под /api/v1/: papers, repos, articles, pine и агрегат stats. Она говорит на чистом JSON с параметрами q, category, limit и offset, возвращает истинное общее число и распределение фасетов рядом с каждой страницей и поддерживает CORS. GET /api/v1/papers?q=optimal+execution&category=q-fin.TR — это однострочник откуда угодно. Тот же эндпоинт питает собственную страницу /papers сайта — браузер просто еще один клиент API.

Честный отказ

Поисковый бэкенд, который врет, хуже того, что лежит. Мы заняли осознанную позицию по тому, что происходит, когда Meilisearch недоступен. Слой данных бросает исключение при сбое, а не молча возвращает пустые результаты, — и вызывающая сторона решает, как это обработать. Для наборов данных, у которых все еще есть копия в памяти, инструменты откатываются к обычному .filter() по этой копии, так что сайт остается работать. Для статей, где Meilisearch и есть источник истины и второй копии нет, инструменты и API возвращают явную ошибку (API отвечает 503), а не отдают устаревшие или частичные данные. У каждого вызова поиска короткий таймаут, чтобы зависший индекс не подвесил инструмент. Принцип: деградируй громко, никогда не отдавай тихо неверные ответы.

Как данные попадают внутрь

Конвейер загрузки: внешние источники проходят через этапы извлечения и нормализации в единый индекс

Корпус питается конвейером скрейперов, все работают против бесплатных публичных источников.

  • Статьи приходят из Atom API arXiv. Сборщик подтягивает полный корпус q-fin в JSONL, шаг сборки объединяет его с курируемым набором (дедупликация по arXiv id, слияние происхождения), и результат передается индексатору. У сборщика также есть режим "обогати вот эти конкретные id" для затравки из внешних списков чтения.
  • Код — это обход релевантных для квантов репозиториев GitHub, захваченных с метаданными, которые важны для фильтрации: звезды, форки, основной язык, темы и счетчики файлов Python и ноутбуков.
  • Статьи блогов скрейпятся из квант-блогов и агрегаторов, причем лучшие зеркалируются локально, чтобы пережить отмирание ссылок. Главная помечает, для каких статей мы сохранили локальную копию.
  • Стратегии — это Pine-скрипты TradingView с их метаданными: автор, категория, теги, лайки и наличие в листинге кода, графика или анализа.
  • Видео индексируют релевантные YouTube-каналы, чтобы доклады и разборы можно было найти рядом с письменным материалом.

Переиндексация в продакшене идет через SSH-туннель к привязанному к localhost Meilisearch, потому что движок никогда не выставлен в интернет. Весь цикл — сбор, сборка, деплой, индексация — спроектирован так, чтобы запускаться повторно идемпотентно, что и делает ежедневный cron.

Доступ и хостинг

Безопасный аутентифицированный доступ к размещенному сервису: ворота аутентификации перед светящимся стеком сервера

researcher работает на нашем Server 1 как небольшой стек Docker Compose: контейнер Next.js за Traefik и контейнер Meilisearch, привязанный к localhost. Приложение Next.js читает свои наборы данных на стороне сервера и общается с Meilisearch по внутренней сети.

Доступ закрыт через auth.marketmaker.cc, наш общий сервис идентификации. Токены — это JWT с RS256, проверяемые против JWKS сервиса аутентификации — каждое решение об авторизации проверяет подпись (со строгими проверками издателя и алгоритма, с отказом по умолчанию, если эндпоинт ключей недоступен), а путь декодирования без проверки используется только для косметики UI вроде показа вашего email в навбаре. Сервис аутентификации выдает роли для каждого сервиса; на researcher роль admin закрывает внутреннюю административную зону (где мы запускаем и мониторим скрейперы), а публичной главной странице токен не нужен вовсе. Это та же ткань аутентификации, что стоит перед остальными нашими внутренними инструментами, так что один вход переносится через всю экосистему.

Где это место в стеке Marketmaker

Где исследовательский архив встраивается в стек Marketmaker: центральный субстрат, питающий ИИ-агентов и исследователей-людей

researcher — это инфраструктура, а не пункт назначения. Дело не в сайте — дело в том, что у нас теперь есть запрашиваемое представление об области, которое разделяют и люди, и агенты.

Для нас как для людей это место, откуда берется немалая часть вот этого самого блога. Когда мы разбираем инструмент вроде VectorBT или препарируем фреймворк вроде TradingAgents или Fincept Terminal, отправной точкой часто служит поиск по researcher: на какие статьи это опирается, какие другие репозитории решают ту же задачу, кто об этом писал. Архив — это воронка; посты блога — это то, что из нее выпадает.

Для наших ИИ-агентов это нечто более структурное. Исследовательский субстрат, достижимый через MCP, означает, что агент, занятый работой над стратегией, не должен скрейпить arXiv вживую, жонглировать четырьмя разными API или гадать, что вообще там есть — он вызывает search_papers, search_repos, search_pine против корпуса, который уже унифицирован, дедуплицирован и проиндексирован. Это то же направление, что и наш инструментарий command-and-operate (cmdop) и агентов: давать агентам типизированные, доступные только для чтения, хорошо документированные инструменты поверх реальных данных, громко падать, когда бэкенд недоступен, и позволять одному общему бэкенду обслуживать пользовательский UI и машинный интерфейс из идентичных индексов. Человек листает, а агент запрашивает — но они смотрят на один и тот же архив, и в этом вся суть.

Заключение

researcher начинался как лекарство от маленькой, раздражающей проблемы — того, что количественные исследования разбросаны по четырем местам, которые друг с другом не разговаривают, — и превратился в то, на что мы опираемся ежедневно. Примерно 51 000 документов по статьям, коду, статьям блогов и стратегиям, все за одним полнотекстовым поисковым движком, все достижимое и человеком с браузером, и агентом с MCP-клиентом. Это намеренно негламурно: полнотекстовый поиск, а не эмбеддинги; единый источник истины, а не хитрый кэш; инструменты, которые бросают честные ошибки, а не те, что заметают сбои под ковер.

Если вы строите агентов для торговых исследований, урок обобщается за пределы нашего конкретного корпуса: самое высокорычажное, что вы можете дать агенту, — это не модель побольше, а чистое, унифицированное, запрашиваемое представление данных, которые ему нужны, — открытое через те же индексы, которым доверяют люди. Вот что такое researcher.

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

Авторы

Eugen Soloviov
Eugen Soloviov

Инженер торговых систем

Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.

Newsletter

Будьте в курсе событий

Подпишитесь на нашу рассылку, чтобы получать эксклюзивную аналитику по AI-трейдингу и обновления платформы.

Мы уважаем вашу конфиденциальность. Отписаться можно в любой момент.