Архитектура торгового бота: слои, которые нельзя смешивать
Почти каждый бот, ставший неподдерживаемым, совершил одну и ту же ошибку — позволил логике стратегии напрямую дотянуться до биржи.
Инженеры Vizanix · об авторе
- РАЗДЕЛ
- Архитектура ботов
- ОПУБЛИКОВАНО
- 2026-08-28
- ГЛАВ
- 6
- ЧИТАТЬ ДАЛЬШЕ
- 3
- ЯЗЫК
- написано на русском
Торговый бот — это не одна программа. Это пять забот, которые случайно работают в одном процессе, и разница между системой, которую можно менять, и системой, которую нельзя, — в том, видят ли эти заботы друг друга.
Пять слоёв
| Слой | Отвечает за | Не должен знать про |
|---|---|---|
| Транспорт | REST-клиент, WebSocket, подпись, лимиты, повторы | Стратегии, позиции, P&L |
| Реестр инструментов | Символы, фильтры, категории, шаг цены и лота | Всё, что меняется во времени |
| Состояние рынка | Стаканы, свечи, фандинг, целостность последовательности | Ордера, намерения |
| Стратегия | Сигналы: хочу быть в лонге на 3 единицы X | API биржи, ID ордеров, HTTP |
| Риск и исполнение | Превращение намерения в ордера при ограничениях | Почему стратегия этого захотела |
Критична четвёртая строка. Стратегия выражает намерение — желаемую позицию — и никогда не ставит ордер сама. Всё, что ниже по течению, вправе отказать, изменить объём, отложить или разбить это намерение.
Почему граница стратегии так важна
Когда код стратегии обращается к бирже напрямую, сразу четыре вещи становятся невозможными — и обычно вы обнаруживаете все четыре в один плохой день:
- Бэктест. Нельзя прогнать стратегию, которая ждёт HTTP-клиент. Подменив биржу моком, вы получите тест мока.
- Paper trading. Та же проблема, только вживую.
- Общий риск. Когда три стратегии сами ставят ордера, никто не знает суммарной экспозиции до того, как она уже набрана.
- Смена площадки. Новая биржа означает переписывание каждой стратегии вместо одного адаптера.
# Стратегия говорит намерением. Она не знает, что биржа существует.
@dataclass(frozen=True)
class Intent:
symbol: str
target_qty: Decimal # со знаком: отрицательное — шорт
stop: Decimal | None
reason: str # уходит в журнал аудита дословно
class ImpulseReversal:
def on_bar(self, bar, state) -> Intent | None:
...
return Intent(bar.symbol, -size, stop=level, reason="rsi_overbought_series_4")
# Риск и исполнение решают, что на самом деле произойдёт с этим намерением.
def handle(intent: Intent):
approved = risk.evaluate(intent, portfolio.snapshot())
if approved.rejected:
audit.log(intent, approved.reason)
return
executor.reconcile_to(approved.target_qty, intent.symbol)Поле reason выглядит любезностью. На деле именно оно позволяет через шесть недель ответить на вопрос «почему бот это сделал». Наш сигнальный движок сохраняет причину для каждой оценки, включая отказы, — поэтому «почему сегодня нет входов» это запрос к базе, а не сеанс отладки.
Целевая позиция, а не купить и продать
Выражайте намерение как желаемое конечное состояние, а не как действие. «Быть в лонге на 3» идемпотентно: если бот перезапустился и повторил это, уже держа 3, сверщик не сделает ничего. «Купить 3» — нет: тот же перезапуск купит ещё 3.
Один этот выбор убирает целый класс багов с двойным исполнением и делает восстановление после обрыва тривиальным: приводим к цели, что бы ни случилось во время разрыва.
Пульт работает рядом, а не внутри
Операторам нужно запускать, останавливать, настраивать и смотреть. Этот интерфейс не должен уметь застопорить торговый цикл. В нашем микроструктурном движке Telegram-пульт — отдельный процесс, общающийся через Unix-сокет: реалтайм-цикл никогда не делает сетевой вызов к API чата, не ждёт его и не может быть заблокирован его медлительностью.
Общее правило: всё с непредсказуемой задержкой — чат-API, дашборды, отгрузка логов, обучение моделей — живёт вне цикла, который обязан ответить на рыночное событие за миллисекунды.
Хранение не на горячем пути
Записывать всё — это то, что делает систему честной, а писать синхронно — то, что делает её медленной. Наш сигнальный движок держит 5,76 миллиона оценок за пятнадцатидневное окно — каждую оценку, а не только сработавшие — и делает это пакетными записями в стороне от пути оценки, с индексами под реально выполняемые запросы.
Сканер, который блокируется на записи на диск, перестал быть сканером. Буферизуйте, батчите и позвольте писателю отставать, если надо: торговое решение не зависит от того, что строка уже долетела до диска.
Как выглядит хорошо
- Слой стратегии запускается на исторических данных вообще без сети.
- Вторая площадка добавляется написанием одного адаптера.
- На вопрос «какова была суммарная экспозиция в 14:32» отвечает хранилище.
- Убийство пульта не влияет на торговлю.
- Перезапуск процесса восстанавливает состояние с биржи, а не из локального файла.
Ничего экзотического здесь нет. Это обычное разделение на слои, применённое к области, где цена дырявой границы измеряется деньгами, а не временем на рефакторинг.
Статья описывает инженерную практику. Это не инвестиционная рекомендация. Vizanix разрабатывает программное обеспечение и не обещает торговую доходность.