← Quay lại danh sách bài viết
March 12, 2026
5 phút đọc

Phân Tích Plateau: Cách Phân Biệt Tối Ưu Bền Vững với Overfitting

Phân Tích Plateau: Cách Phân Biệt Tối Ưu Bền Vững với Overfitting
#algotrading
#backtest
#tối ưu hóa
#overfitting
#phân tích plateau
#độ ổn định tham số

Bài 6 trong series "Backtest Không Ảo Tưởng"

Bạn đã chạy study.optimize(), Optuna tìm được bộ tham số với PnL +87%. Bạn hào hứng và chuẩn bị đưa chiến lược vào production. Hai tuần giao dịch thực tế sau đó, PnL gần bằng không. Chuyện gì đã xảy ra?

Bộ tối ưu đã tìm thấy đỉnh của một cây kim trong không gian tham số. Các tham số khớp hoàn hảo với chuỗi giao dịch lịch sử — nhưng chỉ cần một sai lệch nhỏ trong điều kiện thị trường là toàn bộ cấu trúc sụp đổ. Đây là overfitting cổ điển, và nó có thể được phát hiện trước khi triển khai.

Trong bài trước, chúng ta đã so sánh coordinate descent với Bayesian optimization và chỉ ra tại sao Optuna tìm tối ưu hiệu quả hơn. Hôm nay — bước tiếp theo: làm thế nào để đảm bảo điểm tối ưu đã tìm được là bền vững, chứ không phải là kết quả của việc khớp với nhiễu.

Tại Sao Tìm Tham Số "Tốt Nhất" Chỉ Là Nửa Công Việc

Tìm kiếm trong không gian tham số đa chiều Bộ tối ưu điều hướng qua không gian tham số đa chiều rộng lớn để tìm kiếm điểm tối ưu thực sự

Tối ưu hóa tham số chiến lược là việc tìm kiếm cực đại trong không gian đa chiều. Vấn đề là các cực đại có hai loại:

  1. Plateau — một vùng phẳng rộng nơi PnL liên tục cao ở nhiều biến thể tham số. Ngay cả khi điều kiện thị trường dịch chuyển các tham số hiệu quả 10-20%, chiến lược vẫn tiếp tục sinh lời.

  2. Đỉnh nhọn — một đỉnh hẹp nơi PnL chỉ cao ở đúng giá trị tham số. Dịch chuyển một bước là khả năng sinh lời sụp đổ. Đây gần như chắc chắn là overfitting: bộ tối ưu đã tìm thấy một hiện vật của dữ liệu lịch sử, không phải một mẫu hình ổn định.

Một ẩn dụ từ leo núi: một plateau là cao nguyên núi nơi bạn có thể đi bộ an toàn. Một đỉnh nhọn là đầu mũi kim nơi bạn chỉ có thể cân bằng.

Đỉnh Nhọn vs Plateau Phẳng — Trực Giác Hình Ảnh

So sánh đỉnh nhọn và plateau phẳng Trái: một plateau bền vững (núi bàn rộng với sườn thoải). Phải: một đỉnh nhọn mong manh (đầu mũi kim bao quanh bởi các thung lũng sâu)

Hãy tưởng tượng một bản đồ đường đồng mức trong đó các trục là hai tham số chiến lược và màu sắc biểu thị PnL. Hai mẫu hình dễ dàng phân biệt bằng mắt:

Plateau (tối ưu bền vững):

  • Vùng rộng cùng màu
  • Chuyển đổi mượt mà giữa các mức PnL
  • Các đường đồng mức cách xa nhau
  • Dịch chuyển khỏi điểm tối ưu +/-20% chỉ thay đổi PnL không quá 10%

Hãy tưởng tượng một heatmap: ở giữa — một hình chữ nhật vàng tươi rộng khoảng một phần ba toàn bộ bản đồ. Màu sắc dần chuyển sang cam, rồi đỏ về phía các cạnh. Điểm tối ưu không phải là một điểm, mà là một vùng.

Đỉnh nhọn (overfitting):

  • Một đốm sáng hẹp bao quanh bởi màu lạnh
  • Chuyển đổi đột ngột: sụp đổ ngay cạnh điểm tối ưu
  • Các đường đồng mức nén thành vòng tròn chặt
  • Dịch chuyển +/-5% làm PnL giảm 50% hoặc hơn

Hãy tưởng tượng cùng một heatmap, nhưng ở giữa — một chấm vàng nhỏ ngay lập tức bao quanh bởi màu xanh lam và tím. Chỉ một tổ hợp tham số "đúng" duy nhất.

Phân Tích Độ Nhạy Tham Số

Biểu đồ slice độ nhạy tham số Biểu đồ slice cho thấy PnL phụ thuộc vào các giá trị tham số riêng lẻ như thế nào — dải rộng cho thấy sự bền vững, cụm hẹp cho thấy sự mong manh

Phân Tích Một Chiều: PnL vs Một Tham Số

Cách tiếp cận đơn giản nhất — cố định tất cả các tham số trừ một và xem PnL phụ thuộc vào giá trị của nó như thế nào. Optuna cung cấp plot_slice cho mục đích này:

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

Những gì cần tìm kiếm trên biểu đồ slice:

  • Tham số bền vững: đám mây điểm tạo thành dải ngang rộng gần điểm tối ưu. Các trial tốt nhất trải rộng trên phạm vi giá trị tham số rộng.
  • Tham số mong manh: các trial tốt nhất tập trung trong một phạm vi hẹp. Dịch chuyển tham số một hoặc hai bước — và khả năng sinh lời sụp đổ.

Phân Tích Hai Chiều: Biểu Đồ Đường Đồng Mức (Heatmap)

Biểu đồ đường đồng mức cho thấy sự tương tác của hai tham số đồng thời. Đây là công cụ chính để phân tích plateau, vì các tham số hiếm khi hoạt động độc lập — ngưỡng vào và ra, khung thời gian, và kích thước vị thế đều có liên quan với nhau.

from optuna.visualization import plot_contour

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

Biểu đồ đường đồng mức cho một cặp tham số bền vững trông giống như bản đồ địa hình của một đồng bằng đồi núi: đường đồng mức rộng và mượt mà, các vùng lớn cùng màu. Biểu đồ đường đồng mức cho một cặp mong manh — giống như bản đồ của một ngọn núi lửa: các vòng tròn đồng tâm chặt xung quanh một điểm duy nhất.

Đối với một chiến lược có 12 tham số phân tách, điều này cho ra (122)=66\binom{12}{2} = 66 biểu đồ đường đồng mức theo cặp. Bạn không cần nghiên cứu tất cả — hãy bắt đầu với các tham số mà Optuna đánh giá là quan trọng nhất.

Phân Tích Đa Chiều: Xếp Hạng Tầm Quan Trọng của Tham Số

Optuna có thể ước tính đóng góp của từng tham số vào hàm mục tiêu:

from optuna.visualization import plot_param_importances

fig = plot_param_importances(study)
fig.show()

Biểu đồ tầm quan trọng tham số là một biểu đồ cột ngang. Các tham số được xếp hạng theo đóng góp của chúng vào phương sai PnL theo thứ tự giảm dần. 3-4 tham số hàng đầu thường giải thích 70-80% phương sai.

Quy tắc: nếu một tham số giải thích ít hơn 2% phương sai PnL, giá trị của nó thực tế không liên quan đến kết quả — nó bền vững theo định nghĩa. Tập trung phân tích plateau vào 5 tham số quan trọng nhất.

Công Cụ Trực Quan Hóa của Optuna

Biểu đồ đường đồng mức Optuna và trực quan hóa tầm quan trọng tham số Heatmap đường đồng mức cho thấy bối cảnh tương tác tham số cùng với xếp hạng tầm quan trọng

plot_slice — Các Lát Cắt Một Chiều

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

Kết quả — một lưới biểu đồ phân tán. Mỗi subplot cho thấy giá trị hàm mục tiêu (PnL, trục Y) so với một giá trị tham số đơn lẻ (trục X). Các điểm là các trial riêng lẻ. Đối với một tham số bền vững, các điểm tốt nhất (PnL cao nhất) được phân phối trên phạm vi X rộng. Đối với tham số mong manh — tập trung trong một cột hẹp.

plot_contour — Đường Đồng Mức Hai Chiều

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

Mỗi biểu đồ đường đồng mức là một heatmap với hai tham số trên các trục. Màu sắc mã hóa PnL trung bình trong một vùng nhất định của không gian tham số. Vàng/xanh lá — PnL cao, xanh lam/tím — thấp. Các đường đồng mức kết nối các điểm có cùng PnL.

plot_param_importances — Đóng Góp của Tham Số

from optuna.visualization import plot_param_importances

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

fANOVA (functional ANOVA) phân rã phương sai của hàm mục tiêu theo các tham số và tương tác của chúng. Điều này mạnh hơn tương quan đơn giản vì nó tính đến các hiệu ứng phi tuyến.

Các Chỉ Số Định Lượng của Plateau

Trực quan hóa các chỉ số bền vững định lượng Sensitivity ratio, độ rộng plateau, và robustness score — ba chỉ số hình thức hóa chất lượng plateau

Đánh giá trực quan là chủ quan. Chúng ta cần con số. Dưới đây là ba chỉ số hình thức hóa khái niệm "plateau".

Sensitivity Ratio (Tỷ Lệ Độ Nhạy)

Tỷ lệ thay đổi PnL so với thay đổi tham số:

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

trong đó ΔPnL\Delta \text{PnL} là mức giảm PnL khi tham số pip_i lệch khỏi điểm tối ưu một lượng Δpi\Delta p_i.

Giải thích:

  • Si<0.5S_i < 0.5 — tham số bền vững: dịch chuyển 10% gây ra mức giảm PnL ít hơn 5%
  • 0.5Si<2.00.5 \leq S_i < 2.0 — độ nhạy vừa phải
  • Si2.0S_i \geq 2.0 — tham số mong manh: dịch chuyển 10% làm PnL sụp đổ hơn 20%

Độ Rộng Plateau

Độ rộng của vùng tham số mà trong đó PnL vẫn nằm trong X%X\% so với điểm tối ưu:

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}

Độ rộng plateau tương đối:

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

trong đó mẫu số là phạm vi tìm kiếm đầy đủ của tham số.

Giải thích:

  • Wirel(10%)>0.3W_i^{rel}(10\%) > 0.3 — plateau bao phủ hơn 30% phạm vi ở ngưỡng 10%. Tham số bền vững.
  • Wirel(10%)<0.05W_i^{rel}(10\%) < 0.05 — plateau hẹp hơn 5% phạm vi. Dấu hiệu cảnh báo.

Robustness Score (Điểm Bền Vững)

Một chỉ số kết hợp trên tất cả các tham số:

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

trong đó wiw_i là tầm quan trọng chuẩn hóa của tham số ii từ fANOVA (wi=1\sum w_i = 1).

Tích của các độ rộng có trọng số là một chỉ số nghiêm ngặt: nếu ngay cả một tham số quan trọng có plateau hẹp, RR sẽ thấp. Các tham số không quan trọng (có wiw_i nhỏ) gần như không có ảnh hưởng.

Giải thích:

  • R>0.1R > 0.1 — chiến lược bền vững
  • 0.01<R0.10.01 < R \leq 0.1 — cần xác nhận bổ sung (walk-forward)
  • R0.01R \leq 0.01 — overfitting rất có khả năng

Mã Python để Tự Động Phát Hiện Plateau

Đường ống phát hiện plateau tự động Hệ thống tự động quét bối cảnh tham số để xác định các plateau bền vững và các đỉnh mong manh

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"
        ),
    }

Cách Sử Dụng

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

Ví dụ đầu ra:

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%

Ví Dụ Thực Tế với Các Chiến Lược Phân Tách

Ba chiến lược so sánh theo hồ sơ bền vững So sánh Chiến lược A (plateau rộng, bền vững), Chiến lược B (vừa phải), và Chiến lược C (đỉnh nhọn, overfitted)

Hãy xem xét ba chiến lược với 12 tham số phân tách. Mỗi chiến lược đã trải qua tối ưu hóa Optuna với 500 trial.

Chiến Lược A (~55% PnL, ~500 giao dịch, ~15% thời gian)

Các tham số của Chiến lược A tạo thành một plateau rộng. Lấy tham số chính htf_entry_sell:

  • Giá trị tối ưu: 0.020
  • PnL tại 0.015: +51% (giảm 7%)
  • PnL tại 0.025: +49% (giảm 11%)
  • PnL tại 0.010: +43% (giảm 22%)
  • PnL tại 0.030: +41% (giảm 25%)

Nếu bạn tưởng tượng điều này như một biểu đồ một chiều (trục X — giá trị htf_entry_sell, trục Y — PnL), bạn sẽ thấy một parabol thoải với đỉnh phẳng. Phạm vi 0.010-0.030 là plateau, nơi PnL vẫn trong khoảng +/-25% so với điểm tối ưu.

Sensitivity ratio: S=0.110.25=0.44S = \frac{0.11}{0.25} = 0.44 — bền vững.

Độ rộng plateau ở ngưỡng 10%: từ 0.013 đến 0.027, Wrel=0.0140.04=35%W^{rel} = \frac{0.014}{0.04} = 35\%.

Chiến Lược B (~25% PnL, ~40 giao dịch, ~5% thời gian)

Chiến lược B được tối ưu hóa trên số lượng giao dịch nhỏ. Tham số htf_entry_sell:

  • Giá trị tối ưu: 0.018
  • PnL tại 0.015: +24% (giảm 4%)
  • PnL tại 0.025: +9% (giảm 64%)
  • PnL tại 0.012: +11% (giảm 56%)

Trên biểu đồ — một đường cong bất đối xứng và dốc. Plateau chỉ tồn tại trong phạm vi hẹp 0.015-0.020. Bên phải điểm tối ưu — một vách đá.

Sensitivity ratio: S=0.640.39=1.64S = \frac{0.64}{0.39} = 1.64 — độ nhạy vừa phải, nhưng với 40 giao dịch đây là dấu hiệu cảnh báo đỏ. Mẫu nhỏ + plateau hẹp = xác suất overfitting cao.

Độ rộng plateau ở ngưỡng 10%: từ 0.016 đến 0.020, Wrel=0.0040.04=10%W^{rel} = \frac{0.004}{0.04} = 10\%.

Chiến Lược C (~300% PnL, ~400 giao dịch, ~45% thời gian)

Chiến lược C cho thấy PnL ấn tượng, nhưng phân tích plateau tiết lộ các vấn đề:

  • Giá trị tối ưu của htf_entry_sell: 0.022
  • PnL tại 0.020: +295% (giảm 2%)
  • PnL tại 0.025: +142% (giảm 53%)
  • PnL tại 0.019: +128% (giảm 57%)

Trên biểu đồ — một "kim" đặc trưng: đỉnh rất cao tại 0.022, giảm mạnh theo mọi hướng. Biểu đồ đường đồng mức sẽ cho thấy một đốm sáng ngay lập tức bao quanh bởi màu lạnh.

Sensitivity ratio: S=0.530.14=3.79S = \frac{0.53}{0.14} = 3.79mong manh. Mặc dù có 400 giao dịch, chiến lược phụ thuộc quá mức vào giá trị chính xác của một tham số duy nhất.

Độ rộng plateau ở ngưỡng 10%: từ 0.021 đến 0.023, Wrel=0.0020.04=5%W^{rel} = \frac{0.002}{0.04} = 5\%.

Bảng Tóm Tắt

Chiến lược PnL Giao dịch Sensitivity Độ rộng plateau Robustness score Kết luận
Chiến lược A +55% ~500 0.44 35% 0.148 Bền vững
Chiến lược B +25% ~40 1.64 10% 0.032 Kiểm tra (mẫu nhỏ)
Chiến lược C +300% ~400 3.79 5% 0.008 Overfitting

Nghịch lý: Chiến lược C với PnL +300% có robustness score tệ nhất. Chiến lược A với mức "khiêm tốn" +55% là bền vững nhất. Đây là kết quả điển hình của phân tích plateau: những con số ấn tượng thường che giấu sự mong manh.

Khoảng tin cậy cho mỗi chiến lược có thể được xác minh thêm thông qua Monte Carlo bootstrap — nó sẽ cho thấy sự phân tán PnL khi lấy mẫu lại các giao dịch.

Trực Quan Hóa 3D và Heatmap

Bề mặt bối cảnh tham số 3D với phép chiếu đường đồng mức Biểu đồ bề mặt 3D của PnL theo hai tham số với các đường đồng mức chiếu xuống mặt phẳng sàn

Đối với các cặp tham số quan trọng nhất, việc xây dựng bề mặt 3D và heatmap rất hữu ích. Điều này cung cấp sự hiểu biết trực quan về hình dạng bối cảnh.

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

Biểu đồ bề mặt 3D của một chiến lược bền vững giống như một núi bàn — đỉnh phẳng với sườn thoải. Đối với chiến lược mong manh — một đỉnh nhọn, như núi Matterhorn. Heatmap bổ sung cho góc nhìn 3D, hiển thị cùng thông tin trong phép chiếu nhìn từ trên xuống với các đường đồng mức.

Dấu Hiệu Cảnh Báo: Khi Nào Kết Quả Tối Ưu Đáng Ngờ

Bảng điều khiển dấu hiệu cảnh báo cho kết quả tối ưu Các chỉ báo cảnh báo báo hiệu overfitting tiềm ẩn trong kết quả tối ưu

Tám dấu hiệu cho thấy tối ưu hóa đã tìm thấy overfitting thay vì mẫu hình thực sự:

1. Sensitivity Ratio > 2 cho Tham Số Quan Trọng

Nếu PnL giảm hơn 20% khi tham số dịch chuyển 10% — điểm tối ưu mong manh.

2. Độ Rộng Plateau < 10% của Phạm Vi Tìm Kiếm

Nếu vùng "tốt" chiếm ít hơn 10% phạm vi đã khám phá — bộ tối ưu rất có thể đã tìm thấy một hiện vật.

3. Top-3 Trial Có PnL Cao Gấp 2-3 Lần So Với Trung Vị

Nếu các trial tốt nhất là các ngoại lệ so với phần còn lại thay vì là "đỉnh đồi" — đây không phải là 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. Số Lượng Giao Dịch Thấp (< 50) với PnL Cao

Mẫu nhỏ + PnL cao = phương sai cao trong ước tính. Phân tích plateau trên 40 giao dịch tự nó không đáng tin cậy. Đối với các chiến lược như vậy, Monte Carlo bootstrap là rất quan trọng.

5. Một Tổ Hợp Tham Số "Ma Thuật"

Nếu biểu đồ đường đồng mức cho thấy một đốm sáng duy nhất giữa cánh đồng xám — đây không phải là chiến lược, đây là một tổ hợp khớp với dữ liệu.

6. Quá Nhiều Tham Số

Đối với 12 tham số với 10 giá trị mỗi tham số, không gian tìm kiếm chứa 101210^{12} tổ hợp. Optuna khám phá ~500. Xác suất tìm thấy một "hiện vật tốt" trong không gian như vậy cao. Càng nhiều tham số, phân tích plateau càng phải nghiêm ngặt hơn.

7. PnL Giảm Mạnh Ngoài Mẫu

Nếu PnL trong mẫu là +87% và walk-forward cho thấy +12% — tối ưu hóa đã khớp các tham số với giai đoạn huấn luyện. Chi tiết hơn trong bài viết Walk-Forward optimization.

8. Tham Số "Bị Ghim" vào Ranh Giới Phạm Vi

Nếu giá trị tối ưu trùng với ranh giới lưới tìm kiếm — điểm tối ưu có thể nằm ngoài phạm vi. Mở rộng phạm vi và chạy lại tối ưu hóa.

Báo Cáo Phân Tích Plateau Tự Động

Tổng hợp tất cả vào một báo cáo duy nhất được tạo sau mỗi lần tối ưu:

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

Ví dụ đầu ra:

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

Mối Quan Hệ với Xác Nhận Walk-Forward

Xác nhận walk-forward bổ sung cho phân tích plateau Tính bền vững theo tham số (phân tích plateau) và tính bền vững theo thời gian (walk-forward) như hai hệ thống xác nhận bổ sung

Phân tích plateau và xác nhận walk-forward (WFO) là các phương pháp bổ sung cho nhau:

  • Phân tích plateau trả lời câu hỏi: "Điểm tối ưu có ổn định với các dịch chuyển tham số nhỏ không?" Đây là kiểm tra tính bền vững theo tham số.
  • Walk-forward trả lời câu hỏi: "Các tham số có hoạt động trên dữ liệu mà bộ tối ưu chưa thấy không?" Đây là kiểm tra tính bền vững theo thời gian.

Một chiến lược có thể vượt qua phân tích plateau (plateau rộng) nhưng thất bại với walk-forward (chế độ thị trường đã thay đổi). Và ngược lại — có thể vượt qua walk-forward với các tham số cố định nhưng có điểm tối ưu mong manh.

Khuyến nghị: luôn sử dụng cả hai phương pháp. Nếu một chiến lược vượt qua phân tích plateau (R>0.1R > 0.1) walk-forward (PnLOOS>50%×PnLIS\text{PnL}_{OOS} > 50\% \times \text{PnL}_{IS}) — đây là tín hiệu mạnh về tính bền vững. Chi tiết hơn trong bài viết Walk-Forward optimization.

Để đánh giá khoảng tin cậy PnL ở mỗi giai đoạn, hãy áp dụng Monte Carlo bootstrap. Và để so sánh chính xác các chiến lược với thời gian hoạt động khác nhau, hãy sử dụng chỉ số PnL trên thời gian hoạt động.

Khuyến Nghị

Trước Khi Tối Ưu

  1. Giới hạn số lượng tham số. Càng ít tham số — plateau càng đáng tin cậy. 5-7 tham số là mức tối đa hợp lý. 12 đã đòi hỏi sự thận trọng tăng cao.

  2. Đặt phạm vi có ý nghĩa. Đừng đặt htf_entry_sell từ 0.001 đến 1.0 nếu phạm vi thực tế là 0.005 đến 0.05. Phạm vi quá rộng không cần thiết tạo ra ảo giác về plateau.

  3. Sử dụng đủ số lượng trial. Đối với 12 tham số, tối thiểu 300-500 trial. Để phân tích plateau đáng tin cậy — 1000+.

Trong Quá Trình Tối Ưu

  1. Theo dõi sự hội tụ. Nếu Optuna tiếp tục tìm các giải pháp tốt hơn đáng kể sau 400 trial — quá trình chưa hội tụ, và phân tích plateau sẽ không đáng tin cậy.

  2. Sử dụng pruning một cách thận trọng. Pruning tích cực (MedianPruner) có thể cắt bỏ các trial trông không tốt ở các bước đầu nhưng quan trọng để xây dựng một bức tranh đầy đủ về bối cảnh.

Sau Khi Tối Ưu

  1. Tự động tạo báo cáo plateau. Tích hợp generate_plateau_report() vào đường ống tối ưu hóa. Đừng dựa vào đánh giá trực quan — sử dụng con số.

  2. Kiểm tra 5 tham số hàng đầu. Nếu fANOVA cho thấy 3 tham số giải thích 80% phương sai — 9 tham số còn lại có thể được kiểm tra ít kỹ hơn.

  3. So sánh với chiến lược cơ sở. Nếu chiến lược với các tham số mặc định (không tối ưu) cho thấy +30%, và tối ưu hóa cho +55% — chênh lệch chỉ là 25 pp, và plateau rất có thể rộng. Nếu mặc định cho thấy 0%, và tối ưu hóa cho +300% — toàn bộ khả năng sinh lời phụ thuộc vào việc khớp tham số chính xác.

  4. Kiểm tra cuối cùng — walk-forward. Phân tích plateau là điều kiện cần nhưng chưa đủ cho tính bền vững. Luôn xác nhận ngoài mẫu.

Kết Luận

Tối ưu hóa tham số là một công cụ mạnh mẽ, nhưng không có phân tích plateau thì đó là một trò chơi xổ số. Bạn không biết liệu bạn đã tìm thấy một mẫu hình ổn định hay đã khớp mô hình với nhiễu.

Ba quy tắc của phân tích plateau:

  1. Tính robustness score. Tích của các độ rộng plateau có trọng số cho ra một con số tóm tắt tính bền vững của tất cả các tham số. R>0.1R > 0.1 — đèn xanh.

  2. Sensitivity ratio < 1 cho các tham số quan trọng. Nếu dịch chuyển tham số 10% gây ra mức giảm PnL ít hơn 10% — tham số bền vững. Nếu nhiều hơn — hãy thận trọng.

  3. Trực quan hóa các biểu đồ đường đồng mức. Không có chỉ số nào có thể thay thế sự hiểu biết về hình dạng bối cảnh. Một núi bàn phẳng — tốt. Một mũi kim nhọn — xấu.

Phân tích plateau chỉ mất 5 phút sau khi tối ưu và có thể tiết kiệm vài tuần giao dịch thực tế thua lỗ. Đây là bước bắt buộc giữa study.optimize() và việc khởi chạy bot.


Liên Kết Hữu Ích

  1. Tài liệu Optuna — 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)

Trích Dẫn

@article{soloviov2026plateauanalysis,
  author = {Soloviov, Eugen},
  title = {Plateau Analysis: How to Distinguish a Robust Optimum from Overfitting},
  year = {2026},
  url = {https://marketmaker.cc/vi/blog/post/plateau-analysis-overfitting},
  version = {0.1.0},
  description = {Tại sao tìm ra các tham số chiến lược tốt nhất chỉ là nửa công việc. Cách phân biệt trực quan và định lượng một plateau ổn định với một đỉnh nhọn mong manh, và tại sao các biểu đồ đường đồng mức Optuna là bước bắt buộc trước khi đưa chiến lược đã tối ưu vào production.}
}
Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

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

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.