← 返回文章列表
March 12, 2026
5 分鐘閱讀

平臺分析:如何區分穩健最優與過擬合

平臺分析:如何區分穩健最優與過擬合
#演算法交易
#回測
#最佳化
#過擬合
#平臺分析
#參數穩定性
🎯
Part 4 of 9 · Collection
Backtesting Without Fooling Yourself

"回測無幻覺"系列第6篇

你運行了 study.optimize(),Optuna找到了PnL +87%的參數組合。你很興奮,準備將策略投入生產。兩週實盤交易後,PnL接近零。發生了什麼?

最佳化器在參數空間中找到了一根針尖。參數完美地擬合了歷史交易序列——但市場條件的最微小偏差就會摧毀整個結構。這是經典的過擬合,本可以在啟動之前就被發現。

上一篇文章中,我們比較了座標下降法和貝葉斯最佳化,展示了為什麼Optuna能更高效地找到最優值。今天是下一步:如何確保找到的最優值是穩健的,而不是對噪聲的擬合結果。

為什麼找到"最佳"參數只是工作的一半

在多維參數空間中搜索 最佳化器在廣闊的多維參數景觀中導航,尋找真正的最優值

策略參數最佳化是在多維空間中尋找最大值。問題在於最大值有兩種型別:

  1. 平臺(plateau) ——寬闊平坦的區域,PnL在參數變化時始終保持較高。即使市場條件將有效參數偏移10-20%,策略仍將繼續盈利。

  2. 尖峰(sharp peak) ——狹窄的頂點,只有在精確的參數值下PnL才高。偏移一步就會使盈利崩潰。這幾乎可以確定是過擬合:最佳化器找到了歷史資料的偽像,而非穩定的規律。

登山比喻:平臺是一個可以安全行走的山地高原。尖峰是一根只能在上面保持平衡的針尖。

尖峰 vs 平坦平臺——視覺直覺

尖峰與平坦平臺對比 左側:穩健的平臺(寬闊的桌山,斜坡平緩)。右側:脆弱的尖峰(針尖被深谷包圍)

想像一張等高線圖,兩軸是策略的兩個參數,顏色代表PnL。兩種模式在視覺上很容易區分:

平臺(穩健最優):

  • 同一顏色的大面積區域
  • PnL水平之間的平滑過渡
  • 等高線間距較大
  • 從最優值偏移±20%時PnL變化不超過10%

想像一張熱力圖:中心是一個約佔整個圖三分之一大小的明亮黃色矩形。顏色逐漸過渡到橙色,然後向邊緣變為紅色。最優值不是一個點,而是一個區域。

尖峰(過擬合):

  • 被冷色包圍的狹窄亮點
  • 急劇過渡:最優值旁邊就是深淵
  • 等高線緊縮成密集的環
  • 偏移±5%時PnL下降50%或更多

想像同樣的熱力圖,但中心只有一個微小的黃色點,立即被藍色和紫色包圍。唯一"正確"的參數組合。

參數敏感性分析

參數敏感性切片圖 切片圖展示PnL對各個參數值的依賴——寬頻表示穩健性,窄簇表示脆弱性

一維分析:PnL vs 單個參數

最簡單的方法——固定除一個外的所有參數,觀察PnL如何依賴於該參數的值。Optuna為此提供了 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()

在切片圖上看什麼:

  • 穩健參數: 點雲在最優值附近形成寬闊的水平帶。最佳trial分佈在參數值的寬範圍內。
  • 脆弱參數: 最佳trial集中在狹窄範圍內。參數偏移一兩步——盈利就崩潰。

二維分析:等高線圖(熱力圖)

等高線圖同時顯示兩個參數的互動作用。這是平臺分析的關鍵工具,因為參數很少獨立作用——入場和退出閾值、時間框架和倉位大小是相互關聯的。

from optuna.visualization import plot_contour

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

穩健參數對的等高線圖看起來像丘陵平原的地形圖:平滑寬闊的等高線,大面積同色區域。脆弱參數對的等高線圖——像火山錐的地圖:圍繞單一點的緊密同心環。

對於有12個分離參數的策略,這給出 (122)=66\binom{12}{2} = 66 個成對等高線圖。不必全部研究——從Optuna評為最重要的參數開始。

多維分析:參數重要性排名

Optuna可以估算每個參數對目標函數的貢獻:

from optuna.visualization import plot_param_importances

fig = plot_param_importances(study)
fig.show()

參數重要性圖是一個水平直方圖。參數按其對PnL方差的貢獻降序排列。前3-4個參數通常解釋了70-80%的方差。

規則: 如果一個參數解釋的PnL方差不到2%,其值對結果幾乎無關緊要——它天然是穩健的。將平臺分析聚焦在最重要的前5個參數上。

Optuna視覺化工具

Optuna等高線圖和參數重要性視覺化 展示參數互動景觀的等高線熱力圖和重要性排名

plot_slice——一維切片

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()

結果是一個散點圖網格。每個子圖顯示目標函數值(PnL,Y軸)與單個參數值(X軸)的關係。點是各個trial。對於穩健參數,最佳點(最高PnL)分佈在X的寬範圍內。對於脆弱參數——聚集在狹窄的列中。

plot_contour——二維等高線

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()

每個等高線圖是一張以兩個參數為軸的熱力圖。顏色編碼參數空間給定區域的平均PnL。黃色/綠色——高PnL,藍色/紫色——低PnL。等高線連線PnL相同的點。

plot_param_importances——參數貢獻

from optuna.visualization import plot_param_importances

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

fANOVA(函數方差分析)將目標函數的方差分解到各參數及其互動作用上。這比簡單相關更強大,因為它考慮了非線性效應。

平臺的定量指標

定量穩健性指標視覺化 敏感性比率、平臺寬度和穩健性評分——三個將平臺質量形式化的指標

視覺評估是主觀的。我們需要數字。以下三個指標將"平臺"概念形式化。

敏感度比率

PnL變化與參數變化的比率:

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

其中 ΔPnL\Delta \text{PnL} 是參數 pip_i 偏離最優值 Δpi\Delta p_i 時的PnL下降。

解讀:

  • Si<0.5S_i < 0.5 ——參數穩健:10%偏移導致不到5%的PnL下降
  • 0.5Si<2.00.5 \leq S_i < 2.0 ——中等敏感度
  • Si2.0S_i \geq 2.0 ——參數脆弱:10%偏移導致PnL下降20%以上

平臺寬度

參數區域的寬度,在該區域內PnL保持在最優值的 X%X\% 以內:

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

相對平臺寬度:

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

其中分母是參數的完整搜尋範圍。

解讀:

  • Wirel(10%)>0.3W_i^{rel}(10\%) > 0.3 ——在10%閾值下平臺覆蓋範圍的30%以上。穩健參數。
  • Wirel(10%)<0.05W_i^{rel}(10\%) < 0.05 ——平臺窄於範圍的5%。紅色警報。

穩健性得分

所有參數的綜合指標:

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

其中 wiw_i 是來自fANOVA的參數 ii 的歸一化重要性(wi=1\sum w_i = 1)。

加權寬度的乘積是一個嚴格的指標:如果任何一個重要參數有狹窄的平臺,RR 就會很低。不重要的參數(wiw_i 很小)幾乎沒有影響。

解讀:

  • R>0.1R > 0.1 ——策略是穩健的
  • 0.01<R0.10.01 < R \leq 0.1 ——需要額外驗證(Walk-Forward)
  • R0.01R \leq 0.01 ——過擬合的可能性很高

自動化平臺檢測的Python程式碼

自動化平臺檢測流水線 自動化系統掃描參數景觀,識別穩健平臺和脆弱尖峰

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:
    """
    计算单个参数的敏感度比率。

    将所有参数固定在最佳值,变化param_name,
    通过trial插值估计PnL下降。
    """
    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]:
    """
    计算绝对和相对平台宽度。

    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:
    """
    计算综合稳健性得分。

    Returns:
        包含逐参数指标和最终得分的dict
    """
    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"
        ),
    }

使用方法

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()

示例輸出:

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%

分離策略的實際案例

三種策略的穩健性概況對比 比較策略A(寬平臺,穩健)、策略B(中等)和策略C(尖峰,過擬合)

讓我們檢查三個具有12個分離參數的策略。每個策略都經過了500次trial的Optuna最佳化。

策略A(~55% PnL,~500筆交易,~15%時間)

策略A的參數形成了一個寬闊的平臺。以關鍵參數 htf_entry_sell 為例:

  • 最優值:0.020
  • 0.015時的PnL:+51%(下降7%)
  • 0.025時的PnL:+49%(下降11%)
  • 0.010時的PnL:+43%(下降22%)
  • 0.030時的PnL:+41%(下降25%)

如果將其想像為一維圖(X軸——htf_entry_sell 值,Y軸——PnL),你會看到一條平頂的平緩拋物線。範圍0.010-0.030是平臺,PnL保持在最優值的±25%以內。

敏感度比率:S=0.110.25=0.44S = \frac{0.11}{0.25} = 0.44 ——穩健。

10%閾值下的平臺寬度:從0.013到0.027,Wrel=0.0140.04=35%W^{rel} = \frac{0.014}{0.04} = 35\%

策略B(~25% PnL,~40筆交易,~5%時間)

策略B在少量交易上進行了最佳化。參數 htf_entry_sell

  • 最優值:0.018
  • 0.015時的PnL:+24%(下降4%)
  • 0.025時的PnL:+9%(下降64%)
  • 0.012時的PnL:+11%(下降56%)

在圖上——一條不對稱的陡峭曲線。平臺僅存在於0.015-0.020的狹窄範圍內。最優值右側是懸崖。

敏感度比率:S=0.640.39=1.64S = \frac{0.64}{0.39} = 1.64 ——中等敏感度,但在40筆交易下這是一個紅色警報。小樣本+窄平臺=過擬合機率高。

10%閾值下的平臺寬度:從0.016到0.020,Wrel=0.0040.04=10%W^{rel} = \frac{0.004}{0.04} = 10\%

策略C(~300% PnL,~400筆交易,~45%時間)

策略C展示了驚人的PnL,但平臺分析揭示了問題:

  • htf_entry_sell 最優值:0.022
  • 0.020時的PnL:+295%(下降2%)
  • 0.025時的PnL:+142%(下降53%
  • 0.019時的PnL:+128%(下降57%

在圖上——特徵性的"針":0.022處非常高的峰值,各方向急劇下降。等高線圖會顯示一個立即被冷色包圍的亮點。

敏感度比率:S=0.530.14=3.79S = \frac{0.53}{0.14} = 3.79 ——脆弱。儘管有400筆交易,策略過度依賴單個參數的精確值。

10%閾值下的平臺寬度:從0.021到0.023,Wrel=0.0020.04=5%W^{rel} = \frac{0.002}{0.04} = 5\%

彙總表

策略 PnL 交易數 敏感度 平臺寬度 穩健性得分 判定
策略A +55% ~500 0.44 35% 0.148 穩健
策略B +25% ~40 1.64 10% 0.032 需檢查(小樣本)
策略C +300% ~400 3.79 5% 0.008 過擬合

悖論: PnL +300%的策略C具有最差的穩健性得分。"謙虛"的+55%策略A是最穩健的。這是平臺分析的典型結果:亮眼的數字往往掩蓋了脆弱性。

每個策略的置信區間可以通過Monte Carlo自舉法進一步驗證——它會顯示重新取樣交易時PnL的分散程度。

3D視覺化和熱力圖

3D參數景觀曲面與等高線投影 兩個參數上PnL的3D曲面圖,等高線投影到底部平面

對於最重要的參數對,構建3D曲面和熱力圖是有用的。這提供了對景觀形狀的直觀理解。

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,
):
    """
    为一对参数构建3D曲面图和热力图。
    """
    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()

穩健策略的3D曲面圖類似於桌山——平坦的頂部和平緩的斜坡。脆弱策略的——像馬特洪峰一樣的尖頂。熱力圖補充了3D檢視,以俯視投影顯示相同資訊並帶有等高線。

紅色警報:最佳化結果何時可疑

最佳化結果預警儀表板 訊號潛在過擬合的預警指標

八個跡象表明最佳化找到的是過擬合而非真實規律:

1. 關鍵參數的敏感度比率 > 2

如果10%的參數偏移導致PnL下降超過20%——最優值是脆弱的。

2. 平臺寬度 < 搜尋範圍的10%

如果"好"的區域佔探索範圍的不到10%——最佳化器很可能找到了偽像。

3. 前3名trial的PnL是中位數的2-3倍

如果最佳trial是相對於其餘trial的異常值而非"山頂"——這不是平臺。

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. 交易數量少(< 50)且PnL高

小樣本+高PnL=估計的高方差。40筆交易的平臺分析本身就不可靠。對於這類策略,Monte Carlo自舉法至關重要。

5. 一個"神奇"的參數組合

如果等高線圖在灰色背景上顯示單一亮點——這不是策略,這是擬合到資料的組合。

6. 參數太多

對於12個參數每個10個值,搜尋空間包含 101210^{12} 種組合。Optuna探索約500種。在如此大的空間中隨機找到"好"的偽像的機率很高。參數越多,平臺分析應越嚴格。

7. 樣本外PnL急劇下降

如果樣本內PnL為+87%而Walk-Forward顯示+12%——最佳化將參數擬合到了訓練期。更多內容請參見Walk-Forward最佳化文章。

8. 參數"貼"在範圍邊界上

如果最優值與搜尋網格邊界重合——最優值可能在範圍之外。擴大範圍並重新執行最佳化。

自動化平臺分析報告

將所有內容整合到每次最佳化後生成的單一報告中:

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:
    """
    生成完整的平台分析报告。
    """
    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))

示例輸出:

{
  "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
    }
  }
}

與Walk-Forward驗證的關係

Walk-Forward驗證作為平臺分析的補充 參數穩健性(平臺分析)和時間穩健性(Walk-Forward)作為兩個互補的驗證系統

平臺分析和Walk-Forward驗證(WFO)是互補的方法:

  • 平臺分析回答的問題是:"最優值對參數的小偏移有多穩定?"這是對參數穩健性的檢驗。
  • Walk-Forward回答的問題是:"參數在最佳化器未見過的資料上有效嗎?"這是對時間穩健性的檢驗。

一個策略可能通過平臺分析(寬平臺)但未通過Walk-Forward(市場機制改變)。反之亦然——它可能在固定參數上通過Walk-Forward,但具有脆弱的最優值。

建議: 始終使用兩種方法。如果策略通過了平臺分析(R>0.1R > 0.1並且 Walk-Forward(PnLOOS>50%×PnLIS\text{PnL}_{OOS} > 50\% \times \text{PnL}_{IS})——這是穩健性的強訊號。更多詳情請參見Walk-Forward最佳化文章

要評估每個階段的PnL置信區間,請應用Monte Carlo自舉法。要正確比較具有不同活躍時間的策略,請使用按活躍時間計算的PnL指標。

建議

最佳化之前

  1. 限制參數數量。 參數越少——平臺越可靠。5-7個參數是合理的上限。12個已經需要更加謹慎。

  2. 設定有意義的範圍。 如果實際範圍是0.005到0.05,不要將 htf_entry_sell 設定為0.001到1.0。不必要的寬範圍會創造平臺的幻覺。

  3. 使用足夠的trial。 對於12個參數,至少300-500次trial。對於可靠的平臺分析——1000+次。

最佳化期間

  1. 關注收斂性。 如果Optuna在400次trial後仍在找到明顯更好的解——過程尚未收斂,平臺分析將不可靠。

  2. 謹慎使用剪枝。 激進的剪枝(MedianPruner)可能會裁剪掉在早期步驟中看起來較差但對構建完整景觀圖很重要的trial。

最佳化之後

  1. 自動生成平臺報告。generate_plateau_report() 整合到最佳化流程中。不要依賴視覺評估——使用數字。

  2. 檢查前5個參數。 如果fANOVA顯示3個參數解釋了80%的方差——其餘9個可以檢查得不那麼仔細。

  3. 與基線策略比較。 如果使用預設參數(未最佳化)的策略顯示+30%,最佳化後+55%——差距只有25個百分點,平臺很可能是寬的。如果預設顯示0%,最佳化後+300%——所有盈利都依賴於精確的參數擬合。

  4. 最終檢查——Walk-Forward。 平臺分析是穩健性的必要但非充分條件。務必進行樣本外驗證

結論

參數最佳化是一個強大的工具,但沒有平臺分析就像玩輪盤賭。你不知道自己是找到了穩定的規律還是將模型擬合到了噪聲。

平臺分析的三條規則:

  1. 計算穩健性得分。 加權平臺寬度的乘積給出一個總結所有參數穩健性的單一數字。R>0.1R > 0.1 ——綠燈。

  2. 關鍵參數的敏感度比率 < 1。 如果10%的參數偏移導致不到10%的PnL下降——參數是穩健的。如果更多——要謹慎。

  3. 視覺化等高線圖。 沒有任何指標能替代對景觀形狀的理解。平坦的桌山——好。尖銳的針——差。

平臺分析在最佳化後只需5分鐘,可以節省數週的虧損實盤交易。這是 study.optimize() 和啟動機器人之間的必要步驟。


參考連結

  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)

引用

@article{soloviov2026plateauanalysis,
  author = {Soloviov, Eugen},
  title = {Plateau Analysis: How to Distinguish a Robust Optimum from Overfitting},
  year = {2026},
  url = {https://marketmaker.cc/zh/blog/post/plateau-analysis-overfitting},
  version = {0.1.0},
  description = {为什么找到最佳策略参数只是工作的一半。如何从视觉和定量两方面区分稳定的平台与脆弱的尖峰,以及为什么Optuna等高线图是将优化后的策略投入生产前的必要步骤。}
}
免責宣告:本文提供的資訊僅用於教育和參考目的,不構成財務、投資或交易建議。加密貨幣交易涉及重大損失風險。

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

緊跟市場步伐

訂閱我們的時事通訊,獲取獨家 AI 交易見解、市場分析和平台更新。

我們尊重您的隱私。您可以隨時退訂。