Всем привет! Я Александр Панов, разработчик 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 торговых дней). Подробный разбор в отдельной статье, покажу графики доходности.

Российский рынок оказался боковым, большинство агентов закончили вблизи нуля. Лучший результат у ИИ-ансамбля — +1,67%
Российский рынок оказался боковым, большинство агентов закончили вблизи нуля. Лучший результат у ИИ-ансамбля+1,67%
Американский рынок падал, большинство агентов падали вместе с индексом. Gemini 3 Flash Preview единственный смог остаться в плюсе — +0,38%
Американский рынок падал, большинство агентов падали вместе с индексом. Gemini 3 Flash Preview единственный смог остаться в плюсе — +0,38%

Результаты выглядят пока не серьезно, мало статистической значимости, период небольшой. Но положительные сигналы есть: агент умеет читать новостной фон, формировать портфель и в определённые моменты обыгрывать индекс.

Следующая задача — снять ограничения: расширить набор инструментов (короткие позиции, опционы, фьючерсы, деривативы), добавить риск-менеджмент и дать агенту возможность самостоятельно искать возможности на всём рынке.

Дальнейшие работы

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

Коллективный разум. Что если решение принимает не одна модель, а несколько — с разными специализациями, разными промптами, разными взглядами на рынок?

Гибридный подход. Сейчас ИИ-агент — медленная вдумчивая система: анализирует, рассуждает, принимает решения раз в день. Классические алгоритмы быстрее и точнее в узких задачах. Что если создать гибрид таких систем?

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

Список можно продолжать. ИИ-трейдинг — это только зарождающееся направление, где пока больше вопросов чем ответов. Но именно это делает его интересным.

Сейчас проходит конкурс по алготрейдингу — «Финам Арена». Регистрируйтесь, получайте 3 млн рублей в управление через API и запускайте сегодняшнего агента! Лучшие стратегии смогут разделить призовой фонд в 300 тысяч и привлечь реальное инвестирование.

Регистрация открыта до 1 июля 2026 года, торги — с 1 июня по 1 августа.

Конкурс для клиентов брокера Финам. Открыть счёт можно здесь.

Код агента оставляю здесь. Действуйте!