Nós passamos os últimos dois meses no QuantStart testando várias estratégias de negociação usando Python e pandas. A natureza vetorizada dos pandas garante que certas operações em grandes conjuntos de dados sejam extremamente rápidas. No entanto, as formas de backtester vetorizado que estudamos até agora sofrem algumas desvantagens na forma como a execução do comércio é simulada. Nesta série de artigos vamos discutir uma abordagem mais realista para a simulação de estratégia histórica construindo um ambiente de backtesting baseado em eventos usando Python.
Antes de nos aprofundarmos no desenvolvimento de tal backtester, precisamos entender o conceito de sistemas orientados a eventos. Os videogames fornecem um caso de uso natural para o software orientado a eventos e fornecem um exemplo direto para explorar. Um videogame tem vários componentes que interagem entre si em um ambiente em tempo real a altas taxas de quadros. Isso é manuseado executando todo o conjunto de cálculos dentro de um loop
Em cada tique do loop de jogo, uma função é chamada para receber o último evento, que será gerado por alguma ação anterior correspondente dentro do jogo. Dependendo da natureza do evento, que pode incluir um toque ou um clique do mouse, alguma ação subsequente é tomada, que terminará o loop ou gerará alguns eventos adicionais. O processo continuará. Aqui está um exemplo de pseudo-código:
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
O código está continuamente verificando novos eventos e, em seguida, executando ações baseadas nesses eventos. Em particular, permite a ilusão de manuseio de resposta em tempo real porque o código está continuamente sendo looped e eventos verificados.
Os sistemas orientados por eventos oferecem muitas vantagens em relação a uma abordagem vetorizada:
Embora os sistemas orientados por eventos tenham muitos benefícios, eles sofrem de duas grandes desvantagens em relação aos sistemas vetorializados mais simples. Em primeiro lugar, eles são significativamente mais complexos de implementar e testar. Existem mais partes em movimento, levando a uma maior chance de introdução de bugs. Para mitigar essa metodologia de teste de software adequada, como o desenvolvimento orientado a testes, pode ser empregada.
Em segundo lugar, eles são mais lentos de executar em comparação com um sistema vectorizado. Operações vectorizadas ideais não podem ser utilizadas ao realizar cálculos matemáticos.
Para aplicar uma abordagem orientada por eventos a um sistema de backtesting, é necessário definir os nossos componentes (ou objetos) que lidarão com tarefas específicas:
Este é um modelo bastante básico de um motor de negociação. Há espaço significativo para expansão, particularmente no que diz respeito à forma como o Portfólio é usado. Além disso, diferentes modelos de custo de transação também podem ser abstraídos em sua própria hierarquia de classe.
Aqui está um trecho do código Python que demonstra como o backtester funciona na prática. Há dois loops ocorrendo no código. O loop externo é usado para dar ao backtester um batimento cardíaco. Para negociação ao vivo, esta é a frequência com que novos dados de mercado são pesquisados. Para estratégias de backtesting, isso não é estritamente necessário, pois o backtester usa os dados de mercado fornecidos em forma de gotejamento (veja a linha bars.update_bars)).
O loop interno realmente lida com os Eventos do objeto Eventos Queue. Eventos específicos são delegados no respectivo componente e, posteriormente, novos eventos são adicionados à fila. Quando o Eventos Queue está vazio, o loop de batimentos cardíacos continua:
# 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)
Este é o esboço básico de como um backtester orientado por eventos é projetado.