Событие-ориентированное обратное тестирование с помощью Python - часть I

Автор:Доброта, Создано: 2019-03-22 11:53:50, Обновлено:

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

Программное обеспечение на основе событий

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

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

while True:  # Run the loop forever
    new_event = get_new_event()   # Get the latest event

    # Based on the event type, perform an action
    if new_event.type == "LEFT_MOUSE_CLICK":
        open_menu()
    elif new_event.type == "ESCAPE_KEY_PRESS":
        quit_game()
    elif new_event.type == "UP_KEY_PRESS":
        move_player_north()
    # ... and many more events

    redraw_screen()   # Update the screen to provide animation
    tick(50)   # Wait 50 milliseconds

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

Почему мы используем обратный тест?

Системы, основанные на событиях, имеют множество преимуществ по сравнению с векторизированным подходом:

  • Повторное использование кода - по своей конструкции на основе событий базирующийся на обратном тесте можно использовать как для исторического обратного теста, так и для реального трейдинга с минимальным выключением компонентов.
  • Lookahead Bias - при обратном тесте, основанном на событиях, нет предвзятости, поскольку получение данных о рынке рассматривается как "событие", на которое необходимо действовать. Таким образом, можно "отпустить" обратный тестер, основанный на событиях, с данными рынка, повторяя поведение системы управления заказами и портфеля.
  • Реализм - событиями управляемые бэкстеры позволяют значительно настроить то, как выполняются заказы и возникают затраты на транзакции.

Несмотря на то, что системы, управляемые событиями, обладают множеством преимуществ, они страдают от двух основных недостатков по сравнению с более простыми векторизированными системами. Во-первых, их значительно сложнее внедрять и тестировать.

Во-вторых, они более медленны в выполнении по сравнению с векторизированной системой. Оптимальные векторизированные операции не могут быть использованы при проведении математических вычислений.

Общий обзор теста на основе событий

Чтобы применить подход, основанный на событиях, к системе обратного тестирования, необходимо определить наши компоненты (или объекты), которые будут выполнять конкретные задачи:

  • Событие - Событие является фундаментальной классовой единицей системы, основанной на событиях. Он содержит тип (например, MARKET, SIGNAL, ORDER или FILL), который определяет, как он будет обрабатываться в цикле событий.
  • Очередь событий (англ. Event Queue) - объект очереди событий в памяти Python, который хранит все объекты подкласса событий, которые генерируются остальной частью программного обеспечения.
  • DataHandler - это абстрактный базовый класс (ABC), который представляет собой интерфейс для обработки как исторических, так и реальных рыночных данных. Это обеспечивает значительную гибкость, поскольку модули Стратегии и Портфеля могут быть повторно использованы между обоими подходами.
  • Стратегия - Стратегия также представляет собой ABC, который представляет собой интерфейс для получения рыночных данных и генерации соответствующих SignalEvents, которые в конечном итоге используются объектом Portfolio.
  • Портфель - это ABC, который обрабатывает управление ордерами, связанными с текущими и последующими позициями для стратегии. Он также осуществляет управление рисками по всему портфелю, включая секторную экспозицию и размещение позиций. В более сложной реализации это может быть делегировано классу RiskManagement. Портфель берет SignalEvents из очереди и генерирует OrderEvents, которые добавляются в очередь.
  • ExecutionHandler - Исполнительный обработчик моделирует соединение с брокерским агентством. Задача обработчика заключается в том, чтобы взять OrderEvents из очереди и выполнить их, либо с помощью моделированного подхода, либо с помощью фактического соединения с брокерским агентством. После выполнения заказов обработчик создает FillEvents, которые описывают то, что фактически было совершено, включая сборы, комиссии и скольжение (если моделировано).
  • Круг - все эти компоненты завернуты в цикл событий, который правильно обрабатывает все типы событий, направляя их к соответствующему компоненту.

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

Вот фрагмент кода Python, который демонстрирует, как работает бэкстестер на практике. В коде происходят две петли. Внешняя петля используется для того, чтобы дать бэкстестру сердцебиение. Для живой торговли это частота, с которой опрошены новые рыночные данные. Для стратегий бэкстестинга это не является строго необходимым, поскольку бэкстестер использует рыночные данные, предоставленные в форме капельного питания (см. строку bars.update_bars)).

Внутренняя петля фактически обрабатывает события из объекта очереди событий. Конкретные события делегируются соответствующему компоненту и впоследствии в очередь добавляются новые события. Когда очередь событий пуста, петля сердечных событий продолжается:

# Declare the components with respective parameters
bars = DataHandler(..)
strategy = Strategy(..)
port = Portfolio(..)
broker = ExecutionHandler(..)

while True:
    # Update the bars (specific backtest code, as opposed to live trading)
    if bars.continue_backtest == True:
        bars.update_bars()
    else:
        break
    
    # Handle the events
    while True:
        try:
            event = events.get(False)
        except Queue.Empty:
            break
        else:
            if event is not None:
                if event.type == 'MARKET':
                    strategy.calculate_signals(event)
                    port.update_timeindex(event)

                elif event.type == 'SIGNAL':
                    port.update_signal(event)

                elif event.type == 'ORDER':
                    broker.execute_order(event)

                elif event.type == 'FILL':
                    port.update_fill(event)

    # 10-Minute heartbeat
    time.sleep(10*60)

В следующей статье мы рассмотрим иерархию классов событий.


Больше