Tests arrière basés sur des événements avec Python - Partie I

Auteur:La bonté, Créé: 2019-03-22 11:53:50, Mis à jour:

Nous avons passé les deux derniers mois sur QuantStart à tester en arrière diverses stratégies de trading en utilisant Python et les pandas. La nature vectorialisée des pandas garantit que certaines opérations sur de grands ensembles de données sont extrêmement rapides. Cependant, les formes de backtest vectorialisées que nous avons étudiées à ce jour souffrent de certains inconvénients dans la façon dont l'exécution des transactions est simulée. Dans cette série d'articles, nous allons discuter d'une approche plus réaliste de la simulation de stratégie historique en construisant un environnement de backtesting basé sur des événements en utilisant Python.

Logiciel axé sur les événements

Avant d'approfondir le développement d'un tel backtester, nous devons comprendre le concept de systèmes basés sur des événements. Les jeux vidéo fournissent un cas d'utilisation naturel pour les logiciels basés sur des événements et fournissent un exemple simple à explorer. Un jeu vidéo a plusieurs composants qui interagissent les uns avec les autres dans un environnement en temps réel à des fréquences d'images élevées.

À chaque clic de la boucle de jeu, une fonction est appelée pour recevoir le dernier événement, qui aura été généré par une action antérieure correspondante dans le jeu. Selon la nature de l'événement, qui pourrait inclure une pression sur une touche ou un clic de souris, une action ultérieure est effectuée, ce qui terminera la boucle ou générera des événements supplémentaires. Le processus se poursuivra. Voici un exemple de pseudo-code:

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

Le code vérifie en permanence les nouveaux événements, puis exécute des actions basées sur ces événements. En particulier, il permet l'illusion d'un traitement de réponse en temps réel, car le code est continuellement en boucle et les événements vérifiés.

Pourquoi un backtester basé sur des événements?

Les systèmes axés sur les événements offrent de nombreux avantages par rapport à une approche vectorialisée:

  • Reutilisation du code - Un backtester basé sur des événements peut, par conception, être utilisé à la fois pour le backtesting historique et pour le trading en direct avec un échange minimal de composants.
  • Le biais de la recherche - Avec un backtester axé sur les événements, il n'y a pas de biais de la recherche car la réception des données de marché est traitée comme un "événement" sur lequel il faut agir.
  • Réalisme - Les backtesters basés sur les événements permettent une personnalisation significative de la façon dont les ordres sont exécutés et les coûts de transaction encourus.

Bien que les systèmes axés sur les événements présentent de nombreux avantages, ils présentent deux inconvénients majeurs par rapport aux systèmes vectorialisés plus simples. Premièrement, ils sont beaucoup plus complexes à implémenter et à tester.

Deuxièmement, ils sont plus lents à exécuter par rapport à un système vectorié. Les opérations vectoriées optimales ne peuvent pas être utilisées lors de l'exécution de calculs mathématiques.

Vue d'ensemble du backtester basé sur les événements

Pour appliquer une approche basée sur les événements à un système de backtesting, il est nécessaire de définir nos composants (ou objets) qui géreront des tâches spécifiques:

  • Event - L'événement est l'unité de classe fondamentale du système basé sur des événements. Il contient un type (comme MARKET, SIGNAL, ORDER ou FILL) qui détermine comment il sera traité dans la boucle d'événements.
  • La file d'attente d'événement est un objet Python queue en mémoire qui stocke tous les objets de la sous-classe d'événement générés par le reste du logiciel.
  • DataHandler - Le DataHandler est une classe de base abstraite (ABC) qui présente une interface pour le traitement des données historiques ou en direct du marché. Cela offre une flexibilité significative car les modules Stratégie et Portfolio peuvent donc être réutilisés entre les deux approches.
  • Stratégie - La Stratégie est également un ABC qui présente une interface pour prendre des données de marché et générer des SignalEvents correspondants, qui sont finalement utilisés par l'objet Portfolio.
  • Portfolio - Il s'agit d'un ABC qui gère la gestion des ordres associés aux positions actuelles et ultérieures pour une stratégie. Il effectue également la gestion des risques dans l'ensemble du portefeuille, y compris l'exposition sectorielle et la taille des positions. Dans une implémentation plus sophistiquée, cela pourrait être délégué à une classe RiskManagement. Le portefeuille prend les événements de signal de la file d'attente et génère des événements d'ordre qui sont ajoutés à la file d'attente.
  • ExecutionHandler - Le ExecutionHandler simule une connexion à un courtier. Le travail du gestionnaire est de prendre les OrderEvents de la file d'attente et de les exécuter, soit via une approche simulée ou une connexion réelle à un courtier. Une fois les ordres exécutés, le gestionnaire crée FillEvents, qui décrivent ce qui a réellement été transait, y compris les frais, la commission et le glissement (si modélisé).
  • La boucle - Tous ces composants sont enveloppés dans une boucle d'événement qui gère correctement tous les types d'événement, les acheminant vers le composant approprié.

Il s'agit d'un modèle assez basique d'un moteur de trading. Il y a une marge d'expansion significative, en particulier en ce qui concerne la façon dont le portefeuille est utilisé. En outre, différents modèles de coûts de transaction pourraient également être abstraits dans leur propre hiérarchie de classe. À ce stade, il introduit une complexité inutile dans cette série d'articles, nous n'en discuterons donc pas davantage.

Voici un extrait de code Python qui démontre comment le backtester fonctionne dans la pratique. Il y a deux boucles dans le code. La boucle externe est utilisée pour donner un battement de cœur au backtester. Pour le trading en direct, c'est la fréquence à laquelle de nouvelles données de marché sont pollées. Pour les stratégies de backtesting, cela n'est pas strictement nécessaire car le backtester utilise les données de marché fournies sous forme de drip-feed (voir la ligne bars.update_bars)).

La boucle interne gère en fait les événements de l'objet queue d'événements. Des événements spécifiques sont délégués au composant respectif et de nouveaux événements sont ensuite ajoutés à la file d'attente. Lorsque la file d'attente d'événements est vide, la boucle de battement de cœur continue:

# 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)

Dans l'article suivant, nous aborderons la hiérarchie des classes d'événements.


Plus de