Finam

алготрейдинг  ·  python  ·  live execution

// больше никакого wait
.. только await

Как экосистема Zipline эволюционировала к асинхронной живой торговле — и почему одно ключевое слово перед каждым вызовом функции оказалось именно тем ответом, к которому мы все так долго шли.

Я провёл несколько лет на Quantopian. Писал стратегии, запускал бэктесты, смотрел на кривые доходности — убеждал себя, что готов к реальному рынку. Когда платформа закрылась в 2020 году, я, как и многие другие, отправился искать, что будет дальше. Я обнаружил богатую, искреннюю, временами раздражающую экосистему форков, каждый из которых пытался ответить на один и тот же вопрос: как сохранить API в стиле Quantopian и при этом реально торговать вживую?

Я прошёл через большинство из них. Каждый чему-то меня учил. И каждый по-своему упирался в одну и ту же стену.


Экосистема

История форков Zipline — один из лучших примеров того, насколько сложно объединить бэктестинг и живое исполнение в одном фреймворке. Каждый проект нашёл собственный подход, и каждый внёс что-то реальное в сообщество.

zipline-live архивирован

Один из первых форков, всерьёз взявшихся за живую торговлю — подключение Zipline к Interactive Brokers через API TWS. Настоящий первопроходец: он доказал, что концепция работоспособна, и дал сообществу первый реальный опыт живого исполнения, совместимого с Zipline. Авторы работали в рамках существующего синхронного ядра, пробрасывая данные брокера в реальном времени через потоки. Проект больше не поддерживается, но как доказательство концепции своего времени он указал путь всему, что последовало за ним.

zipline-reloaded поддерживается

Пожалуй, самый честный проект в этом списке — и я говорю это как настоящий комплимент. zipline-reloaded чётко обозначил свою нишу: сохранить оригинальный Zipline живым и работоспособным на современном Python с актуальными зависимостями. Никаких обещаний живой торговли, никаких завышенных амбиций. Если вам нужен надёжный бэктестинг с привычным API — это сильный, хорошо поддерживаемый выбор. Он точно знает, что он такое.

zipline-trader низкая активность

Значительный шаг вперёд: Python 3, поддержка Alpaca, расширенные календари для крипторынков. Авторы проделали реальную работу по модернизации стека и дали сообществу рабочий инструмент для запуска первых живых стратегий. Интеграция с брокером строилась на опросе в отдельном потоке — прагматичный выбор в рамках ограничений существующей архитектуры. С тех пор активность поутихла, но проект оставил заметный след и, вероятно, помог многим людям совершить их первую живую сделку.

zipline-live

open source · архивирован

Первооткрыватель. Доказал, что живая торговля возможна в API-совместимой с Zipline среде. IB через TWS.

zipline-reloaded

open source · активен

Только бэктестинг, но сделан хорошо. Современный Python, активная поддержка, честный охват.

zipline-trader

open source · низкая активность

Python 3, Alpaca, криптокалендари. Довёл людей до первого живого исполнения.

Ziplime

open source · активен

Ядро на async. await везде. Брокер — часть архитектуры с первого дня. → github

Эти проекты образуют преемственность, а не конкуренцию. Ziplime стоит на плечах работы, проделанной до него — без того, что zipline-live и zipline-trader нанесли на карту, было бы значительно труднее увидеть, где именно находится архитектурный потолок.


Где находится потолок

Все форки до Ziplime работали с одним и тем же синхронным ядром Zipline — и это был разумный выбор. Переписывать фундамент дорого, это ломает обратную совместимость и меняет публичный контракт каждой функции, которой люди пользовались годами. Добавить поверх обёртку на потоках куда практичнее.

Но у такого подхода есть потолок. В продакшне каждый привычный вызов — это сетевой запрос:

zipline и форки — синхронный контракт
# в бэктесте выглядит мгновенным — в живой торговле блокирует поток
df            = data.history(assets=[asset], fields=["close"], bar_count=context.long_window)
context.asset = context.symbol("AAPL")
order_buy     = context.order_target_percent(asset=asset, target=1.0, style=MarketOrder())

В бэктесте данные берутся с диска — всё мгновенно. В живой торговле каждый из этих вызовов может занять 50–500 мс в ожидании ответа брокера или поставщика данных. Синхронный код блокирует весь поток на это время. При стратегии на несколько инструментов задержки накапливаются на каждом тике.

Потоки решают часть проблемы, но порождают собственные: разделяемое изменяемое состояние, незаметные гонки, трудновоспроизводимые баги под нагрузкой. Именно в этот потолок упёрлись zipline-live и zipline-trader — не из-за недостатка усилий, а потому что ограничение было архитектурным.

«Нельзя прикрутить async к синхронному фундаменту снаружи и ожидать, что он выдержит реальные рыночные условия.»

Что меняет Ziplime

Ziplime сделал шаг, которого другие форки разумно избегали: переписал ядро движка под асинхронную модель. Каждая функция, которая касается I/O, стала корутиной — и это непосредственно видно в API:

ziplime — асинхронный контракт
async def handle_data(context, data):

  # неблокирующий — event loop остаётся свободным, пока мы ждём брокера
  df = await data.history(
      assets=[asset],
      fields=["close"],
      bar_count=context.long_window
  )

  context.asset = await context.symbol("AAPL")

  order_buy = await context.order_target_percent(
      asset=asset,
      target=1.0,
      style=MarketOrder()
  )

За каждым await — передача управления event loop'у Python'овского asyncio. Пока одна корутина ждёт ответа брокера, loop может заниматься другой работой — обрабатывать другой инструмент, слушать WebSocket-поток, писать в лог. Без потоков, без разделяемого состояния, полностью детерминированно.

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

Синхронное ядро

Задержки накапливаются между вызовами. Потоки как обходное решение. Брокер — внешний слой, навешанный поверх движка.

Ziplime: async-ядро

Event loop управляет всем I/O. Параллельные запросы через asyncio.gather(). Брокер — полноправный участник архитектуры с первого дня.

Для стратегий с несколькими инструментами это имеет существенное значение. Вместо того чтобы последовательно запрашивать историю по десяти инструментам, можно отправить все десять запросов конкурентно через asyncio.gather() и ждать завершения самого медленного — а не суммы всех.


Заключение

Глядя на экосистему форков Zipline как на единую дугу, видишь сообщество, неуклонно движущееся к чему-то. zipline-reloaded поддерживает оригинал живым. zipline-live и zipline-trader нанесли на карту, как выглядит живая торговля в этом API-пространстве — и где оно начинает трещать.

Ziplime взял накопленные знания и задал другой вопрос: а что если вместо того, чтобы адаптировать синхронный движок к реальному рынку, спроектировать движок так, чтобы реальный рынок ощущался в нём родным?

Годами мы ждали (wait), пока живая торговля в мире Zipline заработает как надо. Оказалось, что ответ — просто await.

github.com/Limex-com/ziplime

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

Смотреть на GitHub