← กลับไปยังบทความ
March 12, 2026
อ่าน 5 นาที

การวิเคราะห์ Plateau: วิธีแยกแยะจุดที่เหมาะสมที่แข็งแกร่งออกจาก Overfitting

การวิเคราะห์ Plateau: วิธีแยกแยะจุดที่เหมาะสมที่แข็งแกร่งออกจาก Overfitting
#algotrading
#backtest
#การเพิ่มประสิทธิภาพ
#overfitting
#การวิเคราะห์ plateau
#ความเสถียรของพารามิเตอร์

บทความที่ 6 ในซีรีส์ "Backtests Without Illusions"

คุณรัน study.optimize() แล้ว Optuna พบชุดพารามิเตอร์ที่ให้ PnL +87% คุณตื่นเต้นและกำลังเตรียมกลยุทธ์สำหรับการใช้งานจริง สองสัปดาห์หลังจาก live trading PnL อยู่ที่ราวศูนย์ เกิดอะไรขึ้น?

ตัวปรับแต่งค้นพบ ยอดของเข็ม ในพื้นที่พารามิเตอร์ พารามิเตอร์ถูกปรับให้เหมาะสมกับลำดับการเทรดในอดีตอย่างสมบูรณ์แบบ แต่การเบี่ยงเบนเพียงเล็กน้อยในสภาวะตลาดทำลายโครงสร้างทั้งหมด นี่คือ overfitting แบบคลาสสิก และมันสามารถตรวจพบได้ ก่อน การเปิดตัว

ในบทความก่อนหน้า เราเปรียบเทียบ coordinate descent กับ Bayesian optimization และแสดงให้เห็นว่าเหตุใด Optuna จึงค้นหาจุดที่เหมาะสมได้มีประสิทธิภาพมากกว่า วันนี้ — ขั้นตอนถัดไป: วิธีให้แน่ใจว่าจุดที่เหมาะสมที่พบนั้น แข็งแกร่ง ไม่ใช่ผลของการ fitting กับสัญญาณรบกวน

เหตุใดการค้นหาพารามิเตอร์ "ที่ดีที่สุด" จึงเป็นเพียงครึ่งหนึ่งของงาน

การค้นหาในพื้นที่พารามิเตอร์หลายมิติ ตัวปรับแต่งที่นำทางในพื้นที่พารามิเตอร์หลายมิติขนาดใหญ่เพื่อค้นหาจุดที่เหมาะสมที่แท้จริง

การปรับแต่งพารามิเตอร์กลยุทธ์คือการค้นหาค่าสูงสุดในพื้นที่หลายมิติ ปัญหาคือค่าสูงสุดมีสองประเภท:

  1. Plateau — บริเวณราบกว้างที่ PnL สูงอย่างสม่ำเสมอตลอดการเปลี่ยนแปลงพารามิเตอร์ แม้สภาวะตลาดจะเปลี่ยนพารามิเตอร์ที่มีประสิทธิภาพไป 10-20% กลยุทธ์ก็ยังคงทำกำไรได้

  2. ยอดแหลม — จุดยอดแคบที่ PnL สูงเฉพาะที่ค่าพารามิเตอร์ที่แน่นอนเท่านั้น การเลื่อนหนึ่งขั้นทำให้ความสามารถในการทำกำไรพังทลาย นี่เป็น overfitting เกือบแน่นอน: ตัวปรับแต่งพบสิ่งประดิษฐ์ของข้อมูลประวัติ ไม่ใช่รูปแบบที่เสถียร

อุปมาจากการปีนเขา: plateau คือที่ราบสูงบนภูเขาที่คุณสามารถเดินได้อย่างปลอดภัย ยอดแหลมคือปลายเข็มที่คุณสามารถทรงตัวได้เท่านั้น

ยอดแหลมกับ Plateau ราบ — ความเข้าใจเชิงภาพ

การเปรียบเทียบยอดแหลมกับ plateau ราบ ซ้าย: plateau ที่แข็งแกร่ง (ที่ราบสูงบนภูเขากว้างพร้อมความลาดชันอ่อน) ขวา: ยอดแหลมที่เปราะบาง (ปลายเข็มล้อมรอบด้วยหุบเขาลึก)

ลองนึกภาพแผนที่ contour ที่แกนคือพารามิเตอร์กลยุทธ์สองตัวและสีแสดง PnL รูปแบบสองแบบนี้แยกแยะได้ง่ายด้วยสายตา:

Plateau (จุดที่เหมาะสมที่แข็งแกร่ง):

  • บริเวณสีเดียวกันกว้าง
  • การเปลี่ยนผ่านราบรื่นระหว่างระดับ PnL
  • เส้น isoline ห่างกัน
  • การเลื่อนจากจุดที่เหมาะสม +/-20% เปลี่ยน PnL ไม่เกิน 10%

ลองนึกภาพ heatmap: ตรงกลาง — สี่เหลี่ยมสีเหลืองสดใสประมาณหนึ่งในสามของแผนที่ทั้งหมด สีค่อยๆ เปลี่ยนเป็นส้ม จากนั้นเป็นแดงที่ขอบ จุดที่เหมาะสมไม่ใช่จุดหนึ่ง แต่เป็นบริเวณหนึ่ง

ยอดแหลม (overfitting):

  • จุดสว่างแคบล้อมรอบด้วยสีเย็น
  • การเปลี่ยนผ่านฉับพลัน: การล่มสลายอยู่ถัดจากจุดที่เหมาะสม
  • เส้น isoline บีบอัดเป็นวงแหวนแน่น
  • การเลื่อน +/-5% ทำให้ PnL ลดลง 50% หรือมากกว่า

ลองนึกภาพ heatmap เดิม แต่ตรงกลาง — จุดสีเหลืองเล็กๆ ที่ล้อมรอบทันทีด้วยสีน้ำเงินและม่วง การรวมพารามิเตอร์ "ถูกต้อง" เพียงชุดเดียว

การวิเคราะห์ความไวของพารามิเตอร์

กราฟ slice แสดงความไวของพารามิเตอร์ Slice plot แสดงว่า PnL ขึ้นอยู่กับค่าพารามิเตอร์แต่ละตัวอย่างไร — แถบกว้างบ่งบอกถึงความแข็งแกร่ง แถบแคบบ่งบอกถึงความเปราะบาง

การวิเคราะห์หนึ่งมิติ: PnL กับพารามิเตอร์หนึ่งตัว

วิธีที่ง่ายที่สุด — กำหนดพารามิเตอร์ทั้งหมดยกเว้นหนึ่งตัวและดูว่า 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()

สิ่งที่ควรมองหาใน slice plot:

  • พารามิเตอร์ที่แข็งแกร่ง: กลุ่มจุดก่อตัวเป็นแถบแนวนอนกว้างใกล้จุดที่เหมาะสม การทดลองที่ดีที่สุดกระจายตัวในช่วงค่าพารามิเตอร์ที่กว้าง
  • พารามิเตอร์ที่เปราะบาง: การทดลองที่ดีที่สุดกระจุกตัวในช่วงแคบ การเลื่อนพารามิเตอร์หนึ่งหรือสองขั้น — และความสามารถในการทำกำไรพังทลาย

การวิเคราะห์สองมิติ: Contour Plot (Heatmap)

Contour plot แสดงปฏิสัมพันธ์ของพารามิเตอร์สองตัวพร้อมกัน นี่คือเครื่องมือหลักสำหรับการวิเคราะห์ plateau เพราะพารามิเตอร์แทบไม่เคยทำงานอย่างอิสระ — เกณฑ์การเข้าและออก timeframe และขนาด position มีความเชื่อมโยงกัน

from optuna.visualization import plot_contour

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

Contour plot สำหรับคู่พารามิเตอร์ที่แข็งแกร่งดูเหมือนแผนที่ภูมิประเทศของที่ราบเนิน: เส้น isoline กว้างราบรื่น บริเวณสีเดียวกันขนาดใหญ่ Contour plot สำหรับคู่ที่เปราะบาง — เหมือนแผนที่กรวยภูเขาไฟ: วงแหวนศูนย์กลางแน่นรอบจุดเดียว

สำหรับกลยุทธ์ที่มีพารามิเตอร์การแยก 12 ตัว จะได้ (122)=66\binom{12}{2} = 66 contour plot แบบคู่ คุณไม่จำเป็นต้องศึกษาทั้งหมด — เริ่มด้วยพารามิเตอร์ที่ Optuna ประเมินว่าสำคัญที่สุด

การวิเคราะห์หลายมิติ: การจัดลำดับความสำคัญของพารามิเตอร์

Optuna สามารถประเมินการมีส่วนร่วมของพารามิเตอร์แต่ละตัวต่อ objective function:

from optuna.visualization import plot_param_importances

fig = plot_param_importances(study)
fig.show()

กราฟความสำคัญของพารามิเตอร์คือฮิสโตแกรมแนวนอน พารามิเตอร์จัดลำดับตามการมีส่วนร่วมต่อ PnL variance จากมากไปน้อย พารามิเตอร์ 3-4 อันดับแรกมักอธิบาย 70-80% ของ variance

กฎ: ถ้าพารามิเตอร์อธิบาย PnL variance ได้น้อยกว่า 2% ค่าของมันแทบไม่เกี่ยวข้องกับผลลัพธ์ในทางปฏิบัติ — มันแข็งแกร่งโดยนิยาม มุ่งเน้นการวิเคราะห์ plateau ที่ พารามิเตอร์ที่สำคัญที่สุด 5 อันดับแรก

เครื่องมือ Visualization ของ Optuna

Contour plot และ visualization ความสำคัญของพารามิเตอร์ของ Optuna Contour heatmap แสดงพื้นที่ปฏิสัมพันธ์พารามิเตอร์พร้อมการจัดลำดับความสำคัญ

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

ผลลัพธ์ — กริดของ scatter plot แต่ละ subplot แสดงค่า objective function (PnL, แกน Y) เทียบกับค่าพารามิเตอร์เดียว (แกน X) จุดคือการทดลองแต่ละครั้ง สำหรับพารามิเตอร์ที่แข็งแกร่ง จุดที่ดีที่สุด (PnL สูงสุด) กระจายตัวในช่วง X ที่กว้าง สำหรับจุดที่เปราะบาง — จุดกลุ่มอยู่ในคอลัมน์แคบ

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

Contour plot แต่ละอันคือ heatmap ที่มีพารามิเตอร์สองตัวบนแกน สีเข้ารหัส PnL เฉลี่ยในบริเวณที่กำหนดของพื้นที่พารามิเตอร์ เหลือง/เขียว — PnL สูง น้ำเงิน/ม่วง — ต่ำ เส้น isoline เชื่อมต่อจุดที่มี PnL เท่ากัน

plot_param_importances — การมีส่วนร่วมของพารามิเตอร์

from optuna.visualization import plot_param_importances

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

fANOVA (functional ANOVA) แยกส่วน variance ของ objective function ตามพารามิเตอร์และปฏิสัมพันธ์ระหว่างกัน นี่มีประสิทธิภาพมากกว่าความสัมพันธ์แบบง่ายเพราะคำนึงถึงผลกระทบที่ไม่เชิงเส้น

เมตริกเชิงปริมาณของ Plateau

การ visualization เมตริกความแข็งแกร่งเชิงปริมาณ Sensitivity ratio, ความกว้างของ plateau, และ robustness score — สามเมตริกที่กำหนดคุณภาพของ plateau อย่างเป็นทางการ

การประเมินด้วยภาพเป็นเรื่องส่วนตัว เราต้องการ ตัวเลข นี่คือสามเมตริกที่กำหนดแนวคิดของ "plateau" อย่างเป็นทางการ

Sensitivity Ratio

อัตราส่วนของการเปลี่ยนแปลง 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} คือการลดลงของ PnL เมื่อพารามิเตอร์ pip_i เบี่ยงเบนจากจุดที่เหมาะสมโดย Δpi\Delta p_i

การตีความ:

  • Si<0.5S_i < 0.5 — พารามิเตอร์แข็งแกร่ง: การเลื่อน 10% ทำให้ PnL ลดลงน้อยกว่า 5%
  • 0.5Si<2.00.5 \leq S_i < 2.0 — ความไวปานกลาง
  • Si2.0S_i \geq 2.0 — พารามิเตอร์เปราะบาง: การเลื่อน 10% ทำให้ PnL พังทลาย 20%+

ความกว้างของ Plateau

ความกว้างของบริเวณพารามิเตอร์ที่ PnL อยู่ภายใน X%X\% ของจุดที่เหมาะสม:

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}

ความกว้าง plateau แบบสัมพัทธ์:

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 — plateau ครอบคลุมมากกว่า 30% ของช่วงที่เกณฑ์ 10% พารามิเตอร์ที่แข็งแกร่ง
  • Wirel(10%)<0.05W_i^{rel}(10\%) < 0.05 — plateau แคบกว่า 5% ของช่วง สัญญาณเตือน

Robustness Score

เมตริกรวมสำหรับพารามิเตอร์ทั้งหมด:

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

โดยที่ wiw_i คือความสำคัญแบบ normalized ของพารามิเตอร์ ii จาก fANOVA (wi=1\sum w_i = 1)

ผลคูณของความกว้างแบบ weighted เป็นเมตริกที่เข้มงวด: ถ้าแม้แต่พารามิเตอร์ สำคัญ หนึ่งตัวมี plateau แคบ RR จะต่ำ พารามิเตอร์ที่ไม่สำคัญ (ที่มี wiw_i เล็ก) แทบไม่มีผลกระทบ

การตีความ:

  • R>0.1R > 0.1 — กลยุทธ์แข็งแกร่ง
  • 0.01<R0.10.01 < R \leq 0.1 — ต้องการการตรวจสอบเพิ่มเติม (walk-forward)
  • R0.01R \leq 0.01 — overfitting มีความน่าจะเป็นสูงมาก

โค้ด Python สำหรับการตรวจจับ Plateau อัตโนมัติ

Pipeline การตรวจจับ plateau อัตโนมัติ ระบบอัตโนมัติที่สแกนพื้นที่พารามิเตอร์เพื่อระบุ plateau ที่แข็งแกร่งและยอดแหลมที่เปราะบาง

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

การใช้งาน

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%

ตัวอย่างภาคปฏิบัติกับกลยุทธ์การแยก

กลยุทธ์สามแบบเปรียบเทียบตาม robustness profile การเปรียบเทียบกลยุทธ์ A (plateau กว้าง แข็งแกร่ง) กลยุทธ์ B (ปานกลาง) และกลยุทธ์ C (ยอดแหลม overfitted)

ลองตรวจสอบกลยุทธ์สามแบบที่มีพารามิเตอร์การแยก 12 ตัว แต่ละกลยุทธ์ผ่านการปรับแต่ง Optuna ด้วย 500 trials

กลยุทธ์ A (~55% PnL, ~500 trades, ~15% time)

พารามิเตอร์ของกลยุทธ์ A ก่อตัวเป็น plateau กว้าง ลองดูพารามิเตอร์หลัก htf_entry_sell:

  • ค่าที่เหมาะสม: 0.020
  • PnL ที่ 0.015: +51% (ลดลง 7%)
  • PnL ที่ 0.025: +49% (ลดลง 11%)
  • PnL ที่ 0.010: +43% (ลดลง 22%)
  • PnL ที่ 0.030: +41% (ลดลง 25%)

ถ้าคุณนึกภาพสิ่งนี้เป็น plot หนึ่งมิติ (แกน X — ค่า htf_entry_sell, แกน Y — PnL) คุณจะเห็นพาราโบลาอ่อนที่มียอดราบ ช่วง 0.010-0.030 คือ plateau ที่ PnL อยู่ภายใน +/-25% ของจุดที่เหมาะสม

Sensitivity ratio: S=0.110.25=0.44S = \frac{0.11}{0.25} = 0.44 — แข็งแกร่ง

ความกว้าง plateau ที่เกณฑ์ 10%: จาก 0.013 ถึง 0.027, Wrel=0.0140.04=35%W^{rel} = \frac{0.014}{0.04} = 35\%

กลยุทธ์ B (~25% PnL, ~40 trades, ~5% time)

กลยุทธ์ B ถูกปรับแต่งด้วยจำนวน trade น้อย พารามิเตอร์ htf_entry_sell:

  • ค่าที่เหมาะสม: 0.018
  • PnL ที่ 0.015: +24% (ลดลง 4%)
  • PnL ที่ 0.025: +9% (ลดลง 64%)
  • PnL ที่ 0.012: +11% (ลดลง 56%)

บน plot — เส้นโค้งที่ไม่สมมาตรและชัน plateau มีเฉพาะในช่วงแคบ 0.015-0.020 ทางขวาของจุดที่เหมาะสม — มีหน้าผา

Sensitivity ratio: S=0.640.39=1.64S = \frac{0.64}{0.39} = 1.64 — ความไวปานกลาง แต่ด้วย 40 trades นี่คือ สัญญาณเตือน ตัวอย่างเล็ก + plateau แคบ = ความน่าจะเป็น overfitting สูง

ความกว้าง plateau ที่เกณฑ์ 10%: จาก 0.016 ถึง 0.020, Wrel=0.0040.04=10%W^{rel} = \frac{0.004}{0.04} = 10\%

กลยุทธ์ C (~300% PnL, ~400 trades, ~45% time)

กลยุทธ์ C แสดง PnL ที่น่าทึ่ง แต่การวิเคราะห์ plateau เผยให้เห็นปัญหา:

  • ค่าที่เหมาะสมของ htf_entry_sell: 0.022
  • PnL ที่ 0.020: +295% (ลดลง 2%)
  • PnL ที่ 0.025: +142% (ลดลง 53%)
  • PnL ที่ 0.019: +128% (ลดลง 57%)

บน plot — "เข็ม" ที่เป็นลักษณะเฉพาะ: ยอดสูงมากที่ 0.022 ลดลงอย่างชันในทุกทิศทาง Contour plot จะแสดงจุดสว่างที่ล้อมรอบทันทีด้วยสีเย็น

Sensitivity ratio: S=0.530.14=3.79S = \frac{0.53}{0.14} = 3.79เปราะบาง แม้จะมี 400 trades กลยุทธ์ยังขึ้นอยู่กับค่าแน่นอนของพารามิเตอร์เดียวมากเกินไป

ความกว้าง plateau ที่เกณฑ์ 10%: จาก 0.021 ถึง 0.023, Wrel=0.0020.04=5%W^{rel} = \frac{0.002}{0.04} = 5\%

ตารางสรุป

กลยุทธ์ PnL Trades Sensitivity ความกว้าง Plateau Robustness score คำตัดสิน
กลยุทธ์ A +55% ~500 0.44 35% 0.148 แข็งแกร่ง
กลยุทธ์ B +25% ~40 1.64 10% 0.032 ตรวจสอบ (ตัวอย่างเล็ก)
กลยุทธ์ C +300% ~400 3.79 5% 0.008 Overfitting

ความขัดแย้ง: กลยุทธ์ C ที่มี PnL +300% มี robustness score แย่ที่สุด กลยุทธ์ A ที่มี "เพียง" +55% แข็งแกร่งที่สุด นี่เป็นผลลัพธ์ทั่วไปของการวิเคราะห์ plateau: ตัวเลขที่น่าประทับใจมักซ่อนความเปราะบาง

ช่วงความเชื่อมั่นสำหรับแต่ละกลยุทธ์สามารถตรวจสอบเพิ่มเติมผ่าน Monte Carlo bootstrap — จะแสดงการกระจาย PnL เมื่อสุ่มตัวอย่าง trades ใหม่

การ Visualization 3D และ Heatmap

พื้นผิว landscape พารามิเตอร์ 3D พร้อม contour projection Surface plot 3D ของ PnL บนพารามิเตอร์สองตัวพร้อมเส้น contour ที่ฉายบนพื้นระนาบ

สำหรับคู่พารามิเตอร์ที่สำคัญที่สุด การสร้าง 3D surface และ heatmap นั้นมีประโยชน์ ให้ความเข้าใจที่ชัดเจนเกี่ยวกับรูปร่างของ landscape

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

Surface plot 3D สำหรับกลยุทธ์ที่แข็งแกร่งคล้ายที่ราบสูงบนภูเขา — ยอดราบพร้อมความลาดชันอ่อน สำหรับกลยุทธ์ที่เปราะบาง — ยอดแหลม เหมือน Matterhorn Heatmap เสริม 3D view โดยแสดงข้อมูลเดียวกันในการ projection จากด้านบนพร้อมเส้น isoline

สัญญาณเตือน: เมื่อผลการปรับแต่งน่าสงสัย

แดชบอร์ดสัญญาณเตือนสำหรับผลการปรับแต่ง ตัวบ่งชี้คำเตือนที่ส่งสัญญาณถึง overfitting ที่อาจเกิดขึ้นในผลการปรับแต่ง

แปดสัญญาณที่บ่งบอกว่าการปรับแต่งพบ overfitting แทนที่จะเป็นรูปแบบจริง:

1. Sensitivity Ratio > 2 สำหรับพารามิเตอร์หลัก

ถ้า PnL ลดลงมากกว่า 20% เมื่อพารามิเตอร์เลื่อน 10% — จุดที่เหมาะสมเปราะบาง

2. ความกว้าง Plateau < 10% ของช่วงการค้นหา

ถ้าบริเวณ "ดี" ครอบคลุมน้อยกว่า 10% ของช่วงที่สำรวจ — ตัวปรับแต่งส่วนใหญ่พบสิ่งประดิษฐ์

3. 3 Trials อันดับแรกให้ PnL สูงกว่า Median 2-3 เท่า

ถ้า trials ที่ดีที่สุดเป็น outlier จากส่วนที่เหลือ แทนที่จะเป็น "ยอดเขา" — ไม่ใช่ 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. จำนวน Trade น้อย (< 50) กับ PnL สูง

ตัวอย่างเล็ก + PnL สูง = variance สูงในการประเมิน การวิเคราะห์ plateau บน 40 trades นั้นไม่น่าเชื่อถือในตัวเอง สำหรับกลยุทธ์เหล่านี้ Monte Carlo bootstrap เป็นสิ่งสำคัญ

5. การรวมพารามิเตอร์ "มหัศจรรย์" หนึ่งชุด

ถ้า contour plot แสดงจุดสว่างเดียวท่ามกลางสนามสีเทา — นี่ไม่ใช่กลยุทธ์ แต่เป็นการรวมที่ถูก fitting กับข้อมูล

6. พารามิเตอร์มากเกินไป

สำหรับพารามิเตอร์ 12 ตัวที่แต่ละตัวมี 10 ค่า พื้นที่การค้นหามี 101210^{12} การรวม Optuna สำรวจ ~500 ความน่าจะเป็นของการพบ "ดี" สิ่งประดิษฐ์ในพื้นที่ดังกล่าวสูง ยิ่งพารามิเตอร์มาก การวิเคราะห์ plateau ควรเข้มงวดมากขึ้น

7. PnL ลดลงอย่างชันนอกตัวอย่าง

ถ้า PnL ในตัวอย่างคือ +87% และ walk-forward แสดง +12% — การปรับแต่งใส่พารามิเตอร์เข้ากับช่วงการฝึก เรื่องนี้มีรายละเอียดเพิ่มเติมในบทความ Walk-Forward optimization

8. พารามิเตอร์ "ยึด" อยู่ที่ขอบของช่วง

ถ้าค่าที่เหมาะสมตรงกับขอบกริดการค้นหา — จุดที่เหมาะสมอาจอยู่นอกช่วง ขยายช่วงและรันการปรับแต่งใหม่

รายงานการวิเคราะห์ Plateau อัตโนมัติ

รวบรวมทุกอย่างเข้าในรายงานเดียวที่สร้างหลังการปรับแต่งแต่ละครั้ง:

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

ตัวอย่างผลลัพธ์:

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

Walk-forward validation เสริม plateau analysis ความแข็งแกร่งเชิงพารามิเตอร์ (plateau analysis) และความแข็งแกร่งเชิงเวลา (walk-forward) ในฐานะระบบ validation สองระบบที่เสริมกัน

Plateau analysis และ walk-forward validation (WFO) เป็นวิธี ที่เสริมกัน:

  • Plateau analysis ตอบคำถาม: "จุดที่เหมาะสมมีความเสถียรต่อการเปลี่ยนแปลงพารามิเตอร์เล็กน้อยเพียงใด?" นี่คือการตรวจสอบความแข็งแกร่ง เชิงพารามิเตอร์
  • Walk-forward ตอบคำถาม: "พารามิเตอร์ทำงานกับข้อมูลที่ตัวปรับแต่งยังไม่เคยเห็นหรือไม่?" นี่คือการตรวจสอบความแข็งแกร่ง เชิงเวลา

กลยุทธ์สามารถผ่าน plateau analysis (plateau กว้าง) แต่ล้มเหลว walk-forward (ระบอบตลาดเปลี่ยน) และในทางกลับกัน — ผ่าน walk-forward ด้วยพารามิเตอร์คงที่แต่มีจุดที่เหมาะสมเปราะบาง

คำแนะนำ: ใช้ทั้งสองวิธีเสมอ ถ้ากลยุทธ์ผ่าน plateau analysis (R>0.1R > 0.1) และ walk-forward (PnLOOS>50%×PnLIS\text{PnL}_{OOS} > 50\% \times \text{PnL}_{IS}) — นี่เป็นสัญญาณแข็งแกร่งของความแข็งแกร่ง รายละเอียดเพิ่มเติมในบทความ Walk-Forward optimization

เพื่อประเมินช่วงความเชื่อมั่น PnL ในแต่ละขั้นตอน ให้ใช้ Monte Carlo bootstrap และสำหรับการเปรียบเทียบกลยุทธ์ที่มีเวลาใช้งานต่างกันอย่างถูกต้อง ให้ใช้เมตริก PnL per active time

คำแนะนำ

ก่อนการปรับแต่ง

  1. จำกัดจำนวนพารามิเตอร์ ยิ่งพารามิเตอร์น้อย — plateau ยิ่งน่าเชื่อถือ 5-7 พารามิเตอร์คือจำนวนสูงสุดที่สมเหตุสมผล 12 ตัวต้องการความระมัดระวังสูงขึ้นแล้ว

  2. กำหนดช่วงที่มีความหมาย อย่ากำหนด htf_entry_sell ตั้งแต่ 0.001 ถึง 1.0 ถ้าช่วงที่เป็นจริงคือ 0.005 ถึง 0.05 ช่วงที่กว้างโดยไม่จำเป็นสร้างภาพลวงตาของ plateau

  3. ใช้ trials เพียงพอ สำหรับพารามิเตอร์ 12 ตัว ต้องการอย่างน้อย 300-500 trials สำหรับการวิเคราะห์ plateau ที่น่าเชื่อถือ — 1000+

ระหว่างการปรับแต่ง

  1. ติดตาม convergence ถ้า Optuna ยังคงพบผลลัพธ์ที่ดีกว่าอย่างมีนัยสำคัญหลัง 400 trials — กระบวนการยังไม่ converge และการวิเคราะห์ plateau จะไม่น่าเชื่อถือ

  2. ใช้ pruning อย่างระมัดระวัง การ pruning เชิงรุก (MedianPruner) สามารถตัด trials ที่ดูไม่ดีในขั้นตอนแรกแต่สำคัญสำหรับการสร้างภาพ landscape ที่สมบูรณ์

หลังการปรับแต่ง

  1. สร้างรายงาน plateau โดยอัตโนมัติ รวม generate_plateau_report() เข้าใน pipeline การปรับแต่ง อย่าพึ่งพาการประเมินด้วยภาพ — ใช้ตัวเลข

  2. ตรวจสอบพารามิเตอร์ 5 อันดับแรก ถ้า fANOVA แสดงว่าพารามิเตอร์ 3 ตัวอธิบาย 80% ของ variance — พารามิเตอร์ที่เหลือ 9 ตัวสามารถตรวจสอบได้ไม่ละเอียดนัก

  3. เปรียบเทียบกับกลยุทธ์พื้นฐาน ถ้ากลยุทธ์ที่มีพารามิเตอร์ default (ไม่มีการปรับแต่ง) แสดง +30% และกลยุทธ์ที่ปรับแต่งแล้ว +55% — ความแตกต่างเพียง 25 pp และ plateau น่าจะกว้าง ถ้า default แสดง 0% และปรับแต่งแล้ว +300% — ความสามารถในการทำกำไรทั้งหมดขึ้นอยู่กับการ fitting พารามิเตอร์ที่แม่นยำ

  4. การตรวจสอบสุดท้าย — walk-forward Plateau analysis เป็นเงื่อนไขที่จำเป็นแต่ไม่เพียงพอสำหรับความแข็งแกร่ง ตรวจสอบ out-of-sample เสมอ

สรุป

การปรับแต่งพารามิเตอร์เป็นเครื่องมือที่มีประสิทธิภาพ แต่หากไม่มีการวิเคราะห์ plateau มันคือการเล่นรูเล็ต คุณไม่รู้ว่าคุณพบรูปแบบที่เสถียรหรือ fitting โมเดลกับสัญญาณรบกวน

กฎสามข้อของการวิเคราะห์ plateau:

  1. คำนวณ robustness score ผลคูณของความกว้าง plateau แบบ weighted ให้ตัวเลขเดียวที่สรุปความแข็งแกร่งของพารามิเตอร์ทั้งหมด R>0.1R > 0.1 — ไฟเขียว

  2. Sensitivity ratio < 1 สำหรับพารามิเตอร์หลัก ถ้าการเลื่อนพารามิเตอร์ 10% ทำให้ PnL ลดลงน้อยกว่า 10% — พารามิเตอร์แข็งแกร่ง ถ้ามากกว่า — ระมัดระวัง

  3. Visualize contour plot ไม่มีเมตริกใดที่ทดแทนความเข้าใจรูปร่างของ landscape ได้ ที่ราบสูงราบ — ดี เข็มแหลม — ไม่ดี

Plateau analysis ใช้เวลา 5 นาทีหลังการปรับแต่งและสามารถประหยัดสัปดาห์ของการเทรดจริงที่ไม่ทำกำไร มันเป็นขั้นตอนบังคับระหว่าง study.optimize() และการเปิดตัว bot


ลิงก์ที่มีประโยชน์

  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/th/blog/post/plateau-analysis-overfitting},
  version = {0.1.0},
  description = {เหตุใดการค้นหาพารามิเตอร์ที่ดีที่สุดของกลยุทธ์จึงเป็นเพียงครึ่งหนึ่งของงาน วิธีแยกแยะ plateau ที่เสถียรออกจากยอดแหลมที่เปราะบางทั้งในเชิงภาพและเชิงปริมาณ และเหตุใด contour plot ของ Optuna จึงเป็นขั้นตอนบังคับก่อนนำกลยุทธ์ที่ผ่านการปรับแต่งไปใช้งานจริง}
}
ข้อจำกัดความรับผิดชอบ: ข้อมูลที่ให้ไว้ในบทความนี้มีไว้เพื่อการศึกษาและให้ข้อมูลเท่านั้น และไม่ถือเป็นคำแนะนำทางการเงิน การลงทุน หรือการเทรด การเทรดสกุลเงินดิจิทัลมีความเสี่ยงสูงที่จะขาดทุน

ผู้เขียน

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 เฉพาะ การวิเคราะห์ตลาด และการอัปเดตแพลตฟอร์ม

เราเคารพความเป็นส่วนตัวของคุณ ยกเลิกการสมัครได้ทุกเมื่อ