← Alle Insights

Pandas-Performance für Trading: Vektorisierung, Speicher, Bottlenecks.

Pandas ist im Quant-Bereich allgegenwärtig — und gleichzeitig der häufigste Grund, warum Backtests stundenlang laufen. Die Bibliothek ist schnell, wenn man sie richtig benutzt. Sie ist quälend langsam, wenn man sie wie eine for-Schleife behandelt. In diesem Artikel zeige ich Ihnen, wo die echten Bremsen sitzen und wie Sie typische Trading-Workloads um Faktor 10 bis 100 beschleunigen.

Warum Pandas langsam wird.

Pandas wird in 95 % der Fälle nicht durch das Datenvolumen langsam, sondern durch den Programmierstil. Wer eine Million Bars in eine for-Schleife packt und Zeile für Zeile mit iloc zugreift, bekommt Laufzeiten im Minutenbereich. Die gleiche Operation als Vektor-Ausdruck dauert Millisekunden. Der Unterschied: Pandas ruft im Vektor-Fall hochoptimierten C-Code in NumPy auf, während die Python-Schleife den Interpreter für jeden Wert neu durchläuft.

Drei Hauptursachen für langsame Pandas-Workflows: erstens iterative Zugriffsmuster (iterrows, itertuples, apply mit Python- Lambda), zweitens unnötiges Kopieren von DataFrames durch chained assignment, drittens falsche Datentypen — object-Spalten statt category oder float32 kosten Speicher und CPU-Cycles.

Vektorisierung in der Praxis.

Vektorisierung heißt: Operationen auf ganzen Spalten ausdrücken, nicht auf einzelnen Werten. Ein klassisches Beispiel sind technische Indikatoren. Hier ein RSI, einmal naiv mit Schleife, einmal vektorisiert:

import pandas as pd
import numpy as np

# Variante 1: naive Schleife (langsam)
def rsi_loop(prices, period=14):
    rsi = np.zeros(len(prices))
    for i in range(period, len(prices)):
        window = prices[i-period:i+1]
        gains = [d for d in window.diff().dropna() if d > 0]
        losses = [-d for d in window.diff().dropna() if d < 0]
        avg_gain = sum(gains) / period
        avg_loss = sum(losses) / period
        rs = avg_gain / avg_loss if avg_loss else 0
        rsi[i] = 100 - (100 / (1 + rs))
    return rsi

# Variante 2: vektorisiert (schnell)
def rsi_vec(prices, period=14):
    delta = prices.diff()
    gain = delta.clip(lower=0).rolling(period).mean()
    loss = -delta.clip(upper=0).rolling(period).mean()
    rs = gain / loss
    return 100 - (100 / (1 + rs))

Auf 100.000 Bars liegt die Schleifen-Variante bei rund 12 Sekunden, die vektorisierte Variante bei unter 20 Millisekunden. Das ist Faktor 600 — ohne externe Bibliotheken, nur durch Stilwechsel.

Speicher-Optimierung mit Datentypen.

Standardmäßig nutzt Pandas float64 und int64. Für Trading-Preise reicht meist float32; für Symbole, die sich wiederholen, ist category der richtige Typ. Das senkt nicht nur den Speicherbedarf, sondern beschleunigt auch Group-By-Operationen erheblich.

import pandas as pd

df = pd.read_parquet('ticks_2025.parquet')
print(df.memory_usage(deep=True).sum() / 1e6, 'MB')

# Symbol als category
df['symbol'] = df['symbol'].astype('category')
# Preise als float32 (Tick-Genauigkeit reicht)
for col in ['bid', 'ask', 'last']:
    df[col] = df[col].astype('float32')
# Volume als int32
df['volume'] = df['volume'].astype('int32')

print(df.memory_usage(deep=True).sum() / 1e6, 'MB')
# Typisch: 60-80 % weniger Speicher

Bei Tick-Daten über mehrere Jahre kann das den Unterschied machen, ob der DataFrame in den Arbeitsspeicher passt oder nicht. Wer ständig swappt, verliert mehr Zeit als durch jede Vektorisierung.

Rolling-Window-Operationen.

Rolling-Operationen sind das Brot-und-Butter-Werkzeug in Trading-Analysen. Pandas bietet hier hochoptimierte C-Implementierungen für Standard-Aggregate (Mean, Std, Sum, Min, Max). Custom-Aggregate über rolling.apply sind dagegen langsam, weil sie für jedes Fenster eine Python-Funktion aufrufen.

Wer einen eigenen Rolling-Indikator braucht, hat drei Optionen: erstens raw=True übergeben, damit NumPy-Arrays statt Series gereicht werden; zweitens Numba über das engine='numba'-Argument nutzen; drittens die Operation auf Standard-Aggregaten umformulieren. Beispiel: Eine rollierende z-Score-Berechnung lässt sich vollständig vektorisieren:

import pandas as pd

def rolling_zscore(series, window=20):
    mean = series.rolling(window).mean()
    std = series.rolling(window).std()
    return (series - mean) / std

# Anwendung auf Mehrfachsymbole gleichzeitig
prices = pd.read_parquet('eod_prices.parquet')  # MultiIndex (symbol, date)
zscores = (
    prices['close']
    .groupby(level='symbol')
    .transform(rolling_zscore, window=20)
)

Resampling und Bar-Aggregation.

Tick-Daten zu Bars zu aggregieren ist eine klassische Operation, die naiv sehr teuer wird. Pandas resample ist hier exzellent, solange der Index korrekt sortiert ist und als DatetimeIndex vorliegt. Wichtig: bei Multi-Symbol-Daten groupby vor resample einsetzen, nicht danach.

Für sehr große Tick-Datasets ist es oft schneller, das Resampling auf Numpy-Arrays mit Numba zu schreiben — aber in 90 % der Fälle reicht das eingebaute resample. Hier eine typische OHLCV-Aggregation auf Minuten-Bars:

import pandas as pd

ticks = pd.read_parquet('eurusd_ticks.parquet')
ticks = ticks.set_index('timestamp').sort_index()

bars = ticks.resample('1min').agg(
    open=('last', 'first'),
    high=('last', 'max'),
    low=('last', 'min'),
    close=('last', 'last'),
    volume=('volume', 'sum'),
    n_ticks=('last', 'count'),
)
bars = bars.dropna(subset=['close'])

Wo Pandas an Grenzen stößt.

Pandas ist nicht für jede Last gemacht. Bei Datasets jenseits von 10 GB Roh-Ticks wird es zäh — der Single-Thread-Charakter zeigt sich, und das Speicher-Layout ist für Out-of-Core-Workloads nicht ausgelegt. In dieser Kategorie spielen Polars, DuckDB und Dask. Polars nutzt Apache Arrow als Speicherformat und ist deutlich schneller bei vielen typischen Operationen. DuckDB ist ideal, wenn Sie SQL-affin sind und große Parquet-Datensätze ad-hoc analysieren wollen.

Wann bleiben Sie bei Pandas? Wenn Ihr Workload in den Speicher passt, wenn Sie auf das Ökosystem (statsmodels, scikit-learn, pyfolio) angewiesen sind, oder wenn Sie mit Kollegen arbeiten, die kein Polars kennen. In allen anderen Fällen lohnt der Blick über den Tellerrand.

Praxis-Checkliste.

Fazit.

Pandas ist nicht das Problem — der typische Pandas-Code ist es. Wer einmal verstanden hat, dass jede Python-Schleife über Series teuer ist und dass Datentypen Geld kosten, spart in jedem Backtest Minuten bis Stunden. Die Investition in eine Woche Performance-Tuning zahlt sich über Jahre aus, weil jeder Backtest, jeder Indikator-Lauf, jede Feature-Pipeline davon profitiert.

Ihr Backtest läuft seit Stunden und Sie wissen nicht, wo die Zeit hingeht? Erstgespräch buchen — wir profilen Ihren Code und finden die echten Bottlenecks.