Что такое торговый бот и из каких частей он состоит
В наших проектах торговый бот почти никогда не начинается со стратегии. Приходят с готовой идеей — «покупать на пробое», «ловить спред между спотом и фьючерсом», — а первая же встреча уходит в сторону: как бот узнает, что ордер реально встал в стакан? Что будет, если биржа ответит таймаутом, а через секунду ордер всё-таки исполнится? Кто остановит серию, если рынок пошёл против позиции? Стратегия — это функция: на входе свежие данные, на выходе намерение. Всё остальное превращает намерение в сделку и не даёт потерять деньги на пути.
От ручной торговли бот отличается не скоростью реакции — это как раз второстепенно. Он отличается тем, что работает без пауз, без эмоций и без выходных, и поэтому все его ошибки накапливаются. Трейдер пропустит сигнал и забудет. Бот пропустит сигнал и повторит это тысячу раз за ночь, пока кто-нибудь не посмотрит в лог. Отсюда простое правило: архитектуру выбирают до стратегии, потому что от неё зависит, увидите вы проблему через минуту или через месяц, когда она уже съела депозит.
Любой боевой бот состоит из четырёх частей:
- Коннектор к бирже — держит соединение, подписывает запросы, шлёт ордера и забирает ответы.
- Стратегия или сигнальный слой — решает, что делать: входить, выходить, стоять в стороне.
- Риск-менеджер — ограничивает размер позиции, число сделок и максимальную просадку.
- Слой исполнения и логирования — превращает решение в конкретный ордер и пишет, что реально произошло.
Дальше разберём, почему эти части разделяют и что это даёт на практике.
Соблазн написать всё одним скриптом велик: сто строк, один цикл, ордер отправляется прямо из стратегии. Пока бот торгует на одном инструменте и вы за ним смотрите — работает. Проблемы начинаются, когда нужно поменять биржу или разобраться в инциденте.
Коннектор и процессор разделяют именно ради отладки. В сообществе на ru.stackoverflow разбирали случай, когда нужно было обрабатывать непрерывный поток данных: помогло разделение на коннектор, который читает поток, и процессор, который его обрабатывает — коннектор отдаёт данные через асинхронный генератор, а процессор забирает их циклом. Такая граница позволяет подменить коннектор на заглушку и прогнать стратегию на записанных данных, не трогая боевое соединение.
С заменой биржи то же самое. Если подпись запросов, ретраи и разбор ошибок заперты внутри стратегии, переезд с одной площадки на другую превращается в переписывание всей логики. Если они живут в коннекторе — меняется один модуль, а сигнальный слой и риск-менеджер остаются нетронутыми. Риск-менеджер при этом держат отдельно от стратегии намеренно: стратегия хочет войти, а риск-менеджер решает, можно ли это сделать сейчас. Когда лимиты размазаны по коду сигналов, их невозможно ни проверить, ни ужесточить в моменте.
Слой исполнения и логирования — то место, куда стекается правда. Не то, что бот хотел сделать, а что биржа ответила. Был случай, когда ответы API писались в файл одной слипшейся строкой из-за попытки преобразовать объект в строку перед разбором и отсутствия форматирования при выводе JSON. Мелочь, но именно из-за неё невозможно понять, терялись ордера или нет.
Бот для биржи: подключение к API и подпись запросов
У нас в практике это самое узкое место на старте: люди берут готовую стратегию, а спотыкаются на первых двух запросах к приватным эндпоинтам.
Подпись устроена одинаково почти везде: собрать строку из параметров, добавить временную метку, посчитать HMAC и положить результат в заголовок. Освоив один раз, вы переносите схему на любую биржу.
И обязательно начинайте с тестовой сети — там те же эндпоинты, но реальные деньги не двигаются, а ключи выдаются отдельно от боевых.
<?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.
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 — от того, какие каналы вам нужны. Для нескольких символов удобнее держать один коннектор с массивом подписок, чем плодить соединения.
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 с ним ничего осмысленного сделать не может.
Боль всплывает в двух местах проекта: на этапе отладки, когда хочется глазами прочитать, что вернула биржа, и в проде, когда нужно разобрать лог инцидента через неделю. Если лог слипшийся, разбор ответа превращается в археологию по одной строке. Ниже — рабочий вывод, который мы ставим в проекты автоторговли.
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, мы разбирали выше. Здесь важно, что журнал пишется до отправки, а не после ответа.
Тестирование бота: бэктест, тестовая сеть и запуск в прод
Многие думают, что тестирование бота - это «прогнать стратегию на истории и посмотреть, красивая ли кривая доходности». это три разных проверки, каждая ловит свой класс ошибок. Бэктест проверяет логику: правильно ли бот считает сигналы, не путает ли стороны, не открывает ли вторую позицию поверх первой. Тестовая сеть проверяет интеграцию: проходит ли подпись запроса, соблюдаются ли лимиты, корректно ли бот разбирает ошибки биржи. А прод показывает реальную задержку. Между решением и исполнением всегда есть время, и на волатильном рынке оно стоит денег.
Пропустить бэктест - значит отлаживать логику живыми деньгами. Пропустить тестовую сеть - получить отказ подписи в самый неудачный момент. Пропустить «малый прод» - не узнать, что риск-менеджер считает размер позиции от устаревшей цены. Поэтому у нас в проектах автоторговли эти четыре этапа идут строго по порядку, и на каждом фиксируются метрики, по которым видно, можно ли двигаться дальше.
- Прогнать стратегию на исторических данных и зафиксировать метрики. Берём свечи и стакан за репрезентативный период, включая хотя бы один резкий разворот. Считаем не только доходность, но и просадку, число сделок, средний размер прибыли и убытка. Результат сохраняем в файл - к нему будем возвращаться после прода, чтобы сравнить ожидание с фактом.
- Подключиться к тестовой сети биржи и проверить подпись, лимиты, обработку ошибок. Тестнет даёт те же эндпоинты, что и боевой контур, но с фейковыми деньгами. Здесь ловим всё, что не видно в бэктесте: неверный timestamp, расхождение подписи, превышение rate limit, ответы с кодом ошибки вместо ожидаемых данных. Отдельно проверяем, что бот не падает, а корректно ретраит там, где это безопасно.
- Запустить в прод с минимальным размером позиции и включённым мониторингом. Не с полным депозитом - с размером, который не жалко потерять на отладке. Мониторинг на этом этапе важнее стратегии: логируем каждый запрос, ответ, факт исполнения и текущий P&L. Если что-то идёт не так - бот должен останавливаться сам, а не ждать, пока мы заметим.
- Сравнить фактические исполнения с ожидаемыми и скорректировать риск-менеджер. Берём сделки из прода и сопоставляем с тем, что бот планировал: цена входа, проскальзывание, время от сигнала до 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 мс, иначе результат будет оптимистичнее реальности.
Читайте также: Алготрейдинг с нуля: от идеи стратегии до боевого робота на бирже.