
У ручной торговли есть предел, и он не связан с опытом. Вы не можете одновременно следить за десятками инструментов, реагировать на движение цены за доли секунды и при этом сохранять холодную голову. Когда рынок падает — рука тянется закрыть позицию. Когда растет — жалко продавать. Эмоции и скорость реакции — два барьера, которые мешают торговать по плану.
Алгоритм этих барьеров не знает. Он отрабатывает сигнал мгновенно, торгует строго по заданным правилам и не передумывает в последний момент. Именно поэтому алгоритмическая торговля давно стала стандартом среди профессионалов рынка.
И хорошая новость: чтобы попробовать, не нужно быть программистом. Запустить первого торгового робота проще, чем кажется. БКС предоставляет открытый API — через него ваша программа может получать котировки и выставлять заявки напрямую, без терминала и ручных кликов.
Сегодня напишем первый рабочий скрипт: он узнает текущую цену Сбербанка и выставит заявку на покупку. Все на стандартном Python, без сторонних библиотек — буквально 15 минут от начала до результата.
В примерах используем акции Сбербанка (SBER) — один из самых ликвидных инструментов Мосбиржи. Логика универсальна: заменить тикер на любой другой инструмент — дело одной строки в коде.
Прежде чем перейти к делу — разберем момент, который часто сбивает с толку новичков. Это сэкономит вам много времени.
Адреса запросов — это не сайты для браузера
В коде вы встретите адреса вроде be.broker.ru/...orders. В браузер их вставлять не нужно — это не страницы, а адреса, по которым скрипт сам отправляет запросы. Всю работу с ними берет на себя код.
Код пишется в файл и запускается двумя способами
Сам скрипт — это обычный текстовый файл с расширением .py (например, trade.py). Вы создаете файл, вставляете в него код и запускаете командой python3 trade.py. Запустить его можно двумя способами:
| На своем компьютере | На VPS-сервере |
|---|---|
| Подходит, чтобы быстро попробовать и понять, как все работает. Нужен только установленный Python. Минус: скрипт работает, только пока компьютер включен и есть интернет. Закрыли ноутбук — робот остановился. | Подходит для постоянной работы робота. VPS — это удаленный компьютер в дата-центре, который работает круглосуточно и не зависит от вашего интернета. Это рекомендуемый вариант для реальной торговли. |
Принцип в обоих случаях одинаковый: создаете файл, вставляете код, запускаете. Разница только в том, где этот файл живет и как долго работает.
Хотите, чтобы робот работал 24/7? Тогда вам нужен VPS. Как его выбрать, арендовать и подключиться к нему — мы подробно разобрали в первых двух статьях серии:
Дальше команды показаны для Linux-сервера — это операционная система, на которой обычно работают VPS (такой мы настраивали в Статье 2). Если запускаете на своём компьютере, код будет тот же самый. Отличается только команда запуска: на Linux и macOS — python3 trade.py, на Windows — python trade.py.
Чтобы скрипт мог действовать от вашего имени — запрашивать данные и выставлять заявки — ему нужен ключ доступа. Этот ключ называется refresh-токен, и выпускается он в личном кабинете БКС. Делается один раз, перед началом работы.
Выпустить токенТокен показывается только один раз — сразу после выпуска. Скопируйте его и сохраните в надежном месте. Это ключ от вашего торгового счета: не передавайте его третьим лицам и не публикуйте в открытом коде. Если потеряете — просто выпустите новый.
Зачем это нужно
Токен, который вы выпустили в личном кабинете, — это refresh-токен, долгоживущий ключ на 90 дней. Но напрямую для запросов он не используется. Перед работой его нужно обменять на access-токен — короткоживущий ключ на 24 часа, которым и подписываются все запросы к API.
Зачем так сложно? Ради безопасности. Refresh-токен хранится у вас и никуда не отправляется в каждом запросе. А «в эфире» работает только access-токен — и даже если его перехватят, через сутки он станет бесполезным.
На практике вам не нужно вникать в эти тонкости — скрипт сделает обмен сам. Для этого он обращается к адресу авторизации и передает три параметра:
| Параметр | Значение | Описание |
|---|---|---|
| client_id | trade-api-write | Тип токена. trade-api-write — для торговли, trade-api-read — только для чтения данных |
| refresh_token | <ВАШ_ТОКЕН> | Refresh-токен из личного кабинета БКС |
| grant_type | refresh_token | Тип запроса. Всегда передаём значение refresh_token |
Создаем файл и пишем первый блок
Создаем файл скрипта. На Linux-сервере это делается командой в терминале (на своем компьютере можно создать файл trade.py в любом текстовом редакторе):
nano trade.py
Откроется пустой редактор. Вставляем в него первый блок — настройки и авторизацию:
import http.client
import json
import uuid
# ── Настройки — заполните перед запуском ──────────────────────────────
REFRESH_TOKEN = "ВАШ_REFRESH_ТОКЕН_ИЗ_ЛК_БКС"
TICKER = "SBER"
CLASS_CODE = "TQBR"
QUANTITY = 1 # количество лотов
DISCOUNT = 0.99 # выставляем на 1% ниже рынка
HOST = "be.broker.ru"
# ── Шаг 1: получаем access-токен ──────────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
payload = json.dumps({
"client_id": "trade-api-write",
"refresh_token": REFRESH_TOKEN,
"grant_type": "refresh_token"
})
conn.request(
"POST",
"/trade-api-keycloak/realms/tradeapi/protocol/openid-connect/token",
payload,
{"Content-Type": "application/json"}
)
auth_data = json.loads(conn.getresponse().read().decode("utf-8"))
ACCESS_TOKEN = auth_data["access_token"]
print("[1/3] Access-токен получен ✓")
# Формируем заголовки, которые будем использовать во всех следующих запросах
AUTH_HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Bearer {ACCESS_TOKEN}"
}После успешного выполнения этого блока в переменной ACCESS_TOKEN будет лежать ключ доступа. Мы вынесли заголовки в отдельную переменную AUTH_HEADERS — чтобы не дублировать их в каждом следующем запросе.
Вставьте ваш refresh-токен в строку REFRESH_TOKEN точно как он есть — без пробелов, кавычек и переносов строк. Даже один лишний символ вернет ошибку авторизации.
Зачем это нужно
Мы собираемся выставить лимитную заявку — то есть заявку с конкретной ценой: «куплю, но не дороже, чем за столько-то». Чтобы назначить разумную цену, сначала нужно узнать, где Сбер торгуется прямо сейчас.
Скрипт запросит котировку SBER и возьмет из ответа поле last — цену последней сделки. От нее мы оттолкнемся: поставим цену заявки чуть ниже рынка, чтобы она не сработала мгновенно. Так у вас будет время увидеть заявку в терминале и при желании отменить.
API возвращает не только последнюю цену, но и полный набор данных по бумаге: цены открытия и закрытия, максимум и минимум за день, лучшие цены покупки и продажи. Нам сейчас нужна только last.
Добавляем второй блок в файл
Дописываем этот код в тот же файл trade.py, ниже первого блока:
# ── Шаг 2: получаем текущую цену ─────────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
payload = json.dumps({
"instruments": [{
"ticker": TICKER,
"classCode": CLASS_CODE
}]
})
conn.request(
"POST",
"/trade-api-market-data-connector/api/v1/quotes",
payload,
AUTH_HEADERS
)
quote_data = json.loads(conn.getresponse().read().decode("utf-8"))
last_price = quote_data["records"][0]["last"]
limit_price = round(last_price * DISCOUNT, 2)
print(f"[2/3] Цена {TICKER}: {last_price} ₽ → лимитная цена заявки: {limit_price} ₽")Переменная DISCOUNT задана в настройках как 0,99 — это означает «на 1% ниже рынка». Например, если SBER торгуется по 312,50 руб., лимитная цена составит 309,37 руб. Заявка встанет в стакан и будет ждать, пока цена не опустится до этого уровня.
Если хотите, чтобы заявка исполнилась быстрее — уменьшите отступ, например DISCOUNT = 0,999 (0,1% ниже рынка). Для первого теста лучше держать 1% — это безопаснее.
Что произойдет
Это главный момент — скрипт отправит на биржу заявку: «купи 1 лот Сбера по цене не выше заданной». Биржа поставит ее в стакан заявок и будет ждать, пока не найдется продавец с подходящей ценой. Если цена не достигнута — заявка просто висит активной до конца торгового дня.
Этим лимитная заявка и отличается от рыночной: вы сами контролируете цену, по которой готовы купить, а не соглашаетесь на любую текущую. Для алгоритмической торговли это обычно важнее.
Параметры заявки
В этом запросе мы описываем заявку набором параметров. Вот что означает каждый:
| Параметр | Значение | Описание |
|---|---|---|
| clientOrderId | uuid.uuid4() | Уникальный ID заявки — генерируем сами. Нужен, чтобы потом проверить статус и избежать дублей |
| side | «1» | Направление: 1 — покупка, 2 — продажа |
| orderType | «2» | Тип заявки: 1 — рыночная (по текущей цене), 2 — лимитная (по заданной цене) |
| orderQuantity | 1 | Количество в лотах. Начинаем с 1 — минимально возможного |
| ticker | «SBER» | Тикер инструмента |
| classCode | «TQBR» | Класс бумаги на Мосбирже. Для большинства акций — TQBR |
| price | limit_price | Наша лимитная цена, рассчитанная на шаге 2 |
Скрипт выставит настоящую биржевую заявку, которая может быть исполнена. Убедитесь, что:
• QUANTITY равен 1 — не меняйте до тех пор, пока не убедитесь, что все работает.
• DISCOUNT равен 0,99 — цена заявки будет на 1% ниже рынка, мгновенного исполнения не будет.
• На счете достаточно средств для покупки 1 лота SBER.
• После запуска откройте терминал БКС и найдите заявку — убедитесь, что все верно.
• При необходимости отмените заявку вручную в терминале.
Добавляем третий блок в файл
Дописываем в trade.py, ниже предыдущего кода:
# ── Шаг 3: выставляем лимитную заявку ───────────────────────────────
conn = http.client.HTTPSConnection(HOST)
order_id = str(uuid.uuid4()) # генерируем уникальный ID заявки
payload = json.dumps({
"clientOrderId": order_id,
"side": "1", # 1 = покупка
"orderType": "2", # 2 = лимитная заявка
"orderQuantity": QUANTITY,
"ticker": TICKER,
"classCode": CLASS_CODE,
"price": limit_price
})
conn.request(
"POST",
"/trade-api-bff-operations/api/v1/orders",
payload,
AUTH_HEADERS
)
order_data = json.loads(conn.getresponse().read().decode("utf-8"))
print(f"[3/3] Заявка выставлена ✓ ID: {order_id} Статус: {order_data.get('status')}")Обратите внимание на clientOrderId — мы сохраняем его в переменную order_id. Этот идентификатор понадобится на следующем шаге, чтобы запросить статус заявки. Именно по нему API находит конкретную заявку среди всех ваших.
Зачем это нужно
Если запрос на создание заявки прошел успешно — это значит лишь, что API принял команду. Но заявка еще может не дойти до стакана: биржа отклоняет заявки из-за нарушения ценовых ограничений, нехватки средств на счете или технических причин. Поэтому стоит отдельно запросить статус и убедиться, что заявка действительно активна.
Какие бывают статусы
В ответе придет текущий статус заявки. Чаще всего вы увидите одно из значений:
Добавляем последний блок и запускаем
Дописываем финальный блок в trade.py:
# ── Шаг 4: проверяем статус заявки ──────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
conn.request(
"GET",
f"/trade-api-bff-operations/api/v1/orders/{order_id}",
headers=AUTH_HEADERS
)
status_data = json.loads(conn.getresponse().read().decode("utf-8"))
print(f"Статус заявки: {status_data}")Сохраняем файл (в редакторе nano: Ctrl+O, затем Enter, затем Ctrl+X) и запускаем скрипт командой:
python3 trade.py
Ожидаемый вывод в терминале:
[1/3] Access-токен получен ✓
[2/3] Цена SBER: 312.45 ₽ → лимитная цена заявки: 309.33 ₽
[3/3] Заявка выставлена ✓ ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6 Статус: NEW
Статус заявки: {'clientOrderId': '3fa85f64-...', 'status': 'NEW'}Статус NEW — это хороший результат. Заявка принята биржей и ждет своего часа в стакане. Откройте терминал БКС и найдите ее в разделе активных заявок.
Если вы собирали скрипт по частям — вот он целиком, для удобства. Можно скопировать его одним куском в trade.py, вставить свой токен в первую строку настроек и запустить:
python3 trade.py
import http.client
import json
import uuid
# ══════════════════════════════════════════════════════════════════════
# НАСТРОЙКИ — заполните перед запуском
# ══════════════════════════════════════════════════════════════════════
REFRESH_TOKEN = "ВАШ_REFRESH_ТОКЕН_ИЗ_ЛК_БКС"
TICKER = "SBER"
CLASS_CODE = "TQBR"
QUANTITY = 1 # количество лотов
DISCOUNT = 0.99 # лимит = 1% ниже рынка
HOST = "be.broker.ru"
# ── Шаг 1: получаем access-токен ─────────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
conn.request("POST",
"/trade-api-keycloak/realms/tradeapi/protocol/openid-connect/token",
json.dumps({"client_id": "trade-api-write",
"refresh_token": REFRESH_TOKEN,
"grant_type": "refresh_token"}),
{"Content-Type": "application/json"})
ACCESS_TOKEN = json.loads(conn.getresponse().read())["access_token"]
print("[1/3] Access-токен получен ✓")
AUTH_HEADERS = {"Content-Type": "application/json", "Accept": "application/json",
"Authorization": f"Bearer {ACCESS_TOKEN}"}
# ── Шаг 2: получаем текущую цену ─────────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
conn.request("POST",
"/trade-api-market-data-connector/api/v1/quotes",
json.dumps({"instruments": [{"ticker": TICKER, "classCode": CLASS_CODE}]}),
AUTH_HEADERS)
quote = json.loads(conn.getresponse().read())["records"][0]
last_price = quote["last"]
limit_price = round(last_price * DISCOUNT, 2)
print(f"[2/3] Цена {TICKER}: {last_price} ₽ → лимит: {limit_price} ₽")
# ── Шаг 3: выставляем лимитную заявку ───────────────────────────────
conn = http.client.HTTPSConnection(HOST)
order_id = str(uuid.uuid4())
conn.request("POST",
"/trade-api-bff-operations/api/v1/orders",
json.dumps({"clientOrderId": order_id, "side": "1", "orderType": "2",
"orderQuantity": QUANTITY, "ticker": TICKER,
"classCode": CLASS_CODE, "price": limit_price}),
AUTH_HEADERS)
order_data = json.loads(conn.getresponse().read())
print(f"[3/3] Заявка выставлена ✓ ID: {order_id} Статус: {order_data.get('status')}")
# ── Шаг 4: проверяем статус ──────────────────────────────────────────
conn = http.client.HTTPSConnection(HOST)
conn.request("GET",
f"/trade-api-bff-operations/api/v1/orders/{order_id}",
headers=AUTH_HEADERS)
status_data = json.loads(conn.getresponse().read())
print(f"Статус заявки: {status_data}")Что дальше? Скрипт работает — и это уже настоящий торговый инструмент. Но пока он делает одно действие и останавливается. В следующих статьях добавим логику: стратегию входа, управление рисками, автоматический мониторинг. А пока — поиграйте с параметрами: попробуйте другой тикер, поменяйте DISCOUNT, посмотрите, как меняется цена заявки.
В этой статье мы затронули три метода: авторизацию, котировки и заявки. Но API БКС умеет гораздо больше — отмена заявок, история сделок, состояние портфеля, потоковые данные. Все методы, параметры и примеры ответов подробно описаны в официальной документации.
Если хочется поэкспериментировать с запросами, прежде чем писать код, — обратите внимание на Postman. Это бесплатный сервис, в котором можно отправлять запросы к API в визуальном интерфейсе, без программирования: указываете адрес и параметры — и сразу видите ответ. Удобно, чтобы разобраться, как устроены методы, и проверить токен.
Чтобы упростить старт, мы подготовили готовую коллекцию всех запросов к API БКС. Перейдите по ссылке — и можете сразу выбирать нужные запросы и отправлять их, подставив свой токен: https://www.postman.com/mybroker/workspace/api.
Подробный разбор работы через Postman — тестирование запросов без единой строки кода — будет в отдельной статье серии.
Подписывайтесь на наш канал для трейдеров в «Профите», чтобы быть в курсе последних обновлений, обмениваться опытом с единомышленниками и узнавать лайфхаки.