Wir haben die letzten paar Monate damit verbracht, bei QuantStart verschiedene Handelsstrategien unter Verwendung von Python und Pandas zu backtesten. Die vektorisierte Natur von Pandas stellt sicher, dass bestimmte Operationen auf großen Datensätzen extrem schnell sind. Die bisher untersuchten Formen des vektorisierten Backtesters leiden jedoch unter einigen Nachteilen bei der Simulation der Handelsdurchführung. In dieser Artikelreihe werden wir einen realistischeren Ansatz zur historischen Strategie-Simulation durch den Aufbau einer ereignisgesteuerten Backtestumgebung unter Verwendung von Python diskutieren.
Bevor wir uns mit der Entwicklung eines solchen Backtesters beschäftigen, müssen wir das Konzept von ereignisgesteuerten Systemen verstehen. Videospiele bieten einen natürlichen Anwendungsfall für ereignisgesteuerte Software und ein einfaches Beispiel zum Erforschen. Ein Videospiele hat mehrere Komponenten, die in Echtzeit mit hohen Frameraten miteinander interagieren. Dies wird durch Ausführen der gesamten Reihe von Berechnungen innerhalb einer
Bei jedem Ticken der Spielschleife wird eine Funktion aufgerufen, um das neueste Ereignis zu empfangen, das durch eine entsprechende vorherige Aktion innerhalb des Spiels generiert wurde. Je nach Art des Ereignisses, das einen Tastendruck oder einen Mausklick umfassen könnte, wird eine nachfolgende Aktion durchgeführt, die entweder die Schleife beendet oder einige zusätzliche Ereignisse generiert. Der Prozess wird dann fortgesetzt. Hier sind einige Beispiele für 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
Der Code überprüft kontinuierlich nach neuen Ereignissen und führt dann Aktionen aus, die auf diesen Ereignissen basieren. Insbesondere erlaubt er die Illusion der Echtzeit-Reaktionsbehandlung, da der Code kontinuierlich geschleppt und Ereignisse nachgeprüft wird. Wie sich herausstellen wird, ist dies genau das, was wir benötigen, um eine Hochfrequenz-Handelssimulation durchzuführen.
Ereignisgesteuerte Systeme bieten viele Vorteile gegenüber einem vektorisierten Ansatz:
Obwohl Ereignisgesteuerte Systeme viele Vorteile bieten, leiden sie unter zwei großen Nachteilen gegenüber einfacheren vektorisierten Systemen. Erstens sind sie wesentlich komplexer zu implementieren und zu testen. Es gibt mehr bewegliche Teile, die zu einer größeren Wahrscheinlichkeit der Einführung von Fehlern führen.
Zweitens sind sie im Vergleich zu einem vektorisierten System langsamer auszuführen. Optimale vektorisierte Operationen können bei mathematischen Berechnungen nicht verwendet werden. Wir werden in späteren Artikeln über Möglichkeiten zur Überwindung dieser Einschränkungen sprechen.
Um einen ereignisgesteuerten Ansatz für ein Backtesting-System anzuwenden, ist es notwendig, unsere Komponenten (oder Objekte), die spezifische Aufgaben bewältigen, zu definieren:
Dies ist ein ziemlich grundlegendes Modell einer Handelsmaschine. Es gibt erheblichen Spielraum für die Erweiterung, insbesondere in Bezug auf die Verwendung des Portfolios. Darüber hinaus können unterschiedliche Transaktionskostenmodelle auch in ihre eigene Klassenhierarchie abstrahiert werden. In diesem Stadium führt es unnötige Komplexität in diese Artikelreihe ein, so dass wir es derzeit nicht weiter diskutieren werden. In späteren Tutorials werden wir das System wahrscheinlich erweitern, um zusätzlichen Realismus einzubeziehen.
Hier ist ein Auszug aus Python-Code, der zeigt, wie der Backtester in der Praxis funktioniert. Es gibt zwei Schleifen im Code. Die äußere Schleife wird verwendet, um dem Backtester einen Herzschlag zu geben. Für den Live-Handel ist dies die Häufigkeit, mit der neue Marktdaten erfasst werden. Für Backtesting-Strategien ist dies nicht unbedingt notwendig, da der Backtester die Marktdaten in Tropf-Feed-Form verwendet (siehe die Zeile bars.update_bars)).
Die Innenschleife verarbeitet tatsächlich die Ereignisse aus dem Event-Warteschlange-Objekt. Spezifische Ereignisse werden an die jeweilige Komponente delegiert und anschließend werden neue Ereignisse in die Warteschlange hinzugefügt. Wenn die Ereignis-Warteschlange leer ist, geht die Herzschlagschleife weiter:
# 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)
Dies ist der grundlegende Überblick, wie ein ereignisgesteuerter Backtester entwickelt wird.