Play
online · 30 мин
Туториалы 19 мин чтения 27.09.2026

Торговый бот для биржи: архитектура, инфраструктура и риски автоторговли

Торговый бот — это не только стратегия, но и инфраструктура, которая превращает намерение в сделку и защищает от ошибок. Разбираем архитектуру, риски и реальные требования к автоторговле на бирже.

Что такое торговый бот и из каких частей он состоит

В наших проектах торговый бот почти никогда не начинается со стратегии. Приходят с готовой идеей — «покупать на пробое», «ловить спред между спотом и фьючерсом», — а первая же встреча уходит в сторону: как бот узнает, что ордер реально встал в стакан? Что будет, если биржа ответит таймаутом, а через секунду ордер всё-таки исполнится? Кто остановит серию, если рынок пошёл против позиции? Стратегия — это функция: на входе свежие данные, на выходе намерение. Всё остальное превращает намерение в сделку и не даёт потерять деньги на пути.

От ручной торговли бот отличается не скоростью реакции — это как раз второстепенно. Он отличается тем, что работает без пауз, без эмоций и без выходных, и поэтому все его ошибки накапливаются. Трейдер пропустит сигнал и забудет. Бот пропустит сигнал и повторит это тысячу раз за ночь, пока кто-нибудь не посмотрит в лог. Отсюда простое правило: архитектуру выбирают до стратегии, потому что от неё зависит, увидите вы проблему через минуту или через месяц, когда она уже съела депозит.

По нашему опыту, любой боевой бот состоит из четырёх частей:

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

Дальше разберём, почему эти части разделяют и что это даёт на практике.

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

Коннектор и процессор разделяют именно ради отладки. В сообществе на ru.stackoverflow разбирали случай, когда нужно было обрабатывать непрерывный поток данных: помогло разделение на коннектор, который читает поток, и процессор, который его обрабатывает — коннектор отдаёт данные через асинхронный генератор, а процессор забирает их циклом. Такая граница позволяет подменить коннектор на заглушку и прогнать стратегию на записанных данных, не трогая боевое соединение.

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

Слой исполнения и логирования — то место, куда стекается правда. Не то, что бот хотел сделать, а что биржа ответила. Был случай, когда ответы API писались в файл одной слипшейся строкой из-за попытки преобразовать объект в строку перед разбором и отсутствия форматирования при выводе JSON. Мелочь, но именно из-за неё невозможно понять, терялись ордера или нет.

Бот для биржи: подключение к API и подпись запросов

У нас в практике это самое узкое место на старте: люди берут готовую стратегию, а спотыкаются на первых двух запросах к приватным эндпоинтам.

Подпись устроена одинаково почти везде: собрать строку из параметров, добавить временную метку, посчитать HMAC и положить результат в заголовок. Освоив один раз, вы переносите схему на любую биржу.

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

PHP
<?php
// Подпись приватного запроса к Bybit API V5 (HMAC-SHA256)
// $apiKey, $apiSecret — из переменных окружения, не из кода
$apiKey    = getenv('BYBIT_API_KEY');
$apiSecret = getenv('BYBIT_API_SECRET');

$timestamp  = (string) round(microtime(true) * 1000); // миллисекунды
$recvWindow = '5000';
$params     = ['category' => 'spot', 'symbol' => 'BTCUSDT', 'side' => 'Buy', 'orderType' => 'Market', 'qty' => '0.001'];
$payloadJson = json_encode($params);

// Строка для подписи POST-запроса: timestamp + apiKey + recvWindow + payload
$payload  = $timestamp . $apiKey . $recvWindow . $payloadJson;
$signature = hash_hmac('sha256', $payload, $apiSecret);

$headers = [
    'Content-Type: application/json',
    'X-BAPI-API-KEY: '     . $apiKey,
    'X-BAPI-TIMESTAMP: '   . $timestamp,
    'X-BAPI-RECV-WINDOW: ' . $recvWindow,
    'X-BAPI-SIGN: '        . $signature,
];

$ch = curl_init('https://api.bybit.com/v5/order/create-post');
curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payloadJson,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ключевое здесь — порядок частей в строке подписи. Bybit ждёт ровно timestamp + apiKey + recvWindow + query, и если поменять местами хотя бы два элемента, подпись не сойдётся. Меняется под проект обычно recvWindow: если сервер далеко от биржи и время «плывёт», его увеличивают, чтобы запрос не отбраковался по устаревшей метке. Параметры собираем через http_build_query, потому что биржа подписывает ту же строку, что уйдёт в теле — не пересобирайте её вручную.

Секреты держите в переменных окружения или в менеджере секретов, а не в репозитории и не в init.php. И если ключей несколько (торговый, вывод средств), выдавайте каждому минимально нужные права — тогда утечка одного не обнулит весь аккаунт. Для ротации удобнее RSA-SHA256: библиотека bybit-php поддерживает оба алгоритма, а с RSA вы меняете ключ на стороне биржи, не переписывая подпись в коде.

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

Торговый бот Bybit: базовый URL, регионы и версия API V5

Распространённая ошибка: считать, что «подключиться к Bybit» — это просто вписать домен в конфиг и забыть. На деле выбор базового URL и версии API определяет три вещи сразу: какой набор эндпоинтов вам доступен, как формируется подпись запроса и какая версия библиотеки вообще скомпилируется без правок.

Отдельная история — региональные нюансы. Смена метода доступа для отдельных юрисдикций ломает то, что годами работало: заголовки, которые раньше были обязательными, становятся лишними, а сторонние сниппеты продолжают их рекомендовать. Ниже — матрица, которую мы держим под рукой при старте нового бота, и разбор, что из неё менять под свой аккаунт.

Параметр Значение Нюанс
Базовый URL REST https://api.bybit.com Боевой контур для V5
Версия API V5 Единый набор эндпоинтов вместо старых
Заголовок x-site-id Бразилия, Аргентина Для внутренних аккаунтов больше не требуется
Алгоритмы подписи HMAC-SHA256, RSA-SHA256 Выбор зависит от типа ключа

Читать таблицу стоит не построчно, а как связку решений. Первое — базовый URL: у V5 это https://api.bybit.com, и подменять его на «зеркало» из стороннего туториала не нужно, если вы не решаете отдельную сетевую задачу. Второе — версия: V5 даёт единый интерфейс к спотовым, деривативным и опционным эндпоинтам, поэтому именно её закладывают в новые проекты. Третье — подпись: если у вас ключ типа RSA, выставляйте RSA-SHA256, для обычного секрета — HMAC-SHA256; библиотека bybit-php, по опыту сообщества, поддерживает оба варианта, но конфигурируются они по-разному.

Региональный пункт — самый коварный. Сторонние источники до сих пор путают «базовый URL» и «обязательный метод доступа»: в старых сниппетах x-site-id подаётся как постоянный заголовок, хотя официальный чейнджлог прямо говорит, что для внутренних аккаунтов в Бразилии и Аргентине он больше не требуется. Что менять под себя: домен, версию, алгоритм подписи и наличие x-site-id. И сверять это с официальным чейнджлогом, а не с копипастой из блога.

Автоторговля: как обрабатывать непрерывный поток данных с биржи

Типичный сценарий: стратегия готова, ключи прописаны, ордера отправляются — но бот «тормозит». Причина почти всегда в одном: тикеры и стакан приходят непрерывно, а код пытается обработать их в том же потоке, где и читает. В автоторговле поток данных — это главная сложность архитектуры, а не деталь реализации. Пока вы парсите один ответ, приходят следующие; пока считаете индикатор, стакан уже уехал.

У нас в проектах разделение на коннектор (читает данные) и процессор (принимает решения) снимает большую часть проблем. Коннектор ничего не знает о стратегии — он просто отдаёт поток наружу. Процессор ничего не знает о транспорте — он читает готовый поток и реагирует. Такая развязка позволяет менять WebSocket на REST-поллинг, не переписывая логику торговли, и наоборот. Дальше покажем, как это выглядит в коде на Python с asyncio.

PYTHON
import asyncio
import json

class ExchangeConnector:
    def __init__(self, ws_url: str, symbol: str):
        self.ws_url = ws_url
        self.symbol = symbol

    async def get_data(self):
        """Асинхронный генератор: отдаёт поток наружу через yield."""
        import websockets
        async with websockets.connect(self.ws_url) as ws:
            await ws.send(json.dumps({
                "op": "subscribe",
                "args": [f"tickers.{self.symbol}"],
            }))
            async for raw in ws:
                msg = json.loads(raw)
                if msg.get("topic", "").startswith("tickers"):
                    yield msg["data"]  # вместо print — передаём наверх

Ключевое здесь — yield вместо print. Коннектор не обрабатывает данные, он их отдаёт. К одному потоку можно подключить несколько потребителей: процессор стратегии, логгер, модуль риск-менеджмента. Если у вас один инструмент и одна стратегия, хватит и одного потребителя. Но запас на будущее стоит закладывать сразу.

Процессор запускается отдельной задачей через asyncio.create_task. Это принципиально: блокировать цикл событий нельзя. Любая синхронная операция внутри async for — тяжёлый расчёт, запись на диск, синхронный HTTP-запрос — останавливает чтение из сокета. Пока процессор считает, буфер WebSocket растёт, а котировки в обработке устаревают. Если расчёт неизбежно тяжёлый, выносите его в run_in_executor или в отдельный процесс.

Что стоит менять под свой проект: адрес ws_url и формат подписки зависят от биржи (у Bybit V5 он отличается от Binance), а фильтр topic — от того, какие каналы вам нужны. Для нескольких символов удобнее держать один коннектор с массивом подписок, чем плодить соединения.

PYTHON
class TradingProcessor:
    def __init__(self, connector: ExchangeConnector, trader):
        self.connector = connector
        self.trader = trader

    async def run(self):
        async for data in self.connector.get_data():
            price = float(data["lastPrice"])
            if price > self.trader.target_price:
                await self.trader.place_order("Sell", price)
            elif price < self.trader.stop_price:
                await self.trader.place_order("Buy", price)

async def main():
    connector = ExchangeConnector("wss://stream.bybit.com/v5/public/spot", "BTCUSDT")
    processor = TradingProcessor(connector, trader)
    await asyncio.create_task(processor.run())
    await asyncio.Event().wait()  # держим цикл живым

asyncio.run(main())

Грабля: попытка обработать поток синхронно в одном цикле — читаем, тут же считаем, тут же отправляем ордер. Пока идёт расчёт и сетевой запрос к бирже, входящие сообщения копятся в буфере. Очередь растёт, котировки в обработке устаревают на секунды, и ордер уходит по цене, которой на рынке уже нет. Разносите чтение и обработку в отдельные задачи через create_task — тогда коннектор продолжает читать, пока процессор думает.

Инфраструктура алготрейдинга: где размещать сервер и как снижать задержку

Распространённая ошибка: считать, что инфраструктура бота — это «просто VPS, на котором крутится скрипт». В наших проектах автоторговли инфраструктура — отдельный слой, который живёт параллельно логике стратегии и решает свои задачи. Когда к нам приходят с уже работающим ботом, чаще всего он состоит из четырёх-пяти компонентов, между которыми нет связи.

Смотрим, что входит в этот слой:

  • Сервер — вычислительная площадка под сам бот, его коннектор и обработчики.
  • Сеть — канал до API биржи: маршрут, стабильность, jitter.
  • Мониторинг — метрики процесса: задержка запросов, разрывы соединения, ошибки авторизации.
  • Резервное питание и связность — чтобы падение одного узла не оставило бота без связи с рынком.
  • Хранилище логов и метрик — отдельное от рантайма, чтобы разбор инцидента не зависел от падения сервера.

Почему это не «просто VPS»? Если бот, его логи и мониторинг живут на одной машине, при её падении вы теряете и торговлю, и диагностику одновременно. У клиентов с активной стратегией мы почти всегда разносим рантайм и хранилище телеметрии — хотя бы на уровне отдельного диска или сервиса. Об этом ниже.

Базовый набор, который закрывает большинство сценариев автоторговли:

  • VPS в регионе, близком к инфраструктуре биржи — не «где дешевле», а «где ближе по маршруту».
  • Отдельный сервер или контейнер под бэктесты — чтобы прогон истории не тормозил боевой процессор.
  • Мониторинг процесса: heartbeat, задержка ответов API, статус WebSocket-подписки.
  • Алерты в мессенджер — срабатывают на разрыв коннектора и на серию ошибок авторизации.
  • Ежедневные бэкапы конфигов и ключевых файлов состояния — стратегия, ключи, параметры риска.

Что даёт колокация. Московская Биржа предоставляет услугу Colocation — размещение серверов в дата-центре биржи для низкого уровня задержки сетевого соединения. Это официальная услуга MOEX, а не «серая» оптимизация. Для HFT-команд это стандартная практика: сервер ставится физически рядом с торговым ядром, и путь запроса сокращается до минимума.

Для криптобирж аналог колокации недоступен, но принцип тот же. Выбирайте регион VPS рядом с инфраструктурой конкретной площадки. Если бот торгует на Bybit, смотрите на дата-центры в Азии и Европе, а не на ближайший к вам по географии хостинг. Разница между «сервер в том же регионе, что и API» и «сервер за океаном» — это не абстрактные миллисекунды, а реальное количество проскальзываний на быстрых движениях.

Если торгуете одну пару на среднесроке, хватит VPS рядом с биржей и базового мониторинга. Для стратегий, которые ловят короткие движения, колокация у MOEX или региональный VPS у криптобиржи обязательны. Иначе вся работа над логикой уйдёт в сетевую задержку.

Как парсить и логировать ответы API, чтобы данные не слипались в одну строку

Самый частый запрос на эту тему — «вывел ответ биржи, а он одной строкой на километр, ничего не разобрать». На ru.stackoverflow разбирали ровно такой случай: человек начал писать бота для биржи, а скрипт выводил данные в одну строку вместо построчного вывода с отступами. Причина оказалась типовой — объект ответа сначала превращали в строку через str(), и только потом пытались форматировать. После str() это уже не словарь, а плоский текст, и json.dumps с ним ничего осмысленного сделать не может.

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

PYTHON
import json
import logging
from datetime import datetime, timezone
from pathlib import Path

log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)

def log_api_response(endpoint: str, response: dict, level: int = logging.INFO) -> None:
    """Пишет ответ API в лог построчно, с отступами и сортировкой ключей."""
    day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    logger = logging.getLogger(f"api.{endpoint}")
    logger.setLevel(level)

    if not logger.handlers:
        handler = logging.FileHandler(log_dir / f"{endpoint}-{day}.log", encoding="utf-8")
        handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
        logger.addHandler(handler)

    # объект идёт в json.dumps напрямую — без str() перед этим
    pretty = json.dumps(response, indent=4, sort_keys=True, ensure_ascii=False, default=str)
    logger.info("response:\n%s", pretty)

Ключевое здесь — json.dumps(response, ...) получает словарь как есть. Параметр indent=4 даёт отступы, sort_keys=True фиксирует порядок ключей (удобно диффать два лога между собой), ensure_ascii=False оставляет кириллицу читаемой, а default=str спасает на несериализуемых типах вроде datetime, которые иногда просачиваются из SDK. Если обернуть объект в str() раньше — json.dumps получит строку и выведет её как один экранированный литерал: тот самый эффект «всё в одну строку».

Что стоит менять под свой проект. Разбивку логов по дням мы делаем через дату в имени файла — endpoint-2026-09-27.log. Это упрощает ротацию: старые файлы удаляются по маске, не нужно тащить RotatingFileHandler с хрупкими настройками. Для метрик пишите в лог не только тело ответа, но и retCode, retMsg, время запроса и эндпоинт — по ним потом строится алерт «доля ошибок выше нормы». Если ответ содержит приватные поля (баланс, ключи), вырезайте их из объекта до dumps — в лог они попадать не должны.

Важно: рассинхрон времени с сервером биржи — подпись невалидна, и запрос отклоняется ещё до того, как вы увидите ответ. Синхронизируйте системные часы (ntpd / chrony на сервере) и проверяйте дрейф до отправки подписанных запросов. На ru.stackoverflow в том же случае помогла явная установка сдвига времени через bot.set_shift_seconds(shift_seconds) — если библиотека такое умеет, используйте её синхронизатор, а не правьте подпись вручную.

Риски автоторговли: что ломает бота и как это ловить заранее

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

Мы говорим именно о вторых. Их можно закрыть до запуска. Рыночный риск вы измеряете бэктестом и принимаете осознанно, а технический — это баг, который вы либо нашли на этапе ревью, либо обнаружили в момент, когда бот уже отправил не тот ордер. В наших проектах почти каждый инцидент на проде разбирается до строчки, которая была очевидна при первом чтении, но её пропустили. Ниже пять грабель, которые встречаются чаще всего, и защита, которую мы ставим по умолчанию.

ГрабляПричинаРешение
Утечка API-ключейКлюч с правом вывода в общем конфигеОтдельный ключ, только торговля
Рассинхрон времениЧасы сервера ушли от биржевыхСинхронизация по NTP, запас в подписи
Блокирующий циклСинхронный вызов внутри потока данныхАсинхронные запросы, отдельные задачи
Ордер без лимитаНет проверки размера позицииЖёсткий потолок объёма и цены
Потеря соединения без алертаОбрыв WebSocket не логируетсяHeartbeat и внешнее оповещение

Защиту мы выстраиваем слоями, каждый слой решает свою задачу. Начинается всё с ключа: под торговлю заводится отдельный API-ключ с минимальными правами — только спот или только фьючерсы, без вывода средств и без переводов между счетами. Если такой ключ утечёт, злоумышленник сможет торговать, но не сможет забрать баланс. Дальше лимит размера позиции: перед отправкой ордера код сверяет объём с заранее заданным потолком, и всё, что выше, отклоняется на стороне бота, а не на стороне биржи.

Третий слой — kill-switch: отдельный флаг, который останавливает торговлю и закрывает позиции по одной команде. У нас он вынесен в независимый процесс, чтобы не зависеть от того же цикла, что и сама стратегия. Четвёртый — журнал всех ордеров: не только исполненных, но и отклонённых, с причиной отказа. Без него разбор инцидента превращается в гадание. Как именно парсить и хранить ответы API, мы разбирали выше. Здесь важно, что журнал пишется до отправки, а не после ответа.

Тестирование бота: бэктест, тестовая сеть и запуск в прод

Многие думают, что тестирование бота — это «прогнать стратегию на истории и посмотреть, красивая ли кривая доходности». это три разных проверки, каждая ловит свой класс ошибок. Бэктест проверяет логику: правильно ли бот считает сигналы, не путает ли стороны, не открывает ли вторую позицию поверх первой. Тестовая сеть проверяет интеграцию: проходит ли подпись запроса, соблюдаются ли лимиты, корректно ли бот разбирает ошибки биржи. А прод показывает реальную задержку. Между решением и исполнением всегда есть время, и на волатильном рынке оно стоит денег.

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

  1. Прогнать стратегию на исторических данных и зафиксировать метрики. Берём свечи и стакан за репрезентативный период, включая хотя бы один резкий разворот. Считаем не только доходность, но и просадку, число сделок, средний размер прибыли и убытка. Результат сохраняем в файл — к нему будем возвращаться после прода, чтобы сравнить ожидание с фактом.
  2. Подключиться к тестовой сети биржи и проверить подпись, лимиты, обработку ошибок. Тестнет даёт те же эндпоинты, что и боевой контур, но с фейковыми деньгами. Здесь ловим всё, что не видно в бэктесте: неверный timestamp, расхождение подписи, превышение rate limit, ответы с кодом ошибки вместо ожидаемых данных. Отдельно проверяем, что бот не падает, а корректно ретраит там, где это безопасно.
  3. Запустить в прод с минимальным размером позиции и включённым мониторингом. Не с полным депозитом — с размером, который не жалко потерять на отладке. Мониторинг на этом этапе важнее стратегии: логируем каждый запрос, ответ, факт исполнения и текущий P&L. Если что-то идёт не так — бот должен останавливаться сам, а не ждать, пока мы заметим.
  4. Сравнить фактические исполнения с ожидаемыми и скорректировать риск-менеджер. Берём сделки из прода и сопоставляем с тем, что бот планировал: цена входа, проскальзывание, время от сигнала до fill. Если расхождение стабильно в одну сторону — правим не стратегию, а риск-менеджер: лимиты, размер позиции, порог срабатывания стопа.

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

Откатываемся назад мы в двух случаях. Первый — метрики прода расходятся с бэктестом сильнее, чем объясняет проскальзывание: значит, в логике или в данных бэктеста была ошибка. Второй — риск-менеджер не срабатывает там, где должен: стоп не выставился, лимит не остановил серию. В обоих случаях возвращаемся на шаг назад, а не «подкручиваем параметры на живом счёте» — так дешевле.

Юридическая рамка: что важно знать про листинг и регулирование

Разработчик бота редко задумывается о юрисдикции — до момента, когда бот впервые отправляет ордер. И тут выясняется, что граница между криптоинструментом и ценной бумагой не абстракция из учебника, а вопрос, от которого зависит, законна ли вообще ваша торговля. Токенизированные акции, стейблкоины с доходностью, «цифровые права» на активы — всё это может попадать под регулирование рынка ценных бумаг. Тогда к вашему боту предъявляются совсем другие требования: лицензия брокера, отчётность, KYC. Мы видели проекты, где команда месяцами строила арбитражный бот между двумя площадками, не проверив, что один из инструментов по своей правовой природе — не крипта, а именно ценная бумага. Разбираться с этим до запуска дешевле, чем после первого запроса от регулятора.

Базовый документ, на который стоит ориентироваться в российской рамке, — Федеральный закон № 39-ФЗ «О рынке ценных бумаг» от 22.04.1996. Он регулирует отношения при эмиссии и обращении ценных бумаг, а также деятельность профессиональных участников рынка. Для нас здесь важны два понятия. Первое — эмиссия и обращение: если инструмент, которым торгует бот, признаётся ценной бумагой, то вся цепочка от выпуска до сделки подпадает под закон. Второе — листинг: это включение бумаг организатором торговли в список допущенных к торгам, включая котировальные списки биржи. Проще говоря, листинг — это формальный допуск инструмента к торгам на конкретной площадке, и у каждой биржи свои правила и уровни котировальных списков.

На практике это означает: прежде чем писать коннектор к очередной бирже, стоит проверить, что за инструменты она листит и в каком статусе.

Бот, торгующий только криптовалютами на криптобирже, и бот, работающий с токенизированными акциями, — это два разных правовых режима.

Читайте также: Торговый робот: как написать, протестировать на бэктесте и запустить в прод, ИИ-ассистент для бизнеса: как мы внедряем нейросетевых помощников в Bitrix и 1С.

Частые вопросы

Можно ли запустить бота на домашнем компьютере, если я торгую вручную и не гонюсь за миллисекундами?

Да, для стратегий с горизонтом от минут и выше домашний ПК с проводным интернетом и UPS подойдёт. Но учтите: домашний IP может меняться при переподключении, а Bybit привязывает API-ключ к whitelist IP — при смене адреса запросы начнут отбиваться с ошибкой 10003.

Чем отличается testnet Bybit от бэктеста, если я хочу проверить стратегию перед продом?

Бэктест прогоняет логику на исторических свечах и не проверяет реальную инфраструктуру, а testnet — это живой API с отдельным ключом и балансом-песочницей. Testnet ловит проблемы с подписью, rate limit и обработкой WebSocket, которые бэктест в принципе не видит.

Что делать, если WebSocket от Bybit отвалился ночью, а позиция осталась открытой?

Держите отдельный watchdog-цикл, который раз в 10–30 секунд дёргает REST-метод get_positions и сверяет фактическое состояние с локальным. Если расхождение — либо переподключите WS с ресинком ордербука, либо аварийно закройте позицию через place_order с reduceOnly=true.

А если у меня несколько стратегий на одном аккаунте Bybit — как не перепутать их ордера?

Используйте поле orderLinkId: задавайте префикс под стратегию, например grid_btc_0001, и фильтруйте get_order_history по нему. Так вы сможете отменять и считать PnL каждой стратегии отдельно, не смешивая сделки в общий котёл.

Можно ли парсить ответы API обычным json.loads, или обязательно писать свой парсер?

json.loads достаточно, если ответ валидный JSON — Bybit V5 всегда возвращает объект с полями retCode, retMsg и result. Свой парсер нужен только для логов: пишите их в JSON Lines (одна запись на строку), иначе многострочные ответы слипнутся и grep по ним станет бесполезен.

Что делать если стратегия показывает прибыль в бэктесте, но в проде уходит в минус на комиссиях?

Считайте бэктест с реальными taker/maker-ставками Bybit и с проскальзыванием хотя бы в один тик — на ликвидных парах комиссия съедает 0.055% с каждой стороны. Если стратегия живёт на горизонте секунд, добавьте в модель задержку исполнения 50–200 мс, иначе результат будет оптимистичнее реальности.

#Python #Docker #Redis #PostgreSQL #RabbitMQ
автор · Backend / SRE Engineer
Артем Колячек

Backend-разработчик и SRE в студии Paradigma. Занимается тем, что у других проектов обычно обнаруживается за неделю до запуска: переездом Bitrix-проектов между серверами, починкой кешей после миграций, выстраиванием pipeline для контейнеров, мониторингом под нагрузкой.

До Paradigma — 7 лет в backend (PHP + Postgres) и в operations (Linux, Docker, Coolify, restic-бэкапы). Любит когда логи разговаривают полным синтаксисом ошибки, а не «что-то пошло не так».

На блоге пишет ровно про те ситуации с которыми сам разбирался руками: какой Bitrix-апдейт сломал кеш и как откатить, почему Docker-сеть не находит контейнер после рекрейта, что делать когда asyncpg ругается на event loop.