← Alle Insights

Unit-Tests für Trading-Strategien: warum die meisten Trader nie welche schreiben.

In zehn Jahren Mandantenarbeit habe ich in vielleicht 5 % der bestehenden Trading-Codebases echte Tests gefunden. Der Grund ist nicht Faulheit — Trader sind selten ausgebildete Software-Entwickler, Tests fühlen sich nach „Overhead ohne Edge" an. Das Problem ist: in Strategien sind Bugs nicht ärgerlich, sie sind teuer. Eine falsch berechnete Positionsgröße kostet pro Trade Geld. Tests sind das einzige Mittel, das systematisch dagegen wirkt.

Warum Tests im Trading-Kontext besonders zählen.

Drei Eigenschaften, die Trading-Code von typischer Business-Software unterscheiden:

Tests sind in diesem Kontext kein Software-Engineering-Luxus, sondern Risikomanagement. Genau wie Sie keine Position ohne Stop-Loss eingehen, sollte keine produktive Strategie ohne Test-Suite laufen.

Was getestet gehört — die wichtigsten Bereiche.

Nicht jede Codezeile braucht einen Test. Aber diese vier Bereiche sind nicht verhandelbar:

pytest als Standard.

Für Python-Strategien ist pytest der Standard. Minimaler Setup-Aufwand, exzellente Fixtures, gute Integration mit Mocking und Coverage-Tools. Ein Repository-Layout, das funktioniert:

src/
  strategy/
    signals.py
    sizing.py
    execution.py
    edges.py
tests/
  unit/
    test_signals.py
    test_sizing.py
    test_execution.py
    test_edges.py
  integration/
    test_broker_sandbox.py
  fixtures/
    sample_ohlc_2023.parquet
    splits_2023.csv
  conftest.py

conftest.py enthält geteilte Fixtures — vorgenerierte Preisreihen, Mock-Konten, Holiday-Kalender. So muss kein Test sein eigenes Setup von Hand bauen.

Test-Fixtures mit historischen Daten.

Eine zentrale Idee: jeder Test bekommt deterministische, eingefrorene Marktdaten — nicht Live-Daten. Sonst hängt das Testergebnis vom Tag ab, an dem der Test läuft:

# tests/conftest.py
import pandas as pd
import pytest
from pathlib import Path

FIXTURE_DIR = Path(__file__).parent / "fixtures"

@pytest.fixture(scope="session")
def ohlc_2023():
    """Tages-OHLC für SPY, 2023. Eingefroren."""
    return pd.read_parquet(FIXTURE_DIR / "sample_ohlc_2023.parquet")

@pytest.fixture
def empty_bar_series():
    """5-Min-Bars mit Lücke zwischen 11:30 und 13:00 (Lunch-Halt)."""
    idx = pd.date_range("2023-06-15 09:30", "2023-06-15 16:00", freq="5min")
    idx = idx[(idx.time < pd.Timestamp("11:30").time())
            | (idx.time >= pd.Timestamp("13:00").time())]
    return pd.DataFrame({"close": range(len(idx))}, index=idx)

@pytest.fixture
def split_event():
    """Apple 4-for-1 Split, 31.08.2020."""
    return {"symbol": "AAPL", "date": "2020-08-31", "ratio": 4.0}

Drei Tests bekommen drei Fixtures. Wenn ein Test bricht, weiß ich exakt, welche Daten-Konstellation den Bug ausgelöst hat.

Mocking der Broker-API.

Unit-Tests dürfen niemals echte Orders schicken — auch nicht in Sandbox. Stattdessen wird die Broker-Schnittstelle gemockt. Ein klares Pattern:

# tests/unit/test_execution.py
from unittest.mock import MagicMock
from strategy.execution import submit_order

def test_submits_limit_order_with_correct_params():
    broker = MagicMock()
    broker.submit.return_value = {"id": "abc123", "status": "accepted"}

    submit_order(
        broker, symbol="AAPL", side="buy", qty=100,
        limit_price=180.50, tif="DAY",
    )

    broker.submit.assert_called_once_with(
        symbol="AAPL", side="buy", qty=100,
        order_type="limit", limit_price=180.50, tif="DAY",
    )

def test_rejects_negative_quantity():
    broker = MagicMock()
    import pytest
    with pytest.raises(ValueError, match="qty must be positive"):
        submit_order(broker, symbol="AAPL", side="buy", qty=-10,
                     limit_price=180.50, tif="DAY")
    broker.submit.assert_not_called()

Der zweite Test ist mindestens so wichtig wie der erste: Was passiert, wenn der Aufrufer Mist baut? Die Strategie sollte sich weigern, nicht stillschweigend eine Short-Order draus machen.

Beispiel-Tests für Signal-Generation und Sizing.

Zwei Tests, die in jeder produktiven Strategie sein sollten:

# tests/unit/test_signals.py
import numpy as np
import pandas as pd
from strategy.signals import sma_crossover

def test_sma_crossover_generates_buy_on_golden_cross():
    # 20 absteigende Preise, dann 20 aufsteigende
    prices = pd.Series(
        list(range(40, 20, -1)) + list(range(20, 40)),
        index=pd.date_range("2023-01-01", periods=40),
    )
    signals = sma_crossover(prices, fast=5, slow=10)
    # Erster Buy nach dem Tiefpunkt
    buys = signals[signals == 1]
    assert len(buys) >= 1
    assert buys.index[0] > prices.idxmin()

# tests/unit/test_sizing.py
import pytest
from strategy.sizing import position_size_pct_risk

def test_position_size_2pct_risk_100k_account():
    qty = position_size_pct_risk(
        equity=100_000, risk_pct=0.02,
        entry=100.0, stop=98.0, contract_value=1.0,
    )
    # 2k Risiko / 2 USD pro Aktie = 1000 Aktien
    assert qty == 1000

def test_position_size_zero_when_stop_equals_entry():
    qty = position_size_pct_risk(
        equity=100_000, risk_pct=0.02,
        entry=100.0, stop=100.0, contract_value=1.0,
    )
    assert qty == 0  # Niemals durch 0 dividieren

@pytest.mark.parametrize("equity,risk,entry,stop,expected", [
    (50_000, 0.01, 200, 195, 100),
    (250_000, 0.005, 50, 48, 625),
])
def test_position_size_parametrized(equity, risk, entry, stop, expected):
    qty = position_size_pct_risk(equity, risk, entry, stop, 1.0)
    assert qty == expected

parametrize ist der Hebel: zehn Test-Szenarien in fünf Zeilen Code. Das ist die effizienteste Form, breite Coverage zu erreichen.

Realistische Coverage-Ziele.

Wer in Software-Engineering 90 % Coverage anstrebt, kommt im Trading meist nicht so weit — und sollte das auch nicht. Realistisch:

Coverage ist Mittel, nicht Ziel. Eine Strategie mit 60 % Coverage und 30 sorgfältig ausgewählten Tests ist sicherer als eine mit 95 % Coverage und trivialen Asserts.

Warum die fehlende Test-Disziplin das eigentliche Risiko ist.

Wenn ich bei Mandanten ein Drawdown-Problem analysieren soll, ist die erste Frage selten „ist die Strategie schlecht?". Die ersten Fragen sind: Implementiert der Code, was die Forschung beabsichtigt hat? Sind die Position-Größen korrekt? Hat ein Refactoring im letzten Monat eine subtile Verhaltensänderung eingeführt? In über der Hälfte der Fälle findet sich der Fehler dort — nicht in der Marktthese.

Tests schließen genau diese Klasse von Fragen vorab. Sie verhindern nicht, dass eine Strategie scheitert. Sie verhindern, dass die Strategie aus den falschen Gründen scheitert und Sie tagelang in der falschen Richtung suchen.

Meine Praxis: mindestens 20 Tests pro produktiver Strategie.

Meine Regel — bei eigenen Strategien wie bei Mandantenarbeit: bevor eine Strategie in Live geht, hat sie mindestens 20 Unit-Tests. Davon mindestens je drei für Signal-Generation, Sizing, Order-Submission und Edge-Cases. Die Coverage interessiert mich weniger als die Antwort auf: Welche realistischen Bug-Szenarien fängt diese Suite?

20 Tests sind in einer halben Tagesleistung geschrieben — und sparen mir über die Lebenszeit der Strategie Tage an Debugging und Geld an Bugs, die nie live wurden. Das ist eine der wenigen Aktivitäten in Trading-Entwicklung, bei denen das ROI fast sofort sichtbar ist.

Ihre Strategie läuft live und Sie haben kein einziges Testset, das nachweist, dass sie tut, was sie soll? Erstgespräch buchen — wir bauen die Test-Suite, die Ihren Strategie-Code aus dem „funktioniert hoffentlich"-Zustand herausholt.