Analisi del Plateau: Come Distinguere un Ottimo Robusto dall'Overfitting
Articolo 6 della serie "Backtest Senza Illusioni"
Hai eseguito study.optimize(), Optuna ha trovato un insieme di parametri con PnL +87%. Sei entusiasta e stai preparando la strategia per la produzione. Due settimane di trading live dopo, il PnL è intorno a zero. Cosa è successo?
L'ottimizzatore ha trovato la punta di uno spillo nello spazio dei parametri. I parametri sono perfettamente adattati alla sequenza storica di trade — ma la minima deviazione nelle condizioni di mercato distrugge l'intera costruzione. Questo è il classico overfitting, e avrebbe potuto essere rilevato prima del lancio.
Nel precedente articolo abbiamo confrontato la discesa per coordinate con l'ottimizzazione bayesiana e mostrato perché Optuna trova l'ottimo in modo più efficiente. Oggi — il passo successivo: come assicurarsi che l'ottimo trovato sia robusto, piuttosto che il risultato di un adattamento al rumore.
Perché Trovare i Parametri "Migliori" È Solo Metà del Lavoro
Un ottimizzatore che naviga un vasto panorama di parametri multidimensionale alla ricerca del vero ottimo
L'ottimizzazione dei parametri di strategia è una ricerca di un massimo in uno spazio multidimensionale. Il problema è che i massimi sono di due tipi:
-
Plateau — una regione piatta e ampia dove il PnL è costantemente alto attraverso le variazioni dei parametri. Anche se le condizioni di mercato spostano i parametri effettivi del 10-20%, la strategia continuerà a generare profitto.
-
Picco acuto — una vetta stretta dove il PnL è alto solo al valore esatto del parametro. Uno spostamento di un passo fa crollare la redditività. Questo è quasi certamente overfitting: l'ottimizzatore ha trovato un artefatto dei dati storici, non un pattern stabile.
Una metafora alpinistica: un plateau è un altopiano di montagna dove si può camminare in sicurezza. Un picco acuto è la punta di uno spillo su cui si riesce solo a bilanciare.
Picco Acuto vs Plateau Piatto — Intuizione Visiva
Sinistra: un plateau robusto (ampia montagna a tavola con pendii dolci). Destra: un picco acuto fragile (punta di spillo circondata da valli profonde)
Immagina una mappa topografica dove gli assi sono due parametri della strategia e il colore rappresenta il PnL. Due pattern sono facilmente distinguibili visivamente:
Plateau (ottimo robusto):
- Ampie aree dello stesso colore
- Transizioni graduali tra i livelli di PnL
- Curve di livello distanziate
- Spostarsi dall'ottimo del +/-20% cambia il PnL di non più del 10%
Immagina una heatmap: al centro — un rettangolo giallo brillante pari a circa un terzo dell'intera mappa. Il colore transisce gradualmente verso l'arancione, poi verso il rosso ai bordi. L'ottimo non è un punto, ma una regione.
Picco acuto (overfitting):
- Un punto luminoso ristretto circondato da colori freddi
- Transizioni brusche: un collasso subito accanto all'ottimo
- Curve di livello compresse in anelli stretti
- Spostarsi del +/-5% fa scendere il PnL del 50% o più
Immagina la stessa heatmap, ma al centro — un minuscolo punto giallo immediatamente circondato da blu e viola. Una sola combinazione di parametri "corretta".
Analisi della Sensibilità dei Parametri
Grafici a fette che mostrano come il PnL dipende dai valori dei singoli parametri — bande larghe indicano robustezza, cluster stretti indicano fragilità
Analisi Monodimensionale: PnL vs un Parametro
L'approccio più semplice — fissa tutti i parametri tranne uno e osserva come il PnL dipende dal suo valore. Optuna fornisce plot_slice per questo:
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()
Cosa cercare in un grafico a fette:
- Parametro robusto: la nuvola di punti forma una banda orizzontale ampia vicino all'ottimo. I migliori trial sono distribuiti su un'ampia gamma di valori del parametro.
- Parametro fragile: i migliori trial sono concentrati in un intervallo ristretto. Spostare il parametro di uno o due passi — e la redditività crolla.
Analisi Bidimensionale: Contour Plot (Heatmap)
Un contour plot mostra l'interazione di due parametri simultaneamente. Questo è lo strumento chiave per l'analisi del plateau, poiché i parametri raramente agiscono in modo indipendente — le soglie di entrata e uscita, i timeframe e le dimensioni delle posizioni sono interconnesse.
from optuna.visualization import plot_contour
fig = plot_contour(study, params=["htf_entry_sell", "htf_exit_buy"])
fig.show()
Un contour plot per una coppia di parametri robusta assomiglia a una mappa topografica di una pianura collinare: curve di livello ampie e uniformi, grandi aree dello stesso colore. Un contour plot per una coppia fragile — come la mappa di un cono vulcanico: anelli concentrici stretti attorno a un singolo punto.
Per una strategia con 12 parametri di separazione, questo dà contour plot a coppie. Non è necessario studiarli tutti — inizia con i parametri che Optuna ha valutato come più importanti.
Analisi Multidimensionale: Classifica dell'Importanza dei Parametri
Optuna può stimare il contributo di ciascun parametro alla funzione obiettivo:
from optuna.visualization import plot_param_importances
fig = plot_param_importances(study)
fig.show()
Il grafico dell'importanza dei parametri è un istogramma orizzontale. I parametri sono classificati per il loro contributo alla varianza del PnL in ordine decrescente. I primi 3-4 parametri di solito spiegano il 70-80% della varianza.
Regola: se un parametro spiega meno del 2% della varianza del PnL, il suo valore è praticamente irrilevante per il risultato — è robusto per definizione. Concentra l'analisi del plateau sui 5 parametri più importanti.
Strumenti di Visualizzazione di Optuna
Heatmap di contorno che mostrano il panorama dell'interazione dei parametri insieme alle classifiche di importanza
plot_slice — Fette Monodimensionali
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()
Il risultato — una griglia di scatter plot. Ogni sottografico mostra il valore della funzione obiettivo (PnL, asse Y) rispetto al valore di un singolo parametro (asse X). I punti sono i singoli trial. Per un parametro robusto, i punti migliori (PnL più alto) sono distribuiti su un'ampia gamma di X. Per uno fragile — raggruppati in una colonna stretta.
plot_contour — Contorni Bidimensionali
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()
Ogni contour plot è una heatmap con due parametri sugli assi. Il colore codifica il PnL medio in una determinata regione dello spazio dei parametri. Giallo/verde — PnL alto, blu/viola — basso. Le curve di livello collegano punti con lo stesso PnL.
plot_param_importances — Contributi dei Parametri
from optuna.visualization import plot_param_importances
fig = plot_param_importances(
study,
evaluator=optuna.importance.FanovaImportanceEvaluator()
)
fig.show()
fANOVA (ANOVA funzionale) decompone la varianza della funzione obiettivo tra i parametri e le loro interazioni. Questo è più potente della semplice correlazione perché tiene conto degli effetti non lineari.
Metriche Quantitative del Plateau
Rapporto di sensibilità, larghezza del plateau e punteggio di robustezza — tre metriche che formalizzano la qualità del plateau
La valutazione visiva è soggettiva. Abbiamo bisogno di numeri. Ecco tre metriche che formalizzano il concetto di "plateau."
Rapporto di Sensibilità
Il rapporto tra la variazione del PnL e la variazione del parametro:
dove è il calo del PnL quando il parametro devia dall'ottimo di .
Interpretazione:
- — il parametro è robusto: uno spostamento del 10% causa meno del 5% di calo del PnL
- — sensibilità moderata
- — il parametro è fragile: uno spostamento del 10% fa crollare il PnL del 20%+
Larghezza del Plateau
La larghezza della regione dei parametri all'interno della quale il PnL rimane entro dell'ottimo:
Larghezza relativa del plateau:
dove il denominatore è l'intervallo di ricerca completo del parametro.
Interpretazione:
- — il plateau copre più del 30% dell'intervallo alla soglia del 10%. Parametro robusto.
- — il plateau è più stretto del 5% dell'intervallo. Segnale d'allarme.
Punteggio di Robustezza
Una metrica combinata su tutti i parametri:
dove è l'importanza normalizzata del parametro da fANOVA ().
Il prodotto delle larghezze ponderate è una metrica rigorosa: se anche un solo parametro importante ha un plateau stretto, sarà basso. I parametri non importanti (con piccoli) hanno quasi nessun effetto.
Interpretazione:
- — la strategia è robusta
- — è necessaria una validazione aggiuntiva (walk-forward)
- — l'overfitting è molto probabile
Codice Python per il Rilevamento Automatico del Plateau
Sistema automatizzato che scansiona il panorama dei parametri per identificare plateau robusti e picchi fragili
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:
"""
Calcola il rapporto di sensibilità per un singolo parametro.
Fissa tutti i parametri ai loro valori migliori, varia param_name,
stima il calo del PnL attraverso l'interpolazione dei trial.
"""
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]:
"""
Calcola la larghezza assoluta e relativa del plateau.
Restituisce:
(larghezza_assoluta, larghezza_relativa)
"""
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:
"""
Calcola il punteggio di robustezza combinato.
Restituisce:
dict con metriche per parametro e il punteggio finale
"""
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"
),
}
Utilizzo
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()
Esempio di output:
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%
Esempi Pratici con Strategie di Separazione
Confronto tra Strategia A (plateau ampio, robusta), Strategia B (moderata) e Strategia C (picco acuto, overfitting)
Esaminiamo tre strategie con 12 parametri di separazione. Ogni strategia ha subito un'ottimizzazione Optuna con 500 trial.
Strategia A (~55% PnL, ~500 trade, ~15% del tempo)
I parametri della Strategia A formano un plateau ampio. Prendi il parametro chiave htf_entry_sell:
- Valore ottimale: 0.020
- PnL a 0.015: +51% (calo del 7%)
- PnL a 0.025: +49% (calo dell'11%)
- PnL a 0.010: +43% (calo del 22%)
- PnL a 0.030: +41% (calo del 25%)
Se immagini questo come un grafico monodimensionale (asse X — valore di htf_entry_sell, asse Y — PnL), vedrai una parabola dolce con una cima piatta. L'intervallo 0.010-0.030 è il plateau, dove il PnL rimane entro +/-25% dell'ottimo.
Rapporto di sensibilità: — robusto.
Larghezza del plateau alla soglia del 10%: da 0.013 a 0.027, .
Strategia B (~25% PnL, ~40 trade, ~5% del tempo)
La Strategia B è ottimizzata su un piccolo numero di trade. Parametro htf_entry_sell:
- Valore ottimale: 0.018
- PnL a 0.015: +24% (calo del 4%)
- PnL a 0.025: +9% (calo del 64%)
- PnL a 0.012: +11% (calo del 56%)
Nel grafico — una curva asimmetrica e ripida. Il plateau esiste solo nell'intervallo ristretto 0.015-0.020. A destra dell'ottimo — un precipizio.
Rapporto di sensibilità: — sensibilità moderata, ma con 40 trade questo è un segnale d'allarme. Piccolo campione + plateau stretto = alta probabilità di overfitting.
Larghezza del plateau alla soglia del 10%: da 0.016 a 0.020, .
Strategia C (~300% PnL, ~400 trade, ~45% del tempo)
La Strategia C mostra un PnL sorprendente, ma l'analisi del plateau rivela problemi:
- Valore ottimale di
htf_entry_sell: 0.022 - PnL a 0.020: +295% (calo del 2%)
- PnL a 0.025: +142% (calo del 53%)
- PnL a 0.019: +128% (calo del 57%)
Nel grafico — un caratteristico "ago": un picco molto alto a 0.022, caduta brusca in tutte le direzioni. Il contour plot mostrerebbe un punto luminoso immediatamente circondato da colori freddi.
Rapporto di sensibilità: — fragile. Nonostante 400 trade, la strategia dipende eccessivamente dal valore esatto di un singolo parametro.
Larghezza del plateau alla soglia del 10%: da 0.021 a 0.023, .
Tabella Riassuntiva
| Strategia | PnL | Trade | Sensibilità | Larghezza plateau | Punteggio robustezza | Verdetto |
|---|---|---|---|---|---|---|
| Strategia A | +55% | ~500 | 0.44 | 35% | 0.148 | Robusta |
| Strategia B | +25% | ~40 | 1.64 | 10% | 0.032 | Verificare (campione piccolo) |
| Strategia C | +300% | ~400 | 3.79 | 5% | 0.008 | Overfitting |
Paradosso: la Strategia C con PnL +300% ha il peggior punteggio di robustezza. La Strategia A con un "modesto" +55% è la più robusta. Questo è un risultato tipico dell'analisi del plateau: i numeri impressionanti spesso nascondono fragilità.
Gli intervalli di confidenza per ciascuna strategia possono essere verificati ulteriormente tramite bootstrap Monte Carlo — mostrerà la dispersione del PnL durante il ricampionamento dei trade.
Visualizzazione 3D e Heatmap
Grafico 3D della superficie del PnL su due parametri con curve di livello proiettate sul piano inferiore
Per le coppie di parametri più importanti, è utile costruire una superficie 3D e una heatmap. Questo fornisce una comprensione intuitiva della forma del panorama.
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,
):
"""
Costruisce un grafico 3D della superficie e una heatmap per una coppia di parametri.
"""
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()
Un grafico 3D della superficie per una strategia robusta assomiglia a una montagna a tavola — una cima piatta con pendii dolci. Per una strategia fragile — un picco acuto, come il Cervino. La heatmap completa la vista 3D, mostrando le stesse informazioni in una proiezione dall'alto con curve di livello.
Segnali d'Allarme: Quando i Risultati dell'Ottimizzazione Sono Sospetti
Indicatori di avvertimento che segnalano potenziale overfitting nei risultati dell'ottimizzazione
Otto segnali che l'ottimizzazione ha trovato overfitting piuttosto che un pattern reale:
1. Rapporto di Sensibilità > 2 per un Parametro Chiave
Se il PnL scende di più del 20% con uno spostamento del 10% del parametro — l'ottimo è fragile.
2. Larghezza del Plateau < 10% dell'Intervallo di Ricerca
Se la regione "buona" occupa meno del 10% dell'intervallo esplorato — l'ottimizzatore ha molto probabilmente trovato un artefatto.
3. I Top-3 Trial Producono PnL 2-3x Sopra la Mediana
Se i migliori trial sono outlier rispetto agli altri piuttosto che la "cima della collina" — non è un 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. Numero di Trade Basso (< 50) con PnL Elevato
Piccolo campione + PnL elevato = alta varianza nella stima. L'analisi del plateau su 40 trade è di per sé inaffidabile. Per tali strategie, il bootstrap Monte Carlo è fondamentale.
5. Una "Magica" Combinazione di Parametri
Se il contour plot mostra un singolo punto luminoso in un campo grigio — questa non è una strategia, è una combinazione adattata ai dati.
6. Troppi Parametri
Per 12 parametri con 10 valori ciascuno, lo spazio di ricerca contiene combinazioni. Optuna ne esplora ~500. La probabilità di trovare un "buon" artefatto in tale spazio è alta. Più parametri ci sono, più rigorosa deve essere l'analisi del plateau.
7. PnL Crolla Bruscamente Fuori dal Campione
Se il PnL in-sample è +87% e il walk-forward mostra +12% — l'ottimizzazione ha adattato i parametri al periodo di addestramento. Maggiori dettagli nell'articolo sull'ottimizzazione Walk-Forward.
8. I Parametri Sono "Bloccati" ai Limiti dell'Intervallo
Se il valore ottimale coincide con il confine della griglia di ricerca — l'ottimo potrebbe trovarsi al di là dell'intervallo. Espandi l'intervallo e riesegui l'ottimizzazione.
Report Automatico di Analisi del Plateau
Raccogliendo tutto in un singolo report generato dopo ogni ottimizzazione:
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:
"""
Genera un report completo di analisi del plateau.
"""
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))
Esempio di output:
{
"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
}
}
}
Relazione con la Validazione Walk-Forward
Robustezza parametrica (analisi del plateau) e robustezza temporale (walk-forward) come due sistemi di validazione complementari
L'analisi del plateau e la validazione walk-forward (WFO) sono metodi complementari:
- L'analisi del plateau risponde alla domanda: "Quanto è stabile l'ottimo rispetto a piccoli spostamenti dei parametri?" Questo è un controllo della robustezza parametrica.
- Il walk-forward risponde alla domanda: "I parametri funzionano su dati che l'ottimizzatore non ha visto?" Questo è un controllo della robustezza temporale.
Una strategia può superare l'analisi del plateau (plateau ampio) ma fallire il walk-forward (il regime di mercato è cambiato). E viceversa — può superare il walk-forward con parametri fissi ma avere un ottimo fragile.
Raccomandazione: usa sempre entrambi i metodi. Se una strategia supera l'analisi del plateau () e il walk-forward () — questo è un forte segnale di robustezza. Maggiori dettagli nell'articolo sull'ottimizzazione Walk-Forward.
Per valutare gli intervalli di confidenza del PnL in ogni fase, applica il bootstrap Monte Carlo. E per confrontare correttamente le strategie con diverso tempo attivo, usa la metrica PnL per tempo attivo.
Raccomandazioni
Prima dell'Ottimizzazione
-
Limita il numero di parametri. Meno parametri — più affidabile il plateau. 5-7 parametri è un massimo ragionevole. 12 richiede già maggiore cautela.
-
Imposta intervalli significativi. Non impostare
htf_entry_sellda 0.001 a 1.0 se l'intervallo realistico è 0.005-0.05. Intervalli inutilmente ampi creano l'illusione di un plateau. -
Usa abbastanza trial. Per 12 parametri, un minimo di 300-500 trial. Per un'analisi del plateau affidabile — 1000+.
Durante l'Ottimizzazione
-
Monitora la convergenza. Se Optuna continua a trovare soluzioni significativamente migliori dopo 400 trial — il processo non ha ancora convergito, e l'analisi del plateau sarà inaffidabile.
-
Usa il pruning con cautela. Il pruning aggressivo (MedianPruner) può tagliare trial che sembrano cattivi nei primi passi ma sono importanti per costruire un'immagine completa del panorama.
Dopo l'Ottimizzazione
-
Genera il report del plateau automaticamente. Integra
generate_plateau_report()nel pipeline di ottimizzazione. Non fare affidamento sulla valutazione visiva — usa i numeri. -
Controlla i top-5 parametri. Se fANOVA mostra che 3 parametri spiegano l'80% della varianza — i restanti 9 possono essere controllati con meno rigore.
-
Confronta con la strategia base. Se la strategia con i parametri predefiniti (senza ottimizzazione) mostra +30%, e quella ottimizzata +55% — la differenza è solo di 25 pp, e il plateau è probabilmente ampio. Se quella predefinita mostra 0% e quella ottimizzata +300% — tutta la redditività dipende dall'adattamento preciso dei parametri.
-
Verifica finale — walk-forward. L'analisi del plateau è una condizione necessaria ma non sufficiente per la robustezza. Valida sempre fuori dal campione.
Conclusione
L'ottimizzazione dei parametri è uno strumento potente, ma senza l'analisi del plateau è un gioco d'azzardo. Non sai se hai trovato un pattern stabile o hai adattato il modello al rumore.
Tre regole dell'analisi del plateau:
-
Calcola il punteggio di robustezza. Il prodotto delle larghezze del plateau ponderate dà un singolo numero che riassume la robustezza di tutti i parametri. — via libera.
-
Rapporto di sensibilità < 1 per i parametri chiave. Se uno spostamento del 10% del parametro causa meno del 10% di calo del PnL — il parametro è robusto. Se di più — sii cauto.
-
Visualizza i contour plot. Nessuna metrica può sostituire la comprensione della forma del panorama. Una montagna a tavola piatta — bene. Un ago acuto — male.
L'analisi del plateau richiede 5 minuti dopo l'ottimizzazione e può risparmiare settimane di trading live non redditizio. È un passaggio obbligatorio tra study.optimize() e il lancio del bot.
Link Utili
- 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)
Citazione
@article{soloviov2026plateauanalysis,
author = {Soloviov, Eugen},
title = {Analisi del Plateau: Come Distinguere un Ottimo Robusto dall'Overfitting},
year = {2026},
url = {https://marketmaker.cc/it/blog/post/plateau-analysis-overfitting},
version = {0.1.0},
description = {Perché trovare i migliori parametri di strategia è solo metà del lavoro. Come distinguere visivamente e quantitativamente un plateau stabile da un picco fragile, e perché i contour plot di Optuna sono un passaggio obbligatorio prima di lanciare una strategia ottimizzata in produzione.}
}
Autori
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.