Numba JIT für Backtests: Python-Speed in C-Geschwindigkeit.
Manche Backtest-Logiken lassen sich einfach nicht vektorisieren. Stop-Loss-Logik mit Trailing-Stop, Order-Queue-Simulation, Event-Driven-Loops — alles Code, der natürlich als Schleife geschrieben wird. Numba macht aus diesen Schleifen kompilierten Maschinencode, mit einer einzigen Zeile Decorator. Faktor 50–200 ist Standard.
Was Numba ist und wie es funktioniert.
Numba ist ein Just-In-Time-Compiler für Python, der auf LLVM aufsetzt. Sie
dekorieren eine Funktion mit @njit, und beim ersten Aufruf wird sie zu
nativem Maschinencode kompiliert. Folgende Aufrufe laufen ohne Python-Interpreter
— die Schleife läuft im Tempo von handgeschriebenem C.
Der Trick: Numba unterstützt nur eine Untermenge von Python — vor allem NumPy-Arrays und numerische Typen. Pandas-DataFrames, dicts, Strings funktionieren entweder gar nicht oder nur eingeschränkt. Für Trading-Backtests ist das selten ein Problem, weil die heißen Schleifen meist auf NumPy-Arrays arbeiten.
Erstes Beispiel: Trailing-Stop-Simulation.
Klassischer Fall, den man nicht sauber vektorisieren kann: ein Trailing-Stop, der dem höchsten Preis seit Entry folgt und auslöst, sobald der Preis um X % fällt.
import numpy as np
from numba import njit
@njit(cache=True)
def trailing_stop_backtest(prices, entries, stop_pct=0.02):
n = len(prices)
position = 0
entry_price = 0.0
peak = 0.0
pnl = np.zeros(n)
cum = 0.0
for i in range(n):
price = prices[i]
if position == 0 and entries[i]:
position = 1
entry_price = price
peak = price
elif position == 1:
if price > peak:
peak = price
if price < peak * (1 - stop_pct):
# Exit
cum += (price - entry_price) / entry_price
position = 0
pnl[i] = cum
return pnl
Auf einer Million Bars läuft diese Funktion in unter 100 Millisekunden — ohne Decorator wären es 30 bis 60 Sekunden. Faktor 300 bis 600, ohne die Logik umzuschreiben.
Wann sich Numba lohnt.
Die Faustregel: wenn Sie eine Schleife haben, die nicht vektorisierbar ist (weil jeder Schritt vom vorherigen abhängt) und die mit nur numerischen Daten arbeitet, ist Numba die richtige Wahl. Typische Fälle im Trading:
- Custom-Indikatoren mit Pfadabhängigkeit (Kaufman AMA, Volume-Profil-Berechnungen)
- Event-Driven-Backtester mit Bar-für-Bar-Logik
- Monte-Carlo-Simulationen für Strategie-Robustheit
- Order-Book-Simulationen mit Tick-für-Tick-Matching
- Walk-Forward-Loops über Tausende Parameter-Kombinationen
Wo Numba nicht funktioniert.
Numba unterstützt nicht alles. Stolperfallen aus der Praxis:
- Pandas-DataFrames in
@njit-Funktionen — Sie müssen vorher zu NumPy konvertieren (df.values). - Object-Typen wie Strings oder Dicts — nur eingeschränkt, oft Performance-tot.
- Externe Bibliotheken — scipy, statsmodels, sklearn funktionieren nicht im JIT-Modus.
- Lange Kompilier-Zeiten beim ersten Aufruf — bei großen Funktionen mehrere Sekunden. Lösung:
cache=Truespeichert den Compile. - Schwierige Fehlersuche — JIT-Code ist im Debugger schwer zu inspizieren. Tipp: erst ohne Decorator testen, dann annotieren.
Parallelisierung mit prange.
Numba kann Schleifen automatisch parallelisieren — auf alle Kerne. Voraussetzung: keine Datenabhängigkeiten zwischen den Iterationen. Ein klassischer Anwendungsfall ist ein Parameter-Sweep, bei dem jede Kombination unabhängig getestet wird.
import numpy as np
from numba import njit, prange
@njit(parallel=True, cache=True)
def parameter_sweep(prices, fast_periods, slow_periods):
n_fast = len(fast_periods)
n_slow = len(slow_periods)
sharpe = np.zeros((n_fast, n_slow))
for i in prange(n_fast):
for j in range(n_slow):
fast = fast_periods[i]
slow = slow_periods[j]
if fast >= slow:
continue
sharpe[i, j] = run_strategy(prices, fast, slow)
return sharpe
@njit(cache=True)
def run_strategy(prices, fast, slow):
# ... Single-Combo-Backtest
return 1.5 # Dummy
Bei 8 Kernen erwartet man Faktor 6–7 Speedup — die fehlenden zwei Punkte gehen für Cache-Effekte und Overhead drauf. Trotzdem: Ein Parameter-Sweep, der seriell 20 Minuten lief, ist parallel in 3 Minuten durch.
Numba vs Alternativen.
Numba ist nicht der einzige Weg, Python schneller zu machen. Wer schon Polars benutzt, hat für vektorisierbaren Code oft eine bessere Lösung. Cython gibt mehr Kontrolle, ist aber deutlich aufwendiger im Setup. Rust-Bindings (über pyo3) sind für maximale Performance ideal, aber überdimensioniert für die meisten Backtest- Workloads.
Mein Stack: Pandas/Polars für Datenvorbereitung und vektorisierbare Indikatoren, Numba für die unvektorisierbaren Schleifen, Rust nur dort, wo Numba an Grenzen stößt — was selten passiert.
Tipps für robusten Numba-Code.
- Explizite Typen bei kritischen Funktionen:
@njit('float64[:](float64[:], int32)')macht den Compile-Zeitpunkt deterministisch. - cache=True immer setzen — verhindert Recompile bei jedem Skript-Start.
- Keine globalen Variablen — Numba mag das nicht, und es macht den Code schwer wiederverwendbar.
- Funktionen klein halten — Compile-Zeit skaliert nichtlinear mit Funktionsgröße.
- Erst Logik, dann Annotation: erst die reine Python-Version testen, dann
@njitdrauf, dann benchmarken. - Vorsicht bei nopython=False: ohne strict nopython fällt Numba auf Object-Mode zurück und ist langsam.
Realer Praxisfall.
In einem Projekt für einen Optionsmarket-Maker hatten wir einen Backtest, der pro Marktdaten-Tag etwa 4 Stunden lief. Der heiße Loop war eine bar-by-bar Option-Pricing-Simulation mit Greek-Berechnung — naturgemäß nicht vektorisierbar, weil jeder Hedge vom vorherigen Bestand abhängt.
Mit Numba inkl. prange über die Underlying-Pfade ging die Laufzeit
auf 6 Minuten runter. Vier Stunden zu sechs Minuten — Faktor 40 — und damit der
Unterschied zwischen „ein Test pro Tag" und „dreißig Tests pro Stunde". Diese
Iterationsgeschwindigkeit hat die gesamte Strategie-Entwicklung verändert.
Fazit.
Numba ist das beste Werkzeug in der Python-Welt, wenn Sie unvektorisierbare Trading-Logik schnell machen wollen. Ein Decorator, manchmal eine Typdeklaration — und Sie sind bei C-Speed, ohne C zu schreiben. Wer Backtests baut und Numba nicht kennt, verschenkt Stunden pro Woche. Wer einmal damit gearbeitet hat, will nicht mehr zurück.
Ihr Backtest läuft Stunden und Sie wissen, dass die heiße Schleife das Problem ist? Erstgespräch buchen — wir bringen den richtigen Code in den richtigen JIT.