
Всем привет! Я Александр Панов, разработчик TradeAPI в Финам. Работая с биржевыми данными и следя за развитием LLM-агентов, я задался вопросом — а что если дать языковой модели доступ к бирже и посмотреть, сможет ли она систематически зарабатывать? Так появился этот эксперимент.
Сегодня разберем автономную ИИ-торговлю, реализуем своего ИИ-трейдера, которого вы можете запустить торговать уже сейчас (на виртуальном счете «Финам Арена» в 3 млн рублей и денежным призом) и покажем результаты наших запусков. В конце статьи — ссылка на полный код. Давайте приступать.
Вступление
Все началось с громкого выхода Alpha Arena от nof1.ai - исследовательской ИИ лаборатории, которая поставила себе амбициозную цель: создать ИИ нового поколения, обученный на финансовых данных. В рамках своего эксперимента они дали шести передовым моделям торговать криптоактивами в реальном времени. Вслед за ними похожие эксперименты запустили rockflow, ai4trade и в том числе «Финам».

Смогли ли они создать ИИ, который систематически обыгрывает рынок? Пока — нет. Исследователи из Гонконгского университета отдельно проверили способность передовых моделей торговать на бирже и пришли к выводу: общий интеллект не транслируется автоматически в торговую эффективность. Что мы, кстати, тоже скоро увидим на собственном опыте.
Так что будущее, в котором большие компании обирают простых трейдеров нескоро. Но это не повод расслабляться — скорее повод разобраться в этом и найти в этом новые возможности. Фондовый рынок в теории идеально подходит для ИИ-агентов: почти вся информация оцифрована, есть готовые API, данные структурированы. А теперь еще не нужно обучать нейросеть с нуля — знания о рынках, компаниях и макроэкономике уже сжаты в современных LLM и доступны каждому трейдеру, даже без глубокого понимания математики и статистики. К тому же большие языковые модели умеют делать то, что раньше могли только опытные аналитики: обрабатывать большой поток разнородной информации, выстраивать логические цепочки и формулировать обоснованные выводы. Вопрос не в том, умна ли модель, а в том, как правильно её применить. И вот здесь начинается самое интересное.
Торговая система
Прежде чем запускать ИИ-трейдера, нужно задать ему торговые рамки — что и как будет торговать. Вообще выбор активов и построение торговой стратегии — сама по себе нетривиальная задача, которую тоже можно решать с помощью ИИ. Но для первого запуска зафиксируем всё вручную.
Итак, агент торгует на российском фондовом рынке десятью бумагами: Сбербанк, Газпром, Яндекс, МТС, X5 Retail Group, Аэрофлот, АЛРОСА, Россети, Самолёт, ДВМП. Все голубые фишки — ликвидные, хорошо покрытые новостями, но при этом достаточно волатильные, чтобы было где зарабатывать и где ошибаться.
Торги будут проходить раз в день, под конец вечерней сессии Московской биржи. Именно в это время концентрируется основной объём и движение цен, что даёт агенту максимум информации для решения.
Задача агента — максимизировать доходность портфеля. Для этого ему доступны все ключевые источники: рыночные данные, новости, исторические цены и поиск в интернете. На их основе он должен рассуждать, строить гипотезы и принимать торговые решения — по сути, действовать как управляющий небольшим фондом.
Для реализации такого агента отлично подходит архитектура ReAct (Reasoning + Acting) — подход, в котором языковая модель чередует рассуждение и действие через внешние инструменты. Именно инструменты определяют реальные возможности агента: что он видит, как анализирует и какие решения может принимать.

Инструменты ИИ-трейдинга
Каждый инструмент — это функция с подробным описанием для модели: имя, назначение, входные параметры и формат возвращаемых данных. Именно из этих описаний LLM понимает, когда и как вызывать тот или иной инструмент. Чем точнее описание — тем предсказуемее поведение агента.
Разберём каждый инструмент по отдельности.
1. Рыночные данные
Агент может запрашивать исторические данные за любой промежуток времени с нужной гранулярностью — от минутных свечей до дневных. На их основе он строит картину рынка: смотрит общий тренд, оценивает волатильность, ищет уровни. Обычно он запрашивает данные за последний месяц-полтора.
Для этого используем «Финам TradeAPI» — метод исторических данных Bars и текущих котировок LastQuote. Получаем токен API и подключаем готовый SDK.
Реализуем инструмент get_price() для исторических данных:
@tool async def get_price( symbol: Symbol, start_time: datetime, end_time: datetime, timeframe: TimeFrame ) -> BarsResponse: """Read OHLCV data for specified stock and datetime. Get historical information for specified stock.""" return await get_finam_client().get_bars(symbol, start_time, end_time, timeframe)
Текущие котировки можно было реализовать аналогичным инструментом, но есть смысл загружать их заранее прямо в системный промпт — так агент видит актуальные цены с самого начала без лишнего вызова. Для этого напишем вспомогательную функцию get_price():
class Price(BaseModel): bid: Decimal ask: Decimal async def get_price(symbol: Symbol) -> Price: quote = await get_finam_client().get_last_quote(symbol) return Price( bid=Decimal(quote.quote.bid.value), ask=Decimal(quote.quote.ask.value) )
2. Новости
Второй источник информации — новостной поток. Агент получает свежие заголовки и краткие описания статей, на основе которых может оценить настроение рынка и отреагировать на важные события по конкретным компаниям.
В более сложной версии здесь можно добавить фильтрацию по тикерам и автоматический анализ сентимента. Но для начала обойдёмся простым решением: возвращаем все последние новости одним вызовом (это порядка 8 тыс. токенов, что вполне укладывается в контекст модели).
Используем RSS-поток Финама и библиотеку feedparser для парсинга в удобной для LLM форме:
RSS_URL = "https://www.finam.ru/analysis/conews/rsspoint/" @tool def get_news() -> list[str]: """Fetch latest financial news headlines from Finam RSS feed.""" response = requests.get(RSS_URL, timeout=10) response.raise_for_status() feed = feedparser.parse(response.text) return [(entry.title + ". " + entry.description.split('...')[0]) for entry in feed.entries]
3. Поиск в интернете
Новости дают оперативную картину, но не всегда достаточно контекста для взвешенного решения. Поэтому агент также умеет делать точечные поисковые запросы: искать финансовую отчётность компании, разбираться в её бизнес-модели или оценивать общую ситуацию в секторе.
Для этого используем Tavily — поисковый API, заточенный под нужды ИИ-агентов: он возвращает структурированные результаты с релевантными выдержками, а не сырой HTML. Удобно и экономно по токенам.
class SearchResult(BaseModel): title: str = Field(..., description="The title of the search result.") content: str = Field(..., description="A short description of the search result.") class SearchResponse(BaseModel): answer: str | None = Field(None, description="A short answer") results: list[SearchResult] tavily_client = TavilyClient(api_key=TAVILY_API_KEY) @tool def search(query: str) -> SearchResponse: """Use search tool to scrape and return main content information related to specified query in a structured way.""" response = tavily_client.search(query, max_results=5, topic="general", search_depth="basic", country="russia") return SearchResponse.model_validate(response)
4. Программирование
Предоставим LLM возможность писать и запускать Python-код. Это открывает широкие возможности: подсчёт технических индикаторов, построение скользящих средних, расчёт волатильности или любые другие вычисления, которые проще выразить кодом, чем описать в промпте (идея взята из подхода CodeAct).
Под капотом — базовая питоновская exec(). Благодаря персистентной сессии переменные и результаты вычислений сохраняются между вызовами инструмента в рамках одного запуска агента.
@tool def bash_python(code: str) -> str: """Execute Python code in bash and return result""" output, return_value = execute_code(code, session) # Format result: prioritize return value over output if return_value is not None: result = str(return_value) if output: result = f"{output}\\n{result}" return result return output
5. Торговые операции
Финальный результат, который мы ожидаем от агента — конкретные торговые решения, приносящие прибыль. Вариантов много: открытие длинных и коротких позиций, предсказание движения цены или даже модные ставки на будущие события через Polymarket. В нашем случае оставим всё просто — покупка и продажа акций.
Реализуем две функции: buy и sell. Для исполнения ордеров используем API брокера, но прежде чем торговать реальными средствами, рекомендую начать с демосчёта. А можно вообще попробовать его с виртуальными средствами на «Финам Арена» (о которой будет ниже), готовый клиент для этого в коде уже есть.
@tool async def buy(symbol: Symbol, amount: PositiveInt) -> OrderResponse: """Buy stock function""" order = OrderCreateRequest(symbol=symbol, quantity=FinamDecimal(value=str(amount)), side=Side.BUY) return await get_arena_client().place_order(order) @tool async def sell(symbol: Symbol, amount: PositiveInt) -> OrderResponse: """Sell stock function""" order = OrderCreateRequest(symbol=symbol, quantity=FinamDecimal(value=str(amount)), side=Side.SELL) return await get_arena_client().place_order(order)
Системный промпт
Итак, «руки» торгового ИИ-робота готовы. Теперь напишем прошивку — системный промпт, который определяет мышление и поведение агента.
Хороший системный промпт должен быть максимально ясным, конкретным и структурированным. Задаём роль («ты — управляющий портфелем»), цель («максимизировать доходность»), ограничения и важные примечания. Отдельно передаём контекст: текущее состояние портфеля и свежие котировки через написанную ранее get_price(). Составим такой Jinja-шаблон:
Вы — торговый ассистент по фундаментальному анализу акций, работающий на российских биржах (Московская биржа). Ваш торговый график: - Вы принимаете торговые решения в конце рабочего дня (18:00) биржи - Текущая сессия: {{ datetime }} - Следующее решение: через день Ваши цели: - Анализировать и принимать решения, используя доступные инструменты. - Вам необходимо анализировать котировки различных акций и их доходность. - Ваша долгосрочная цель — максимизировать доходность через данный портфель. - Перед принятием решений собирайте как можно больше информации через инструменты поиска для помощи в принятии решений. Стандарты анализа: - Чётко показывайте ключевые промежуточные шаги: - Изучайте данные о текущих позициях и котировках - Обновляйте оценку и корректируйте веса для каждого актива (если стратегия требует) Примечания: - Вам не нужно запрашивать разрешение пользователя во время операций, вы можете исполнять их напрямую - Вы ДОЛЖНЫ выполнять операции через вызов инструментов, простой вывод операций не будет принят - Вы можете торговать ТОЛЬКО акциями из списка котировок ниже - ПОКУПКА по цене ASK (вы платите по цене продавца) - ПРОДАЖА по цене BID (вы получаете по цене покупателя) СТАТУС ПОРТФЕЛЯ ({{ datetime }}) Текущие позиции: | Тикер | Кол-во | Цена | Стоимость | P&L | |-------|--------|------|-----------|-----| {% for pos in positions %}| {{ pos.symbol }} | {{ pos.quantity }} | {{ "%.2f"|format(pos.current_price) }} ₽ | {{ "%.2f"|format(pos.value) }} ₽ | {{ "%+.2f"|format(pos.unrealized_pnl) }} ₽ | {% endfor %}Денежные средства: {{ "%.2f"|format(cash) }} ₽ Общий баланс (equity): {{ "%.2f"|format(equity) }} ₽ Котировки ({{ datetime }}): | Тикер | Компания | BID (продажа) | ASK (покупка) | |-------|----------|---------------|---------------| {% for q in quotes %}| {{ q.symbol }} | {{ q.name }} | {{ "%.2f"|format(q.bid) }} ₽ | {{ "%.2f"|format(q.ask) }} ₽ | {% endfor %}
Промпт написан на русском — для наглядности. В реальных запусках лучше использовать английский: банально он компактнее и потребляет меньше токенов. Вообще каких-либо убедительных исследований на эту тему я не встречал (или может быть все-таки польский?)
В конечном итоге в отрендеренном виде перед каждым запуском агент получает следующее:

Собираем все вместе
Компонуем агента используя фреймворк LangChain. В качестве языковой модели используем ChatOpenAI класс, который поддерживает все OpenAI-совместимые API провайдеров или локальных моделей. В моем случае это OpenRouter - единый агрегатор всех LLM через единый API-ключ.
async def build_agent_graph(): system_prompt = await render_jinja_prompt() llm = ChatOpenAI(base_url=BASE_URL, model=f"{PROVIDER}/{MODEL}", api_key=OPENROUTER_API_KEY, temperature=0.1, max_retries=5, timeout=10) prompt = ChatPromptTemplate.from_messages([ SystemMessage(system_prompt), MessagesPlaceholder(variable_name="messages"), ]) tools = [bash_python, get_news, get_price, search, buy, sell] agent = prompt | llm.bind_tools(tools) async def call_model(state: AgentGraphState): try: response = await agent.ainvoke(state) except Exception as e: logger.error(f"Error in call_model: {e}") response = AIMessage(content=f"Произошла ошибка при обработке запроса: {str(e)}") return {"messages": [response]} # Построение графа builder = StateGraph(AgentGraphState) builder.add_node("model", call_model) builder.add_node("tools", ToolNode(tools, handle_tool_errors=True)) # Маршрутизация builder.add_edge(START, "model") builder.add_conditional_edges("model", tools_condition) builder.add_edge("tools", "model") return builder.compile()
Здесь в графе реализован классический ReAct цикл: рассуждение, вызов инструмента, получение результата — и снова по кругу, пока модель сама не решит остановиться. LangGraph берет на себя всю маршрутизацию, параллельный запуск инструментов, сбор результатов и обработку ошибок.
Создаем задачу cron на запуск агента каждый будний день в 18:00, под конец вечерней сессии:
0 18 * * 1-5 python src/main.py
Запуск
Кстати, от того же LangChain есть классная платформа трейсинга LangSmith. Подключается через env-переменные и позволяет в реальном времени видеть весь мыслительный процесс агента: о чем думал, какие инструменты вызывал, с какими параметрами, что получил в ответ и где возникли проблемы.

Агент запущен. Что по доходности?
Мы в Финам дали шести ведущим моделям торговать на российском и американском рынках — каждой по 100 000 ₽ и $10 000, с 1 февраля по 1 апреля (39 торговых дней). Подробный разбор в отдельной статье, покажу графики доходности.


Результаты выглядят пока не серьезно, мало статистической значимости, период небольшой. Но положительные сигналы есть: агент умеет читать новостной фон, формировать портфель и в определённые моменты обыгрывать индекс.
Следующая задача — снять ограничения: расширить набор инструментов (короткие позиции, опционы, фьючерсы, деривативы), добавить риск-менеджмент и дать агенту возможность самостоятельно искать возможности на всём рынке.
Дальнейшие работы
Мы на личном примере убедились, что задача не в том, чтобы взять умную модель — а в том, чтобы построить систему с этой умной моделью. Вот несколько идей, что делать дальше:
Коллективный разум. Что если решение принимает не одна модель, а несколько — с разными специализациями, разными промптами, разными взглядами на рынок?
Гибридный подход. Сейчас ИИ-агент — медленная вдумчивая система: анализирует, рассуждает, принимает решения раз в день. Классические алгоритмы быстрее и точнее в узких задачах. Что если создать гибрид таких систем?
Продвинутые инструменты: Анализ сентиментов новостей, технические индикаторы, стоп-лосс и тейк-профит ордера и другие инструменты трейдера применимы и здесь.
Список можно продолжать. ИИ-трейдинг — это только зарождающееся направление, где пока больше вопросов чем ответов. Но именно это делает его интересным.
Сейчас проходит конкурс по алготрейдингу — «Финам Арена». Регистрируйтесь, получайте 3 млн рублей в управление через API и запускайте сегодняшнего агента! Лучшие стратегии смогут разделить призовой фонд в 300 тысяч и привлечь реальное инвестирование.
Регистрация открыта до 1 июля 2026 года, торги — с 1 июня по 1 августа.
Конкурс для клиентов брокера Финам. Открыть счёт можно здесь.
Код агента оставляю здесь. Действуйте!
