← Torna agli articoli
March 12, 2026
5 min di lettura

Analisi del Plateau: Come Distinguere un Ottimo Robusto dall'Overfitting

Analisi del Plateau: Come Distinguere un Ottimo Robusto dall'Overfitting
#algotrading
#backtest
#ottimizzazione
#overfitting
#analisi del plateau
#stabilità dei parametri

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

Ricerca nello spazio dei parametri multidimensionale 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:

  1. 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.

  2. 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

Confronto tra picco acuto e plateau piatto 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 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à (122)=66\binom{12}{2} = 66 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

Contour plot e visualizzazione dell'importanza dei parametri 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

Visualizzazione delle metriche di robustezza quantitative 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:

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

dove ΔPnL\Delta \text{PnL} è il calo del PnL quando il parametro pip_i devia dall'ottimo di Δpi\Delta p_i.

Interpretazione:

  • Si<0.5S_i < 0.5 — il parametro è robusto: uno spostamento del 10% causa meno del 5% di calo del PnL
  • 0.5Si<2.00.5 \leq S_i < 2.0 — sensibilità moderata
  • Si2.0S_i \geq 2.0 — 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 X%X\% dell'ottimo:

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}

Larghezza relativa del plateau:

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

dove il denominatore è l'intervallo di ricerca completo del parametro.

Interpretazione:

  • Wirel(10%)>0.3W_i^{rel}(10\%) > 0.3 — il plateau copre più del 30% dell'intervallo alla soglia del 10%. Parametro robusto.
  • Wirel(10%)<0.05W_i^{rel}(10\%) < 0.05 — il plateau è più stretto del 5% dell'intervallo. Segnale d'allarme.

Punteggio di Robustezza

Una metrica combinata su tutti i parametri:

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

dove wiw_i è l'importanza normalizzata del parametro ii da fANOVA (wi=1\sum w_i = 1).

Il prodotto delle larghezze ponderate è una metrica rigorosa: se anche un solo parametro importante ha un plateau stretto, RR sarà basso. I parametri non importanti (con wiw_i piccoli) hanno quasi nessun effetto.

Interpretazione:

  • R>0.1R > 0.1 — la strategia è robusta
  • 0.01<R0.10.01 < R \leq 0.1 — è necessaria una validazione aggiuntiva (walk-forward)
  • R0.01R \leq 0.01 — l'overfitting è molto probabile

Codice Python per il Rilevamento Automatico del Plateau

Pipeline di 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

Tre strategie confrontate per profilo di robustezza 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à: S=0.110.25=0.44S = \frac{0.11}{0.25} = 0.44 — robusto.

Larghezza del plateau alla soglia del 10%: da 0.013 a 0.027, Wrel=0.0140.04=35%W^{rel} = \frac{0.014}{0.04} = 35\%.

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à: S=0.640.39=1.64S = \frac{0.64}{0.39} = 1.64 — 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, Wrel=0.0040.04=10%W^{rel} = \frac{0.004}{0.04} = 10\%.

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à: S=0.530.14=3.79S = \frac{0.53}{0.14} = 3.79fragile. 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, Wrel=0.0020.04=5%W^{rel} = \frac{0.002}{0.04} = 5\%.

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

Superficie del panorama dei parametri 3D con proiezione dei contorni 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

Dashboard dei segnali d'allarme per i risultati dell'ottimizzazione 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 101210^{12} 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

Validazione walk-forward che complementa l'analisi del plateau 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 (R>0.1R > 0.1) e il walk-forward (PnLOOS>50%×PnLIS\text{PnL}_{OOS} > 50\% \times \text{PnL}_{IS}) — 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

  1. Limita il numero di parametri. Meno parametri — più affidabile il plateau. 5-7 parametri è un massimo ragionevole. 12 richiede già maggiore cautela.

  2. Imposta intervalli significativi. Non impostare htf_entry_sell da 0.001 a 1.0 se l'intervallo realistico è 0.005-0.05. Intervalli inutilmente ampi creano l'illusione di un plateau.

  3. Usa abbastanza trial. Per 12 parametri, un minimo di 300-500 trial. Per un'analisi del plateau affidabile — 1000+.

Durante l'Ottimizzazione

  1. 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.

  2. 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

  1. Genera il report del plateau automaticamente. Integra generate_plateau_report() nel pipeline di ottimizzazione. Non fare affidamento sulla valutazione visiva — usa i numeri.

  2. 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.

  3. 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.

  4. 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:

  1. Calcola il punteggio di robustezza. Il prodotto delle larghezze del plateau ponderate dà un singolo numero che riassume la robustezza di tutti i parametri. R>0.1R > 0.1 — via libera.

  2. 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.

  3. 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

  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)

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.}
}
Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

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

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.