
Всем привет! Я Александр Панов, разработчик 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 августа.
Конкурс для клиентов брокера Финам. Открыть счёт можно здесь.
Код агента оставляю здесь. Действуйте!
Автор: Alex_panov


