← Alle Insights

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:

Wo Numba nicht funktioniert.

Numba unterstützt nicht alles. Stolperfallen aus der Praxis:

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.

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.