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.
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.
Les systèmes axés sur les événements offrent de nombreux avantages par rapport à une approche vectorialisée:
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.
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:
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.