← Zurück zu den Artikeln
March 12, 2026
5 min read

Plateau-Analyse: Wie man ein robustes Optimum von Overfitting unterscheidet

Plateau-Analyse: Wie man ein robustes Optimum von Overfitting unterscheidet
#algotrading
#backtest
#optimization
#overfitting
#plateau analysis
#parameter stability
🎯
Part 4 of 9 · Collection
Backtesting Without Fooling Yourself

Artikel 6 der Serie „Backtests ohne Illusionen"

📄 Aus diesem Artikel ist ein Forschungspapier geworden. Die Metriken sind formalisiert und mit reproduzierbaren Simulationen stresstestet, benchmarkt gegen die Probability of Backtest Overfitting und den Deflated Sharpe Ratio. Lesen Sie das Paper online (interaktive Version + PDF) unter plateau.marketmaker.cc, Code und Daten unter github.com/suenot/plateau-robustness.

Sie haben study.optimize() ausgeführt, Optuna hat einen Parametersatz mit PnL +87% gefunden. Sie sind begeistert und bereiten die Strategie für die Produktion vor. Zwei Wochen Live-Trading später liegt der PnL nahe null. Was ist passiert?

Der Optimierer hat die Spitze einer Nadel im Parameterraum gefunden. Die Parameter sind perfekt an die historische Abfolge von Trades angepasst — aber die geringste Abweichung der Marktbedingungen zerstört das gesamte Konstrukt. Das ist klassisches Overfitting, und es hätte vor dem Start erkannt werden können.

Im vorherigen Artikel haben wir Coordinate Descent mit Bayesianischer Optimierung verglichen und gezeigt, warum Optuna das Optimum effizienter findet. Heute — der nächste Schritt: wie man sicherstellt, dass das gefundene Optimum robust ist, statt das Ergebnis einer Anpassung an Rauschen zu sein.

Warum das Finden der „besten" Parameter nur die halbe Arbeit ist

Suche im mehrdimensionalen Parameterraum Ein Optimierer navigiert durch eine riesige mehrdimensionale Parameterlandschaft auf der Suche nach dem wahren Optimum

Die Optimierung von Strategieparametern ist die Suche nach einem Maximum in einem mehrdimensionalen Raum. Das Problem ist, dass Maxima in zwei Arten vorkommen:

  1. Plateau — ein breiter, flacher Bereich, in dem der PnL bei Parametervariationen durchgehend hoch bleibt. Selbst wenn Marktbedingungen die effektiven Parameter um 10-20% verschieben, bleibt die Strategie profitabel.

  2. Scharfe Spitze — ein schmaler Gipfel, an dem der PnL nur beim exakten Parameterwert hoch ist. Eine Verschiebung um einen Schritt lässt die Profitabilität einbrechen. Das ist fast sicher Overfitting: Der Optimierer hat ein Artefakt der historischen Daten gefunden, kein stabiles Muster.

Eine Bergsteiger-Metapher: Ein Plateau ist ein Hochplateau, auf dem man sicher gehen kann. Eine scharfe Spitze ist die Spitze einer Nadel, auf der man nur balancieren kann.

Scharfe Spitze vs. flaches Plateau — visuelle Intuition

Vergleich scharfe Spitze versus flaches Plateau Links: ein robustes Plateau (breites Tafelgebirge mit sanften Hängen). Rechts: eine fragile scharfe Spitze (Nadelspitze umgeben von tiefen Tälern)

Stellen Sie sich eine Konturkarte vor, bei der die Achsen zwei Strategieparameter darstellen und die Farbe den PnL repräsentiert. Zwei Muster lassen sich visuell leicht unterscheiden:

Plateau (robustes Optimum):

  • Breite Bereiche derselben Farbe
  • Sanfte Übergänge zwischen PnL-Niveaus
  • Weit auseinanderliegende Isolinien
  • Eine Verschiebung vom Optimum um +/-20% ändert den PnL um nicht mehr als 10%

Stellen Sie sich eine Heatmap vor: in der Mitte — ein leuchtend gelbes Rechteck, etwa ein Drittel der Größe der gesamten Karte. Die Farbe geht allmählich in Orange, dann in Rot über, zu den Rändern hin. Das Optimum ist kein Punkt, sondern ein Bereich.

Scharfe Spitze (Overfitting):

  • Ein schmaler heller Fleck, umgeben von kalten Farben
  • Abrupte Übergänge: ein Einbruch direkt neben dem Optimum
  • Isolinien, die zu engen Ringen zusammengedrängt sind
  • Eine Verschiebung um +/-5% lässt den PnL um 50% oder mehr fallen

Stellen Sie sich dieselbe Heatmap vor, aber in der Mitte — ein winziger gelber Punkt, unmittelbar umgeben von Blau und Violett. Eine einzige „richtige" Parameterkombination.

Parameter-Sensitivitätsanalyse

Slice-Plots der Parameter-Sensitivität Slice-Plots, die zeigen, wie der PnL von einzelnen Parameterwerten abhängt — breite Bänder deuten auf Robustheit hin, schmale Cluster auf Fragilität

Eindimensionale Analyse: PnL vs. ein Parameter

Der einfachste Ansatz — alle Parameter außer einem fixieren und sehen, wie der PnL von dessen Wert abhängt. Optuna bietet dafür plot_slice:

import optuna
from optuna.visualization import plot_slice

study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=500)

fig = plot_slice(study, params=["htf_entry_sell", "ltf_momentum", "stop_loss_pct"])
fig.show()

Worauf man bei einem Slice-Plot achten sollte:

  • Robuster Parameter: die Punktwolke bildet ein breites horizontales Band nahe dem Optimum. Die besten Trials sind über einen breiten Bereich von Parameterwerten verteilt.
  • Fragiler Parameter: die besten Trials konzentrieren sich auf einen schmalen Bereich. Eine Verschiebung des Parameters um ein oder zwei Schritte — und die Profitabilität bricht ein.

Zweidimensionale Analyse: Konturdiagramme (Heatmaps)

Ein Konturdiagramm zeigt die Wechselwirkung zweier Parameter gleichzeitig. Das ist das Schlüsselwerkzeug für die Plateau-Analyse, denn Parameter wirken selten unabhängig — Entry- und Exit-Schwellen, Zeitrahmen und Positionsgrößen sind miteinander verknüpft.

from optuna.visualization import plot_contour

fig = plot_contour(study, params=["htf_entry_sell", "htf_exit_buy"])
fig.show()

Ein Konturdiagramm für ein robustes Parameterpaar sieht aus wie eine topografische Karte einer hügeligen Ebene: sanfte breite Isolinien, große Bereiche derselben Farbe. Ein Konturdiagramm für ein fragiles Paar — wie eine Karte eines Vulkankegels: enge konzentrische Ringe um einen einzigen Punkt.

Für eine Strategie mit 12 Separationsparametern ergibt das (122)=66\binom{12}{2} = 66 paarweise Konturdiagramme. Sie müssen nicht alle studieren — beginnen Sie mit den Parametern, die Optuna als am wichtigsten eingestuft hat.

Mehrdimensionale Analyse: Parameter-Wichtigkeits-Ranking

Optuna kann den Beitrag jedes Parameters zur Zielfunktion schätzen:

from optuna.visualization import plot_param_importances

fig = plot_param_importances(study)
fig.show()

Das Parameter-Wichtigkeits-Diagramm ist ein horizontales Histogramm. Parameter werden nach ihrem Beitrag zur PnL-Varianz absteigend geordnet. Die Top-3-4-Parameter erklären in der Regel 70-80% der Varianz.

Regel: Wenn ein Parameter weniger als 2% der PnL-Varianz erklärt, ist sein Wert für das Ergebnis praktisch irrelevant — er ist per Definition robust. Konzentrieren Sie die Plateau-Analyse auf die Top-5 wichtigsten Parameter.

Optuna-Visualisierungswerkzeuge

Optuna-Konturdiagramme und Visualisierung der Parameterwichtigkeit Kontur-Heatmaps, die die Landschaft der Parameterwechselwirkungen zusammen mit Wichtigkeits-Rankings zeigen

plot_slice — Eindimensionale Slices

import optuna
from optuna.visualization import plot_slice

fig = plot_slice(study, params=[
    "htf_entry_sell", "htf_entry_buy",
    "ltf_momentum_threshold", "stop_loss_pct",
    "take_profit_pct", "trailing_stop_pct"
])
fig.update_layout(height=800, title="Parameter Slice Plots")
fig.show()

Das Ergebnis — ein Raster aus Streudiagrammen. Jedes Teildiagramm zeigt den Wert der Zielfunktion (PnL, Y-Achse) gegen einen einzelnen Parameterwert (X-Achse). Punkte sind einzelne Trials. Bei einem robusten Parameter sind die besten Punkte (höchster PnL) über einen breiten X-Bereich verteilt. Bei einem fragilen — in einer schmalen Spalte gruppiert.

plot_contour — Zweidimensionale Konturen

from optuna.visualization import plot_contour

important_pairs = [
    ["htf_entry_sell", "htf_entry_buy"],
    ["htf_entry_sell", "stop_loss_pct"],
    ["ltf_momentum_threshold", "take_profit_pct"],
]

for params in important_pairs:
    fig = plot_contour(study, params=params)
    fig.update_layout(title=f"Contour: {params[0]} vs {params[1]}")
    fig.show()

Jedes Konturdiagramm ist eine Heatmap mit zwei Parametern auf den Achsen. Die Farbe kodiert den durchschnittlichen PnL in einem gegebenen Bereich des Parameterraums. Gelb/Grün — hoher PnL, Blau/Violett — niedriger. Isolinien verbinden Punkte mit demselben PnL.

plot_param_importances — Parameterbeiträge

from optuna.visualization import plot_param_importances

fig = plot_param_importances(
    study,
    evaluator=optuna.importance.FanovaImportanceEvaluator()
)
fig.show()

fANOVA (functional ANOVA) zerlegt die Varianz der Zielfunktion über Parameter und deren Wechselwirkungen. Das ist leistungsfähiger als eine einfache Korrelation, weil es nichtlineare Effekte berücksichtigt.

Quantitative Plateau-Metriken

Visualisierung quantitativer Robustheitsmetriken Sensitivity Ratio, Plateau-Breite und Robustheits-Score — drei Metriken, die die Plateau-Qualität formalisieren

Visuelle Beurteilung ist subjektiv. Wir brauchen Zahlen. Hier sind drei Metriken, die das Konzept eines „Plateaus" formalisieren.

Sensitivity Ratio

Das Verhältnis der PnL-Änderung zur Parameteränderung:

Si=ΔPnL/PnLoptΔpi/pi,optS_i = \frac{\Delta \text{PnL} / \text{PnL}_{opt}}{\Delta p_i / p_{i,opt}}

wobei ΔPnL\Delta \text{PnL} der PnL-Rückgang ist, wenn Parameter pip_i um Δpi\Delta p_i vom Optimum abweicht.

Interpretation:

  • Si<0.5S_i < 0.5 — Parameter ist robust: eine 10%-Verschiebung verursacht weniger als 5% PnL-Rückgang
  • 0.5Si<2.00.5 \leq S_i < 2.0 — moderate Sensitivität
  • Si2.0S_i \geq 2.0 — Parameter ist fragil: eine 10%-Verschiebung lässt den PnL um 20%+ einbrechen

Plateau-Breite

Die Breite des Parameterbereichs, innerhalb dessen der PnL innerhalb von X%X\% des Optimums bleibt:

Wi(X)=pi,maxpi,minsubject toPnL(pi)(1X/100)×PnLoptW_i(X) = p_{i,max} - p_{i,min} \quad \text{subject to} \quad \text{PnL}(p_i) \geq (1 - X/100) \times \text{PnL}_{opt}

Relative Plateau-Breite:

Wirel(X)=Wi(X)pi,maxrangepi,minrangeW_i^{rel}(X) = \frac{W_i(X)}{p_{i,max}^{range} - p_{i,min}^{range}}

wobei der Nenner der vollständige Suchbereich des Parameters ist.

Interpretation:

  • Wirel(10%)>0.3W_i^{rel}(10\%) > 0.3 — das Plateau umfasst mehr als 30% des Bereichs bei der 10%-Schwelle. Robuster Parameter.
  • Wirel(10%)<0.05W_i^{rel}(10\%) < 0.05 — das Plateau ist schmaler als 5% des Bereichs. Warnsignal.

Robustheits-Score

Eine kombinierte Metrik über alle Parameter:

R=i=1k(Wirel(10%))wiR = \prod_{i=1}^{k} \left( W_i^{rel}(10\%) \right)^{w_i}

wobei wiw_i die normalisierte Wichtigkeit von Parameter ii aus fANOVA ist (wi=1\sum w_i = 1).

Das Produkt der gewichteten Breiten ist eine strenge Metrik: Wenn auch nur ein wichtiger Parameter ein schmales Plateau hat, wird RR niedrig sein. Unwichtige Parameter (mit kleinem wiw_i) haben kaum Einfluss.

Interpretation:

  • R>0.1R > 0.1 — die Strategie ist robust
  • 0.01<R0.10.01 < R \leq 0.1 — zusätzliche Validierung erforderlich (Walk-Forward)
  • R0.01R \leq 0.01 — Overfitting ist sehr wahrscheinlich

Python-Code für automatisierte Plateau-Erkennung

Automatisierte Plateau-Erkennungs-Pipeline Automatisiertes System, das die Parameterlandschaft scannt, um robuste Plateaus und fragile Spitzen zu identifizieren

import numpy as np
import optuna
from optuna.importance import FanovaImportanceEvaluator
from typing import Dict, List, Tuple

def compute_sensitivity_ratio(
    study: optuna.Study,
    param_name: str,
    n_steps: int = 20,
) -> float:
    """
    Compute sensitivity ratio for a single parameter.

    Fixes all parameters at their best values, varies param_name,
    estimates PnL drop through trial interpolation.
    """
    best_trial = study.best_trial
    best_value = best_trial.values[0]
    best_param = best_trial.params[param_name]

    all_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
    all_trials.sort(key=lambda t: t.values[0], reverse=True)
    top_trials = all_trials[:max(10, len(all_trials) // 5)]

    param_values = np.array([t.params[param_name] for t in top_trials])
    pnl_values = np.array([t.values[0] for t in top_trials])

    if best_param == 0 or best_value == 0:
        return float('inf')

    from numpy.polynomial import polynomial as P
    coeffs = np.polyfit(param_values, pnl_values, deg=2)
    dpnl_dparam = 2 * coeffs[0] * best_param + coeffs[1]

    sensitivity = abs(dpnl_dparam * best_param / best_value)
    return sensitivity


def compute_plateau_width(
    study: optuna.Study,
    param_name: str,
    threshold_pct: float = 10.0,
) -> Tuple[float, float]:
    """
    Compute absolute and relative plateau width.

    Returns:
        (absolute_width, relative_width)
    """
    best_value = study.best_value
    threshold = best_value * (1 - threshold_pct / 100)

    trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
    good_trials = [t for t in trials if t.values[0] >= threshold]

    if not good_trials:
        return 0.0, 0.0

    good_params = [t.params[param_name] for t in good_trials]
    all_params = [t.params[param_name] for t in trials]

    plateau_min = min(good_params)
    plateau_max = max(good_params)
    absolute_width = plateau_max - plateau_min

    search_range = max(all_params) - min(all_params)
    relative_width = absolute_width / search_range if search_range > 0 else 0

    return absolute_width, relative_width


def compute_robustness_score(
    study: optuna.Study,
    threshold_pct: float = 10.0,
) -> Dict:
    """
    Compute combined robustness score.

    Returns:
        dict with per-parameter metrics and the final score
    """
    evaluator = FanovaImportanceEvaluator()
    importances = optuna.importance.get_param_importances(
        study, evaluator=evaluator
    )

    results = {}
    total_importance = sum(importances.values())

    for param_name, importance in importances.items():
        sensitivity = compute_sensitivity_ratio(study, param_name)
        abs_width, rel_width = compute_plateau_width(
            study, param_name, threshold_pct
        )

        weight = importance / total_importance
        results[param_name] = {
            "importance": importance,
            "weight": weight,
            "sensitivity_ratio": sensitivity,
            "plateau_width_abs": abs_width,
            "plateau_width_rel": rel_width,
        }

    log_score = sum(
        r["weight"] * np.log(max(r["plateau_width_rel"], 1e-10))
        for r in results.values()
    )
    robustness_score = np.exp(log_score)

    return {
        "robustness_score": robustness_score,
        "parameters": results,
        "verdict": (
            "robust" if robustness_score > 0.1
            else "check" if robustness_score > 0.01
            else "overfitting"
        ),
    }

Verwendung

report = compute_robustness_score(study, threshold_pct=10.0)

print(f"Robustness score: {report['robustness_score']:.4f}")
print(f"Verdict: {report['verdict']}")
print()

for name, metrics in report["parameters"].items():
    print(f"  {name}:")
    print(f"    Importance:       {metrics['importance']:.3f}")
    print(f"    Sensitivity:      {metrics['sensitivity_ratio']:.2f}")
    print(f"    Plateau width:    {metrics['plateau_width_rel']:.1%}")
    print()

Beispielausgabe:

Robustness score: 0.1482
Verdict: robust

  htf_entry_sell:
    Importance:       0.312
    Sensitivity:      0.38
    Plateau width:    42.5%

  htf_entry_buy:
    Importance:       0.251
    Sensitivity:      0.45
    Plateau width:    38.1%

  ltf_momentum_threshold:
    Importance:       0.187
    Sensitivity:      1.21
    Plateau width:    22.3%

  stop_loss_pct:
    Importance:       0.098
    Sensitivity:      0.67
    Plateau width:    31.0%

  take_profit_pct:
    Importance:       0.072
    Sensitivity:      0.89
    Plateau width:    28.4%

  trailing_delta:
    Importance:       0.031
    Sensitivity:      0.22
    Plateau width:    55.2%

Praktische Beispiele mit Separationsstrategien

Drei Strategien im Vergleich nach Robustheitsprofil Vergleich von Strategie A (breites Plateau, robust), Strategie B (moderat) und Strategie C (scharfe Spitze, overfitted)

Betrachten wir drei Strategien mit 12 Separationsparametern. Jede Strategie durchlief eine Optuna-Optimierung mit 500 Trials.

Strategie A (~55% PnL, ~500 Trades, ~15% Zeit)

Die Parameter von Strategie A bilden ein breites Plateau. Nehmen wir den Schlüsselparameter htf_entry_sell:

  • Optimaler Wert: 0,020
  • PnL bei 0,015: +51% (7% Rückgang)
  • PnL bei 0,025: +49% (11% Rückgang)
  • PnL bei 0,010: +43% (22% Rückgang)
  • PnL bei 0,030: +41% (25% Rückgang)

Stellt man sich das als eindimensionalen Plot vor (X-Achse — Wert von htf_entry_sell, Y-Achse — PnL), sieht man eine sanfte Parabel mit flacher Spitze. Der Bereich 0,010-0,030 ist das Plateau, in dem der PnL innerhalb von +/-25% des Optimums bleibt.

Sensitivity Ratio: S=0.110.25=0.44S = \frac{0.11}{0.25} = 0.44 — robust.

Plateau-Breite bei 10%-Schwelle: von 0,013 bis 0,027, Wrel=0.0140.04=35%W^{rel} = \frac{0.014}{0.04} = 35\%.

Strategie B (~25% PnL, ~40 Trades, ~5% Zeit)

Strategie B ist auf einer geringen Anzahl von Trades optimiert. Parameter htf_entry_sell:

  • Optimaler Wert: 0,018
  • PnL bei 0,015: +24% (4% Rückgang)
  • PnL bei 0,025: +9% (64% Rückgang)
  • PnL bei 0,012: +11% (56% Rückgang)

Im Diagramm — eine asymmetrische und steile Kurve. Das Plateau existiert nur im schmalen Bereich 0,015-0,020. Rechts vom Optimum — eine Klippe.

Sensitivity Ratio: S=0.640.39=1.64S = \frac{0.64}{0.39} = 1.64 — moderate Sensitivität, aber bei 40 Trades ist das ein Warnsignal. Kleine Stichprobe + schmales Plateau = hohe Wahrscheinlichkeit für Overfitting.

Plateau-Breite bei 10%-Schwelle: von 0,016 bis 0,020, Wrel=0.0040.04=10%W^{rel} = \frac{0.004}{0.04} = 10\%.

Strategie C (~300% PnL, ~400 Trades, ~45% Zeit)

Strategie C zeigt einen verblüffenden PnL, aber die Plateau-Analyse offenbart Probleme:

  • Optimaler Wert von htf_entry_sell: 0,022
  • PnL bei 0,020: +295% (2% Rückgang)
  • PnL bei 0,025: +142% (53% Rückgang)
  • PnL bei 0,019: +128% (57% Rückgang)

Im Diagramm — eine charakteristische „Nadel": ein sehr hoher Gipfel bei 0,022, scharfer Abfall in alle Richtungen. Das Konturdiagramm würde einen hellen Fleck zeigen, unmittelbar umgeben von kalten Farben.

Sensitivity Ratio: S=0.530.14=3.79S = \frac{0.53}{0.14} = 3.79fragil. Trotz 400 Trades ist die Strategie übermäßig vom exakten Wert eines einzelnen Parameters abhängig.

Plateau-Breite bei 10%-Schwelle: von 0,021 bis 0,023, Wrel=0.0020.04=5%W^{rel} = \frac{0.002}{0.04} = 5\%.

Zusammenfassende Tabelle

Strategie PnL Trades Sensitivity Plateau-Breite Robustheits-Score Urteil
Strategie A +55% ~500 0.44 35% 0.148 Robust
Strategie B +25% ~40 1.64 10% 0.032 Prüfen (kleine Stichprobe)
Strategie C +300% ~400 3.79 5% 0.008 Overfitting

Paradox: Strategie C mit PnL +300% hat den schlechtesten Robustheits-Score. Strategie A mit einem „bescheidenen" +55% ist am robustesten. Das ist ein typisches Ergebnis der Plateau-Analyse: beeindruckende Zahlen verschleiern oft Fragilität.

Konfidenzintervalle für jede Strategie können zusätzlich mittels Monte-Carlo-Bootstrap überprüft werden — er zeigt die PnL-Streuung beim Resampling von Trades.

3D-Visualisierung und Heatmaps

3D-Parameterlandschaftsoberfläche mit Konturprojektion 3D-Oberflächenplot des PnL über zwei Parameter mit auf die Bodenfläche projizierten Konturlinien

Für die wichtigsten Parameterpaare ist es sinnvoll, eine 3D-Oberfläche und Heatmap zu erstellen. Das vermittelt ein intuitives Verständnis der Landschaftsform.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

def plot_parameter_landscape(
    study: "optuna.Study",
    param_x: str,
    param_y: str,
    grid_size: int = 50,
):
    """
    Build a 3D surface plot and heatmap for a pair of parameters.
    """
    trials = [t for t in study.trials
              if t.state == optuna.trial.TrialState.COMPLETE]

    x_vals = np.array([t.params[param_x] for t in trials])
    y_vals = np.array([t.params[param_y] for t in trials])
    z_vals = np.array([t.values[0] for t in trials])

    from scipy.interpolate import griddata

    xi = np.linspace(x_vals.min(), x_vals.max(), grid_size)
    yi = np.linspace(y_vals.min(), y_vals.max(), grid_size)
    Xi, Yi = np.meshgrid(xi, yi)
    Zi = griddata((x_vals, y_vals), z_vals, (Xi, Yi), method='cubic')

    fig = plt.figure(figsize=(18, 7))

    ax1 = fig.add_subplot(121, projection='3d')
    surf = ax1.plot_surface(Xi, Yi, Zi, cmap=cm.viridis, alpha=0.85,
                            edgecolor='none')
    ax1.set_xlabel(param_x)
    ax1.set_ylabel(param_y)
    ax1.set_zlabel('PnL, %')
    ax1.set_title('3D Parameter Landscape')
    fig.colorbar(surf, ax=ax1, shrink=0.5)

    ax2 = fig.add_subplot(122)
    hm = ax2.pcolormesh(Xi, Yi, Zi, cmap=cm.viridis, shading='auto')
    contours = ax2.contour(Xi, Yi, Zi, levels=10, colors='white',
                           linewidths=0.8, alpha=0.7)
    ax2.clabel(contours, inline=True, fontsize=8, fmt='%.0f%%')

    best = study.best_trial
    ax2.scatter(best.params[param_x], best.params[param_y],
                color='red', s=100, marker='*', zorder=5, label='Optimum')

    ax2.set_xlabel(param_x)
    ax2.set_ylabel(param_y)
    ax2.set_title('Contour Heatmap')
    ax2.legend()
    fig.colorbar(hm, ax=ax2)

    plt.tight_layout()
    plt.savefig(f'landscape_{param_x}_vs_{param_y}.png', dpi=150)
    plt.show()

Ein 3D-Oberflächenplot für eine robuste Strategie ähnelt einem Tafelberg — flache Kuppe mit sanften Hängen. Bei einer fragilen Strategie — eine scharfe Spitze, wie das Matterhorn. Die Heatmap ergänzt die 3D-Ansicht, indem sie dieselbe Information in einer Draufsicht-Projektion mit Isolinien zeigt.

Warnsignale: Wenn Optimierungsergebnisse verdächtig sind

Warnsignal-Dashboard für Optimierungsergebnisse Warnindikatoren, die auf mögliches Overfitting in Optimierungsergebnissen hinweisen

Acht Anzeichen dafür, dass die Optimierung Overfitting statt eines echten Musters gefunden hat:

1. Sensitivity Ratio > 2 für einen Schlüsselparameter

Wenn der PnL bei einer 10%-Parameterverschiebung um mehr als 20% fällt — ist das Optimum fragil.

2. Plateau-Breite < 10% des Suchbereichs

Wenn der „gute" Bereich weniger als 10% des untersuchten Bereichs einnimmt — hat der Optimierer höchstwahrscheinlich ein Artefakt gefunden.

3. Top-3-Trials liefern PnL 2-3x über dem Median

Wenn die besten Trials Ausreißer gegenüber dem Rest sind statt der „Bergspitze" — ist es kein Plateau.

top_3_mean = np.mean(sorted([t.values[0] for t in study.trials
                              if t.state == optuna.trial.TrialState.COMPLETE],
                             reverse=True)[:3])
median_pnl = np.median([t.values[0] for t in study.trials
                         if t.state == optuna.trial.TrialState.COMPLETE])

outlier_ratio = top_3_mean / median_pnl
if outlier_ratio > 2.5:
    print(f"WARNING: Top trials are {outlier_ratio:.1f}x above median — possible overfitting")

4. Geringe Trade-Anzahl (< 50) bei hohem PnL

Kleine Stichprobe + hoher PnL = hohe Varianz in der Schätzung. Plateau-Analyse bei 40 Trades ist an sich schon unzuverlässig. Für solche Strategien ist Monte-Carlo-Bootstrap entscheidend.

5. Eine „magische" Parameterkombination

Wenn das Konturdiagramm einen einzigen hellen Punkt inmitten eines grauen Feldes zeigt — ist das keine Strategie, sondern eine an die Daten angepasste Kombination.

6. Zu viele Parameter

Bei 12 Parametern mit jeweils 10 Werten enthält der Suchraum 101210^{12} Kombinationen. Optuna untersucht ~500. Die Wahrscheinlichkeit, in einem solchen Raum ein „gutes" Artefakt zu finden, ist hoch. Je mehr Parameter, desto strenger sollte die Plateau-Analyse sein.

7. PnL fällt out-of-sample stark ab

Wenn der In-Sample-PnL +87% beträgt und Walk-Forward +12% zeigt — hat die Optimierung die Parameter an die Trainingsperiode angepasst. Mehr dazu im Artikel zur Walk-Forward-Optimierung.

8. Parameter sind an den Bereichsgrenzen „festgenagelt"

Wenn der optimale Wert mit der Grenze des Suchrasters zusammenfällt — könnte das Optimum außerhalb des Bereichs liegen. Erweitern Sie den Bereich und führen Sie die Optimierung erneut aus.

Automatisierter Plateau-Analyse-Bericht

Alles zusammen in einem einzigen Bericht, der nach jeder Optimierung generiert wird:

import json
from datetime import datetime

def generate_plateau_report(
    study: "optuna.Study",
    strategy_name: str,
    n_trades: int,
    threshold_pct: float = 10.0,
) -> dict:
    """
    Generate a complete plateau analysis report.
    """
    robustness = compute_robustness_score(study, threshold_pct)

    red_flags = []

    sorted_params = sorted(
        robustness["parameters"].items(),
        key=lambda x: x[1]["importance"],
        reverse=True
    )
    for name, metrics in sorted_params[:3]:
        if metrics["sensitivity_ratio"] > 2.0:
            red_flags.append(
                f"High sensitivity for {name}: "
                f"S={metrics['sensitivity_ratio']:.2f}"
            )

    for name, metrics in robustness["parameters"].items():
        if metrics["plateau_width_rel"] < 0.05:
            red_flags.append(
                f"Narrow plateau for {name}: "
                f"W={metrics['plateau_width_rel']:.1%}"
            )

    all_values = sorted(
        [t.values[0] for t in study.trials
         if t.state == optuna.trial.TrialState.COMPLETE],
        reverse=True
    )
    if len(all_values) > 10:
        top3 = np.mean(all_values[:3])
        med = np.median(all_values)
        if med > 0 and top3 / med > 2.5:
            red_flags.append(
                f"Top trials are outliers: "
                f"{top3:.1f} vs median {med:.1f} "
                f"({top3/med:.1f}x)"
            )

    if n_trades < 50:
        red_flags.append(f"Low trade count: {n_trades}")

    report = {
        "strategy": strategy_name,
        "timestamp": datetime.now().isoformat(),
        "best_pnl": study.best_value,
        "n_trials": len(study.trials),
        "n_trades": n_trades,
        "robustness_score": robustness["robustness_score"],
        "verdict": robustness["verdict"],
        "red_flags": red_flags,
        "parameters": robustness["parameters"],
    }

    return report


report = generate_plateau_report(
    study, strategy_name="Strategy A", n_trades=491
)

print(json.dumps(report, indent=2, default=str))

Beispielausgabe:

{
  "strategy": "Strategy A",
  "best_pnl": 55.2,
  "n_trials": 500,
  "n_trades": 491,
  "robustness_score": 0.1482,
  "verdict": "robust",
  "red_flags": [],
  "parameters": {
    "htf_entry_sell": {
      "importance": 0.312,
      "sensitivity_ratio": 0.44,
      "plateau_width_rel": 0.35
    }
  }
}

Zusammenhang mit Walk-Forward-Validierung

Walk-Forward-Validierung als Ergänzung zur Plateau-Analyse Parametrische Robustheit (Plateau-Analyse) und zeitliche Robustheit (Walk-Forward) als zwei sich ergänzende Validierungssysteme

Plateau-Analyse und Walk-Forward-Validierung (WFO) sind komplementäre Methoden:

  • Plateau-Analyse beantwortet die Frage: „Wie stabil ist das Optimum gegenüber kleinen Parameterverschiebungen?" Das ist eine Prüfung der parametrischen Robustheit.
  • Walk-Forward beantwortet die Frage: „Funktionieren die Parameter auf Daten, die der Optimierer nicht gesehen hat?" Das ist eine Prüfung der zeitlichen Robustheit.

Eine Strategie kann die Plateau-Analyse bestehen (breites Plateau), aber bei Walk-Forward scheitern (Marktregime hat sich geändert). Und umgekehrt — sie kann Walk-Forward mit fixen Parametern bestehen, aber ein fragiles Optimum haben.

Empfehlung: Verwenden Sie immer beide Methoden. Wenn eine Strategie die Plateau-Analyse (R>0.1R > 0.1) und Walk-Forward (PnLOOS>50%×PnLIS\text{PnL}_{OOS} > 50\% \times \text{PnL}_{IS}) besteht — ist das ein starkes Signal für Robustheit. Mehr Details im Artikel zur Walk-Forward-Optimierung.

Um PnL-Konfidenzintervalle in jeder Phase zu beurteilen, wenden Sie Monte-Carlo-Bootstrap an. Und um Strategien mit unterschiedlicher aktiver Zeit korrekt zu vergleichen, verwenden Sie die Metrik PnL pro aktiver Zeit.

Empfehlungen

Vor der Optimierung

  1. Begrenzen Sie die Anzahl der Parameter. Je weniger Parameter — desto zuverlässiger das Plateau. 5-7 Parameter sind ein vernünftiges Maximum. Bei 12 ist bereits erhöhte Vorsicht geboten.

  2. Setzen Sie sinnvolle Bereiche. Setzen Sie htf_entry_sell nicht von 0,001 bis 1,0, wenn der realistische Bereich 0,005 bis 0,05 beträgt. Unnötig breite Bereiche erzeugen die Illusion eines Plateaus.

  3. Verwenden Sie ausreichend Trials. Für 12 Parameter mindestens 300-500 Trials. Für eine zuverlässige Plateau-Analyse — 1000+.

Während der Optimierung

  1. Achten Sie auf Konvergenz. Wenn Optuna nach 400 Trials weiterhin deutlich bessere Lösungen findet — ist der Prozess nicht konvergiert, und die Plateau-Analyse wird unzuverlässig sein.

  2. Verwenden Sie Pruning mit Vorsicht. Aggressives Pruning (MedianPruner) kann Trials abschneiden, die in frühen Schritten schlecht aussehen, aber für ein vollständiges Bild der Landschaft wichtig sind.

Nach der Optimierung

  1. Generieren Sie den Plateau-Bericht automatisch. Integrieren Sie generate_plateau_report() in die Optimierungs-Pipeline. Verlassen Sie sich nicht auf visuelle Beurteilung — verwenden Sie Zahlen.

  2. Prüfen Sie die Top-5-Parameter. Wenn fANOVA zeigt, dass 3 Parameter 80% der Varianz erklären — können die restlichen 9 weniger gründlich geprüft werden.

  3. Vergleichen Sie mit der Baseline-Strategie. Wenn die Strategie mit Standardparametern (ohne Optimierung) +30% zeigt und die optimierte +55% — beträgt der Unterschied nur 25 Prozentpunkte, und das Plateau ist wahrscheinlich breit. Wenn der Standard 0% zeigt und der optimierte +300% — hängt die gesamte Profitabilität von der präzisen Parameteranpassung ab.

  4. Abschließende Prüfung — Walk-Forward. Plateau-Analyse ist eine notwendige, aber keine hinreichende Bedingung für Robustheit. Validieren Sie immer out-of-sample.

Fazit

Parameteroptimierung ist ein mächtiges Werkzeug, aber ohne Plateau-Analyse ist es ein Roulette-Spiel. Sie wissen nicht, ob Sie ein stabiles Muster gefunden oder das Modell an Rauschen angepasst haben.

Drei Regeln der Plateau-Analyse:

  1. Berechnen Sie den Robustheits-Score. Das Produkt der gewichteten Plateau-Breiten liefert eine einzige Zahl, die die Robustheit aller Parameter zusammenfasst. R>0.1R > 0.1 — grünes Licht.

  2. Sensitivity Ratio < 1 für Schlüsselparameter. Wenn eine 10%-Parameterverschiebung weniger als 10% PnL-Rückgang verursacht — ist der Parameter robust. Wenn mehr — seien Sie vorsichtig.

  3. Visualisieren Sie Konturdiagramme. Keine Metrik kann das Verständnis der Landschaftsform ersetzen. Ein flacher Tafelberg — gut. Eine scharfe Nadel — schlecht.

Plateau-Analyse dauert 5 Minuten nach der Optimierung und kann Wochen unprofitablen Live-Tradings ersparen. Es ist ein obligatorischer Schritt zwischen study.optimize() und dem Start des Bots.


Nützliche Links

  1. Optuna Documentation — Visualization
  2. Hutter, F., Hoos, H., Leyton-Brown, K. — An Efficient Approach for Assessing Hyperparameter Importance (fANOVA, 2014)
  3. Pardo, R. — The Evaluation and Optimization of Trading Strategies
  4. Marcos Lopez de Prado — Advances in Financial Machine Learning, Chapter 11: Dangers of Backtesting
  5. Bailey, D.H. et al. — The Probability of Backtest Overfitting (2015)
  6. Optuna — optuna.visualization.plot_contour
  7. Optuna — optuna.importance.FanovaImportanceEvaluator
  8. Bergstra, J. & Bengio, Y. — Random Search for Hyper-Parameter Optimization (2012)

Zitierung

@article{soloviov2026plateauanalysis,
  author = {Soloviov, Eugen},
  title = {Plateau Analysis: How to Distinguish a Robust Optimum from Overfitting},
  year = {2026},
  url = {https://marketmaker.cc/en/blog/post/plateau-analysis-overfitting},
  version = {0.1.0},
  description = {Why finding the best strategy parameters is only half the work. How to visually and quantitatively distinguish a stable plateau from a fragile peak, and why Optuna contour plots are a mandatory step before launching an optimized strategy into production.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Dem Markt einen Schritt voraus

Abonniere unseren Newsletter für exklusive KI-Trading-Einblicke, Marktanalysen und Plattform-Updates.

Wir respektieren deine Privatsphäre. Jederzeit abbestellbar.