Backtesting orientado por eventos com Python - Parte I

Autora:Bem-estar, Criado: 2019-03-22 11:53:50, Atualizado:

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.

Software orientado por eventos

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 infinito conhecido como event-loop ou game-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.

Por que um teste de retorno orientado por eventos?

Os sistemas orientados por eventos oferecem muitas vantagens em relação a uma abordagem vetorizada:

  • Reutilização de código - Um backtester baseado em eventos, por concepção, pode ser usado tanto para backtesting histórico quanto para negociação ao vivo com um mínimo de interrupção de componentes.
  • Bias de Lookahead - Com um backtester orientado por eventos, não há preconceito de lookahead, pois a recepção de dados de mercado é tratada como um "evento" que deve ser agido.
  • Realismo - Os backtesters orientados por eventos permitem uma personalização significativa sobre como as ordens são executadas e os custos de transação são incorridos.

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.

Visão geral do backtester orientado por eventos

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:

  • Evento - O evento é a unidade de classe fundamental do sistema orientado por eventos. Ele contém um tipo (como MARKET, SIGNAL, ORDER ou FILL) que determina como ele será manuseado dentro do ciclo de eventos.
  • Coleta de eventos - A coleta de eventos é um objeto de coleta de Python em memória que armazena todos os objetos da subclasse Event que são gerados pelo resto do software.
  • DataHandler - O DataHandler é uma classe básica abstrata (ABC) que apresenta uma interface para lidar com dados históricos ou de mercado ao vivo. Isso fornece flexibilidade significativa, pois os módulos de Estratégia e Portfólio podem ser reutilizados entre ambas as abordagens. O DataHandler gera um novo MarketEvent a cada batimento cardíaco do sistema (veja abaixo).
  • Estratégia - A Estratégia é também um ABC que apresenta uma interface para a captação de dados de mercado e geração de Eventos de Sinais correspondentes, que são finalmente utilizados pelo objeto Portfólio.
  • Portfólio - Este é um ABC que lida com a gestão de ordens associadas às posições atuais e subsequentes para uma estratégia. Ele também realiza a gestão de risco em toda a carteira, incluindo a exposição do setor e o dimensionamento da posição. Em uma implementação mais sofisticada, isso pode ser delegado em uma classe de RiskManagement. O Portfólio leva SignalEvents da fila e gera OrderEvents que são adicionados à fila.
  • ExecutionHandler - O ExecutionHandler simula uma conexão com uma corretora. O trabalho do manipulador é pegar OrderEvents da fila e executá-los, seja através de uma abordagem simulada ou uma conexão real com uma corretora de fígado. Uma vez que as ordens são executadas, o manipulador cria FillEvents, que descrevem o que foi realmente transacionado, incluindo taxas, comissão e deslizamento (se modelado).
  • O Loop - Todos esses componentes são envolvidos em um ciclo de eventos que lida corretamente com todos os tipos de Eventos, encaminhando-os para o componente apropriado.

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.


Mais.