Мы провели последние пару месяцев на 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
Код постоянно проверяет на наличие новых событий, а затем выполняет действия, основанные на этих событиях. В частности, он позволяет иллюзию обработки ответа в режиме реального времени, потому что код постоянно закручивается и проверяются события. Как станет ясно, это именно то, что нам нужно для выполнения высокочастотного трейдингового моделирования.
Системы, основанные на событиях, имеют множество преимуществ по сравнению с векторизированным подходом:
Несмотря на то, что системы, управляемые событиями, обладают множеством преимуществ, они страдают от двух основных недостатков по сравнению с более простыми векторизированными системами. Во-первых, их значительно сложнее внедрять и тестировать.
Во-вторых, они более медленны в выполнении по сравнению с векторизированной системой. Оптимальные векторизированные операции не могут быть использованы при проведении математических вычислений.
Чтобы применить подход, основанный на событиях, к системе обратного тестирования, необходимо определить наши компоненты (или объекты), которые будут выполнять конкретные задачи:
Это довольно основная модель торговой системы. Существует значительное пространство для расширения, особенно в отношении того, как используется портфель. Кроме того, различные модели затрат на транзакции также могут быть абстрагированы в свою собственную иерархию классов. На данном этапе она вводит ненужную сложность в рамках этой серии статей, поэтому мы не будем в настоящее время обсуждать ее дальше.
Вот фрагмент кода 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)
В следующей статье мы рассмотрим иерархию классов событий.