Plateau-Analyse: Wie man ein robustes Optimum von Overfitting unterscheidet
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
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:
-
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.
-
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
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, 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 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
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
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:
wobei der PnL-Rückgang ist, wenn Parameter um vom Optimum abweicht.
Interpretation:
- — Parameter ist robust: eine 10%-Verschiebung verursacht weniger als 5% PnL-Rückgang
- — moderate Sensitivität
- — 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 des Optimums bleibt:
Relative Plateau-Breite:
wobei der Nenner der vollständige Suchbereich des Parameters ist.
Interpretation:
- — das Plateau umfasst mehr als 30% des Bereichs bei der 10%-Schwelle. Robuster Parameter.
- — das Plateau ist schmaler als 5% des Bereichs. Warnsignal.
Robustheits-Score
Eine kombinierte Metrik über alle Parameter:
wobei die normalisierte Wichtigkeit von Parameter aus fANOVA ist ().
Das Produkt der gewichteten Breiten ist eine strenge Metrik: Wenn auch nur ein wichtiger Parameter ein schmales Plateau hat, wird niedrig sein. Unwichtige Parameter (mit kleinem ) haben kaum Einfluss.
Interpretation:
- — die Strategie ist robust
- — zusätzliche Validierung erforderlich (Walk-Forward)
- — Overfitting ist sehr wahrscheinlich
Python-Code für automatisierte Plateau-Erkennung
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
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: — robust.
Plateau-Breite bei 10%-Schwelle: von 0,013 bis 0,027, .
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: — 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, .
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: — fragil. 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, .
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-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
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 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
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 () und Walk-Forward () 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
-
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.
-
Setzen Sie sinnvolle Bereiche. Setzen Sie
htf_entry_sellnicht 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. -
Verwenden Sie ausreichend Trials. Für 12 Parameter mindestens 300-500 Trials. Für eine zuverlässige Plateau-Analyse — 1000+.
Während der Optimierung
-
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.
-
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
-
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. -
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.
-
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.
-
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:
-
Berechnen Sie den Robustheits-Score. Das Produkt der gewichteten Plateau-Breiten liefert eine einzige Zahl, die die Robustheit aller Parameter zusammenfasst. — grünes Licht.
-
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.
-
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
- Optuna Documentation — Visualization
- Hutter, F., Hoos, H., Leyton-Brown, K. — An Efficient Approach for Assessing Hyperparameter Importance (fANOVA, 2014)
- Pardo, R. — The Evaluation and Optimization of Trading Strategies
- Marcos Lopez de Prado — Advances in Financial Machine Learning, Chapter 11: Dangers of Backtesting
- Bailey, D.H. et al. — The Probability of Backtest Overfitting (2015)
- Optuna — optuna.visualization.plot_contour
- Optuna — optuna.importance.FanovaImportanceEvaluator
- 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.}
}
Authors
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.