# Parameter Optimization & Overfitting

ใน[บทที่แล้ว](#/chapter-14/ema-strategy) เราใช้ EMA 20/200 กับ SPY แล้วได้ผลตอบแทนสะสม 367.41% ในช่วง 20 ปี คำถามถัดมาคือ ถ้าเปลี่ยน 20 กับ 200 เป็นตัวเลขอื่น ผลจะดีขึ้นไหม บทนี้จะลองทุกคู่ที่เป็นไปได้ในช่วงหนึ่ง หาคู่ที่ดีที่สุด แล้วตรวจว่าคู่ที่ "ดีที่สุด" นั้นดีจริง หรือแค่เข้ากับข้อมูลชุดที่เราใช้หาพอดี

## ทำไมต้องเป็น 20 กับ 200 {#why-optimize}

ตัวเลข 20 และ 200 ไม่ได้มาจากสูตร เราเลือกเพราะคนนิยมใช้ ถ้าเปลี่ยนเป็น 25/180 หรือ 65/120 กลยุทธ์ก็ยังเป็น EMA สั้นตัดยาวเหมือนเดิม ต่างกันแค่ความเร็วในการตอบสนองต่อราคา

ตัวเลขที่เราตั้งเองแบบนี้เรียกว่า **parameter** การปรับหา parameter ที่ทำให้ตัวชี้วัดที่เราเลือกออกมาดีที่สุดเรียกว่า **Parameter Optimization** วิธีที่ตรงที่สุดคือลองทุกคู่ในตาราง (grid search หรือ brute force) หนังสือ *Python for Algorithmic Trading* ของ Yves Hilpisch ทำแบบเดียวกันกับ SMA บน EUR/USD ปี 2010–2019 โดยให้ `optimize_parameters` ไล่ SMA เร็ว 30–50 กับ SMA ช้า 200–300 แล้วได้คู่ 48/238 (หน้า PDF 87–88)

เขียนเป็นสมการได้ว่า เราเลือกคู่ $(n_f^*, n_s^*)$ ที่ทำให้ Sharpe สูงที่สุดใน grid $G$

$$
(n_f^*, n_s^*) = \arg\max_{(n_f,\,n_s) \in G} \text{Sharpe}(n_f, n_s),
\qquad
\text{Sharpe} = \frac{\bar r}{s_r}\sqrt{252}
$$

$n_f$ คือจำนวนวันของ EMA เร็ว $n_s$ คือจำนวนวันของ EMA ช้า $\bar r$ กับ $s_r$ คือค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของผลตอบแทนรายวันหลังค่าธรรมเนียม บทนี้ใช้อัตราปลอดความเสี่ยง 0% เหมือนบทที่ 4

## Parameter Optimization: ลองทุกคู่ {#grid-search}

เราใช้ข้อมูลและกติกาชุดเดียวกับบทที่ 4 ทุกอย่าง คือราคา SPY รายวันจาก Webull OpenAPI ถือ SPY 100% เมื่อ EMA เร็วอยู่เหนือ EMA ช้าที่ราคาปิดวันก่อน ซื้อขายที่ราคาเปิดวันถัดไป และเสียค่าธรรมเนียมสมมติ 0.1% ต่อข้าง สิ่งเดียวที่เปลี่ยนคือคู่ EMA

| สิ่งที่ลอง | ค่า |
| --- | --- |
| EMA เร็ว | 5, 10, 15, …, 100 วัน (20 ค่า) |
| EMA ช้า | 50, 60, 70, …, 300 วัน (26 ค่า) |
| เงื่อนไข | EMA เร็วต้องสั้นกว่า EMA ช้า |
| จำนวนคู่ที่ทดสอบ | 484 คู่ |
| ตัวชี้วัดที่ใช้เลือก | Sharpe รายวัน × √252 |

โค้ดในไฟล์ `lesson_15.py` ทำแค่วนลูปเรียก backtest ของบทที่ 4 ทีละคู่

```python
from lesson_15 import load_bars, run_grid, EVALUATION

bars = load_bars()                      # SPY รายวันจาก Webull ที่ดึงไว้แล้ว
results = run_grid(bars, EVALUATION)    # 484 คู่ ช่วง 2006-09-26 ถึง 2026-09-25
best = max(results, key=lambda r: r['sharpe'])
print(best['fast'], best['slow'], round(best['sharpe'], 2))   # 5 90 0.84
```

เมื่อมองย้อนหลังทั้ง 20 ปี คู่ที่ดีที่สุดคือ EMA 5/90 ได้ Sharpe 0.84 และ CAGR 9.49% ขาดทุนหนักสุด (Max DD) −19.01% ส่วน EMA 20/200 ได้ Sharpe 0.66 อยู่อันดับ 237 จาก 484 คู่ ตรงกลางตารางพอดี ถ้าเราเอา 5/90 ไปโชว์ ตัวเลขจะดูดีกว่าบทที่แล้วมาก แต่เราได้ 5/90 มาจากการเห็นราคาทั้ง 20 ปีก่อนแล้วค่อยเลือก ซึ่งในวันที่เริ่มเทรดจริงเราไม่มีข้อมูลนั้น

::: parameter-heatmap

ลองกดสลับระหว่าง "ช่วงฝึก 2006–2016" กับ "ช่วงทดสอบ 2016–2026" แล้วดูบริเวณสีเข้ม ในช่วงแรก สีเข้มกองอยู่แถว EMA เร็ว 55–90 คู่กับ EMA ช้า 80–130 พอเปลี่ยนเป็นช่วงหลัง สีเข้มย้ายไปอยู่ที่ EMA เร็วสั้น ๆ 10–25 วัน คู่ที่เด่นในสิบปีหนึ่งไม่ได้เด่นในอีกสิบปี

## Overfitting: จำอดีตแทนที่จะเรียนรู้ {#overfitting}

Hilpisch ยกนิยามของ Man Institute ว่า overfitting คือการที่แบบจำลองอธิบาย noise แทนที่จะอธิบาย signal ผลในข้อมูลที่ใช้ทดสอบจึงดี แต่ทำนายข้อมูลใหม่ได้น้อยหรือไม่ได้เลย (หน้า PDF 100) EMA มีแค่สองตัวเลขก็ overfit ได้ เพราะเราลอง 484 คู่แล้วเก็บคู่ที่ออกมาดีที่สุด ยิ่งลองมาก ก็ยิ่งมีโอกาสเจอคู่ที่บังเอิญเข้ากับเหตุการณ์ในอดีตพอดี

อีกคำที่มาคู่กันคือ **data snooping** Hilpisch อ้าง White (2000) ว่าเกิดขึ้นเมื่อเราใช้ข้อมูลชุดเดียวกันมากกว่าหนึ่งครั้งเพื่อสรุปผลหรือเลือกแบบจำลอง Grid search บนข้อมูล 20 ปีชุดเดียวคือการใช้ข้อมูลชุดเดิมซ้ำ 484 ครั้ง

> ![ช่างแคระยืนบนม้านั่งถือไขควง ขณะที่หุ่นกลก้าวเดิน แผ่นเกราะบ่าทองเหลืองที่มีน็อตและหน้าปัดเล็ก ๆ มากมายหลวมจนน็อตกับสปริงกระเด็น บนโต๊ะมีแผ่นเกราะเหล็กเรียบที่มีสายรัดหนังสองเส้น](images/quantara-story-optimization-armor.png)
>
> เรื่องเล่าจากโลกสมมติ Quantara · โรงช่างแห่ง Deltaris
>
> ### เกราะที่พอดีกับท่าเดียว
>
> ช่างใช้เวลาทั้งคืนประกอบบ่าเกราะชิ้นใหม่ให้หุ่นกล เขาขันน็อตสี่สิบกว่าตัวและหมุนหน้าปัดเล็ก ๆ ทีละนิด จนแผ่นทองเหลืองแนบกับไหล่หุ่นไม่มีช่องแม้แต่เส้นผม ตลอดเวลานั้นหุ่นต้องยกแขนค้างไว้ท่าเดียว เพราะช่างบอกว่า "อย่าขยับ เดี๋ยววัดใหม่"
>
> เช้ามาช่างยืนบนม้านั่ง ยิ้มกว้าง "ลองเดินดูสิ" หุ่นลดแขนลงแล้วก้าวไปหนึ่งก้าว น็อตกระเด็นออกมาเป็นสาย สปริงเด้งไปติดชั้นวางเฟือง หุ่นก้มมองไหล่ตัวเองแล้วพูดเบา ๆ ว่า "ข้าต้องยกแขนแบบนี้ไปตลอดชีวิตไหม?"
>
> ช่างเงียบไปครู่หนึ่ง แล้วหันไปมองแผ่นเกราะเรียบ ๆ ที่มีสายรัดแค่สองเส้น ซึ่งวางอยู่บนโต๊ะมาตั้งแต่เมื่อวาน "อันนั้นหลวมไปหน่อย" เขาบ่น หุ่นตอบ "แต่มันไม่หลุดนะ"

## แบ่งข้อมูลเป็นช่วงฝึกกับช่วงทดสอบ {#in-sample-out-of-sample}

วิธีพื้นฐานที่สุดในการจับ overfitting คือแบ่งข้อมูลก่อนเริ่ม optimize ส่วนแรกเรียกว่า **In-sample** หรือช่วงฝึก ใช้หาพารามิเตอร์ ส่วนหลังเรียกว่า **Out-of-sample** หรือช่วงทดสอบ เก็บไว้โดยไม่ดูจนกว่าจะเลือกเสร็จ Hilpisch ใช้การแบ่งแบบนี้กับกลยุทธ์ regression และเขียนไว้ว่ากลยุทธ์ที่มีขั้นตอน optimize หรือ fit ควรประเมินแบบนี้ เพราะช่วยลดปัญหา data snooping และ overfitting (หน้า PDF 118)

บทนี้แบ่ง 20 ปีออกเป็นสองท่อนเท่ากัน

| ช่วง | วันที่ | วันซื้อขาย | ใช้ทำอะไร |
| --- | --- | --- | --- |
| ช่วงฝึก (In-sample) | 2006-09-26 ถึง 2016-09-23 | 2,517 | ลอง 484 คู่ แล้วเลือกคู่ที่ Sharpe สูงสุด |
| ช่วงทดสอบ (Out-of-sample) | 2016-09-26 ถึง 2026-09-25 | 2,514 | นำคู่ที่เลือกแล้วมารันครั้งเดียว ห้ามกลับไปเลือกใหม่ |

แต่ละช่วงเริ่มบัญชีใหม่ด้วยเงินสด 100,000 USD ส่วน EMA คำนวณต่อเนื่องจากราคาก่อนหน้า เส้น EMA วันแรกของช่วงทดสอบจึงใช้ราคาในอดีตได้ตามปกติ เพราะราคาเหล่านั้นเกิดขึ้นแล้วจริง

ในช่วงฝึก คู่ที่ชนะคือ EMA 65/120 ได้ Sharpe 0.72 CAGR 8.59% และ Max DD −17.31% ส่วนการซื้อแล้วถือ SPY ในช่วงเดียวกันได้ Sharpe 0.44 และ Max DD −55.19% เพราะช่วงนี้มีวิกฤตปี 2008 EMA 65/120 ออกจากตลาดได้ก่อนช่วงที่ร่วงหนัก ถ้าหยุดดูแค่ตรงนี้ เราจะสรุปว่าเจอกลยุทธ์ที่ทั้งกำไรมากกว่าและเสี่ยงน้อยกว่าตลาด

::: optimization-equity

พอนำ EMA 65/120 ไปรันในช่วงทดสอบ ผลออกมาดังนี้

| ช่วงทดสอบ 2016–2026 | Sharpe | CAGR | Max DD | อันดับ Sharpe จาก 484 คู่ |
| --- | --- | --- | --- | --- |
| EMA 65/120 (ชนะช่วงฝึก) | 0.77 | 10.42% | −29.36% | 197 |
| EMA 20/200 (ค่าที่ใช้ในบทที่ 4) | 0.83 | 10.93% | −26.67% | 101 |
| ซื้อแล้วถือ SPY | 0.89 | 15.42% | −33.72% | — |

Sharpe ของ EMA 65/120 ในช่วงทดสอบ (0.77) สูงกว่าในช่วงฝึก (0.72) เสียอีก ตัวเลขนี้หลอกตาได้ง่าย เพราะสิบปีหลังเป็นตลาดขาขึ้นเกือบตลอด 475 จาก 484 คู่ได้ Sharpe ในช่วงทดสอบสูงกว่าช่วงฝึก ค่ามัธยฐานของทั้ง 484 คู่ขยับจาก 0.57 ในช่วงฝึกเป็น 0.74 ในช่วงทดสอบ เวลาอ่านผลช่วงทดสอบ ให้เทียบกับทางเลือกอื่นในช่วงเวลาเดียวกัน อย่าเทียบกับตัวเลขของตัวเองในช่วงฝึก

เมื่อเทียบแบบนั้น คู่ที่ชนะช่วงฝึกตกไปอยู่อันดับ 197 แพ้ EMA 20/200 ที่เราไม่ได้ optimize เลย และแพ้การซื้อแล้วถือทั้ง Sharpe และ CAGR ข้อได้เปรียบที่เห็นในช่วงฝึกมาจากการหลบปี 2008 ได้ พอไม่มีวิกฤตแบบเดิม ข้อได้เปรียบนั้นก็หายไป

## อันดับที่ไม่ข้ามช่วงเวลา {#rank-stability}

ถ้าการ optimize ได้ผลจริง คู่ที่อยู่อันดับต้นในช่วงฝึกควรอยู่อันดับต้นในช่วงทดสอบด้วย เราวัดเรื่องนี้ได้ด้วย **Spearman rank correlation** ซึ่งเทียบอันดับของสองชุด ค่า 1 แปลว่าอันดับเหมือนกันทุกตำแหน่ง ค่า 0 แปลว่าไม่สัมพันธ์กัน และค่าติดลบแปลว่าอันดับกลับทิศ

$$
\rho = \operatorname{corr}\big(\operatorname{rank}(\text{Sharpe}_{\text{IS}}),\ \operatorname{rank}(\text{Sharpe}_{\text{OOS}})\big)
$$

สำหรับ 484 คู่ในบทนี้ $\rho = -0.09$ รู้อันดับในช่วงฝึกแล้วก็เดาอันดับในช่วงทดสอบแทบไม่ได้

::: is-oos-scatter

ลองเลือกคู่จาก 10 อันดับแรกของช่วงฝึกทีละคู่ อันดับในช่วงทดสอบกระจายตั้งแต่ 23 ถึง 436 คู่ที่ดีที่สุดในช่วงทดสอบคือ EMA 10/110 ได้ Sharpe 1.08 แต่ในช่วงฝึกมันอยู่อันดับ 315 ส่วน EMA 20/200 อยู่อันดับ 439 ในช่วงฝึก ก่อนขึ้นมาอันดับ 101 ในช่วงทดสอบ

บางคนแนะนำให้เลือกคู่ที่อยู่กลาง "ที่ราบ" ซึ่งคู่ข้างเคียงก็ได้ผลดีใกล้กัน แทนการเลือกยอดแหลมที่ดีอยู่คู่เดียว เราลองแล้วด้วยการเฉลี่ย Sharpe ของแต่ละคู่กับคู่ข้างเคียงรอบตัวในช่วงฝึก คู่ที่ได้คะแนนเฉลี่ยสูงสุดคือ EMA 70/110 แต่ในช่วงทดสอบมันอยู่อันดับ 386 ในข้อมูลชุดนี้ การเลือกที่ราบช่วยไม่ได้ เพราะทั้งที่ราบย้ายที่ไปพร้อมกับสภาพตลาด

## ลองกับตลาดที่ไม่มีสัญญาณ {#noise-test}

ถ้าเรายังไม่แน่ใจว่าผลดีในช่วงฝึกมาจากความบังเอิญได้มากขนาดไหน วิธีหนึ่งคือสร้างตลาดที่เรารู้แน่ว่าไม่มีแนวโน้มให้ EMA จับได้ แล้วทำ optimization แบบเดิม

เราสุ่มหยิบผลตอบแทนรายวันของ SPY ช่วง 2006–2026 มาเรียงใหม่แบบใส่คืน (bootstrap) สร้างเป็นโลกสมมติ 60 โลก โลกละ 2,520 วัน ขนาดการขึ้นลงรายวันยังเหมือน SPY แต่ลำดับเวลาถูกทำลาย แนวโน้มที่ EMA ใช้จึงไม่มีอยู่จริง ในแต่ละโลก เราแบ่งครึ่งแรกเป็นช่วงฝึก ครึ่งหลังเป็นช่วงทดสอบ แล้วลองเลือกคู่ที่ดีที่สุดจาก N คู่

> ![ช่างแคระบนดาดฟ้าริมท่าเรือกอดกังหันลมรูปปลาตัวหนึ่งอย่างดีใจ รอบตัวมีกังหันลมบอกทิศเล็ก ๆ ปักเป็นแถวหลายสิบตัว หุ่นกลกางร่มสีน้ำเงินให้ขณะลมเปลี่ยนทิศ](images/quantara-story-optimization-weathervanes.png)
>
> เรื่องเล่าจากโลกสมมติ Quantara · ดาดฟ้าริมท่าเรือ Deltaris
>
> ### กังหันลมตัวที่โชคดี
>
> ช่างอยากรู้ทิศลมของวันพรุ่งนี้ เขาจึงทำกังหันลมบอกทิศเจ็ดสิบกว่าตัว มีทั้งรูปไก่ รูปเรือ รูปดาว และรูปปลา ปักเรียงเต็มดาดฟ้า แล้วจดทุกเย็นว่าตัวไหนชี้ตรงกับลมจริง
>
> สิ้นสัปดาห์เขาอุ้มกังหันรูปปลาขึ้นมาอย่างภูมิใจ "ตัวนี้ชี้ถูกห้าวันจากเจ็ดวัน!" หุ่นกลมองกังหันตัวอื่นทั้งดาดฟ้าแล้วถามว่า "ถ้าเราทำร้อยตัว จะมีตัวที่ถูกเจ็ดวันไหม?" ช่างยังไม่ทันตอบ ลมก็เปลี่ยนทิศ กังหันทุกตัวหมุนไปคนละทาง รวมถึงตัวปลาด้วย
>
> หุ่นกางร่มบังฝนให้ช่าง ส่วนช่างกอดกังหันรูปปลาไว้แน่นกว่าเดิม

::: noise-trials-viz

เมื่อลองคู่เดียว Sharpe ช่วงฝึกกับช่วงทดสอบใกล้กัน (0.30 กับ 0.31) เพราะไม่มีการเลือก พอเพิ่มเป็นการเลือกจากทั้ง 74 คู่ Sharpe ช่วงฝึกของคู่ที่ชนะเฉลี่ยขึ้นไปถึง 0.57 แต่ช่วงทดสอบอยู่ที่ 0.36 เท่านั้น ส่วนต่าง 0.21 นี้มาจากการเลือกล้วน ๆ เพราะโลกนี้ไม่มีสัญญาณให้เลือก Sharpe ช่วงทดสอบที่ยังเป็นบวกมาจากผลตอบแทนเฉลี่ยของ SPY ที่ติดมากับข้อมูลที่สุ่ม ไม่ได้มาจาก EMA

ผลนี้ใช้อ่านผลกับ SPY จริงได้ด้วย ในช่วงฝึกเราลองไป 484 คู่ มากกว่าในโลกสมมติราวหกเท่าครึ่ง Sharpe 0.72 ของ EMA 65/120 จึงรวมส่วนที่ได้จากการเลือกไว้ด้วย ตัวเลขที่ควรรายงานคู่กับผล optimize จึงมีจำนวนคู่ที่ลองไปด้วย Hilpisch อ้างงานของ Bailey et al. (2015) ไว้ว่า การลองพารามิเตอร์จำนวนมากบนข้อมูลการเงินที่ signal-to-noise ต่ำ มักได้พารามิเตอร์ที่เข้ากับ noise ในอดีต (หน้า PDF 100)

## Walk-forward: เลือกใหม่ทุกปี {#walk-forward}

การแบ่งครั้งเดียวให้ช่วงทดสอบแค่ช่วงเดียว ผลจึงขึ้นกับว่าเราตัดตรงไหน ถ้าตัดปี 2011 แทน 2016 เรื่องราวอาจเปลี่ยน **Walk-forward** แก้เรื่องนี้ด้วยการเลื่อนหน้าต่างไปตามเวลา ซึ่งเป็นแบบ Historical ที่เราพูดถึงใน[บท Introduction of Backtest](#/chapter-13/types-of-backtests)

กติกาในบทนี้คือ ทุกวันที่ 26 กันยายน ตั้งแต่ปี 2011 ถึง 2025 ให้ดู Sharpe ของทั้ง 484 คู่ในห้าปีก่อนหน้า เลือกคู่ที่ดีที่สุด แล้วใช้คู่นั้นเทรดไปหนึ่งปี ปีถัดไปก็เลือกใหม่ รวมเป็น 15 ปีทดสอบที่ไม่มีปีไหนเคยถูกใช้เลือกพารามิเตอร์ของตัวเอง

::: walk-forward-viz

ใน 15 ปี คู่ที่ถูกเลือกเปลี่ยนไป 9 แบบ เริ่มจาก 10/50 ข้ามไป 55/240 และ 55/80 แล้วเป็น 100/270 ต่อด้วย 5/270 จากนั้นกลับมาอยู่ที่ 10/110 สี่ปีติด ถ้าพารามิเตอร์ที่ดีที่สุดมีอยู่จริงและคงที่ เราน่าจะเห็นคู่เดิมหรือคู่ใกล้กันถูกเลือกซ้ำ การที่คำตอบกระโดดไปมาแบบนี้บอกว่าแต่ละรอบ optimization จับลักษณะของห้าปีที่ผ่านมามากกว่าจับกลไกที่ใช้ได้ต่อ

| 2011-09-26 ถึง 2026-09-25 | Sharpe | CAGR | Max DD | เทรด |
| --- | --- | --- | --- | --- |
| Walk-forward เลือกใหม่ทุกปี | 0.78 | 9.33% | −22.46% | 24 |
| EMA 20/200 คงที่ | 0.81 | 10.19% | −26.67% | 10 |
| ซื้อแล้วถือ SPY | 0.95 | 15.54% | −33.72% | — |

Walk-forward ขาดทุนหนักสุดน้อยกว่า EMA 20/200 แต่ Sharpe และ CAGR ต่ำกว่า และเทรดบ่อยกว่าเกินสองเท่า การ optimize ใหม่ทุกปีจึงไม่ได้ให้ผลดีกว่าการใช้คู่คงที่ที่เลือกมาโดยไม่ดูข้อมูลเลย ข้อดีของ walk-forward อยู่ที่ตัวเลขในตารางเป็นผลที่ทำได้จริง ณ เวลานั้น ทุกปีทดสอบใช้พารามิเตอร์ที่เลือกจากข้อมูลก่อนหน้าเท่านั้น จึงบอกเราได้ตรงกว่าตัวเลข 5/90 ที่เลือกหลังเห็นครบ 20 ปี

> ![ช่างแคระนั่งบนถังไม้บนดาดฟ้าเรือ เปิดสมุดบันทึกที่มีรูปเมฆและคลื่น หุ่นกลกำลังปรับเชือกใบเรือ ใบไม้ฤดูใบไม้ร่วงปลิวอยู่บนดาดฟ้า ท่าเรือด้านหลังมีเรือเล็กที่ใบเรือต่างแบบกันจอดเรียงกัน](images/quantara-story-optimization-sails.png)
>
> เรื่องเล่าจากโลกสมมติ Quantara · ท่าเรือ Deltaris
>
> ### ใบเรือของทุกฤดู
>
> ทุกต้นฤดู ช่างจะนั่งบนถังไม้ เปิดสมุดบันทึกของห้าฤดูที่ผ่านมา แล้วบอกหุ่นว่าควรขึงใบเรือตึงแค่ไหน หุ่นปรับเชือกตาม ส่วนเรือที่ใช้ใบแบบฤดูก่อน ๆ ก็ผูกเรียงไว้ที่ท่าเพราะช่างเสียดาย
>
> ฤดูใบไม้ร่วงปีนี้ หุ่นนับเรือที่ท่าได้เก้าลำ "ใบเรือของเราเปลี่ยนทุกปี แต่ลมทะเลเปลี่ยนตามเราหรือเปล่า?" ช่างพลิกสมุดกลับไปหน้าแรก แล้วเขียนเพิ่มหนึ่งบรรทัดว่า "ปีหน้า ลองใช้ใบเดิมดูสักปี"

## ก่อนเชื่อพารามิเตอร์ที่ดีที่สุด {#checklist}

ตัวเลขที่ได้จากการ optimize ต้องอ่านคู่กับวิธีที่ได้มันมา ก่อนเชื่อพารามิเตอร์ชุดไหน ให้ตรวจสิ่งเหล่านี้

1. **แบ่งข้อมูลก่อนเริ่ม** กำหนดช่วงฝึก ช่วงทดสอบ ขอบเขตของ grid และตัวชี้วัดที่ใช้เลือกไว้ล่วงหน้า แล้วเขียนเก็บไว้ ถ้าเปิดดูช่วงทดสอบแล้วกลับไปแก้ grid ช่วงทดสอบนั้นก็กลายเป็นส่วนหนึ่งของการฝึกไปแล้ว
2. **รายงานจำนวนครั้งที่ลอง** Sharpe 0.72 ที่ได้จากการเลือกจาก 484 คู่ มีความหมายต่างจาก Sharpe 0.72 ของกฎที่คิดไว้ก่อนแล้วรันครั้งเดียว
3. **เทียบกับทางเลือกที่ไม่ต้อง optimize** ในบทนี้คือ EMA 20/200 และการซื้อแล้วถือ SPY ในช่วงเวลาเดียวกัน ถ้าคู่ที่ optimize แล้วยังแพ้สองทางเลือกนี้ในช่วงทดสอบ การ optimize ก็ไม่ได้เพิ่มอะไรให้
4. **ดูอันดับ ไม่ใช่แค่ระดับ** ตลาดขาขึ้นทำให้ Sharpe ของทุกคู่สูงขึ้นพร้อมกัน อันดับในช่วงทดสอบบอกได้ดีกว่าว่าการเลือกของเรามีค่าหรือเปล่า
5. **ใช้ walk-forward เมื่อตั้งใจจะ optimize ซ้ำ** ถ้าในการใช้งานจริงเราจะเลือกพารามิเตอร์ใหม่เป็นระยะ backtest ก็ควรทำแบบเดียวกัน และนับต้นทุนจากการเปลี่ยนคู่ด้วย
6. **ลองกับข้อมูลที่ไม่มีสัญญาณ** ถ้ากระบวนการของเราหาผลที่ดูดีได้แม้ในข้อมูลสุ่ม ผลดีบนข้อมูลจริงก็ต้องหักส่วนนั้นออกก่อน

ทุกข้อข้างบนยังอยู่บน SPY ตัวเดียวกับประวัติศาสตร์ 20 ปีชุดเดียว ผลที่ผ่านทุกข้อยังเป็นผลจำลองบนราคาที่ปรับย้อนหลัง ยังไม่รวม spread slippage ภาษี และข้อจำกัดของการส่งคำสั่งจริง

## ลองรันเอง {#practice}

[ดาวน์โหลด Notebook ที่รันแล้ว](downloads/robo-trade-15.ipynb) · [Python example](downloads/lesson_15.py) · [ราคา SPY จาก Webull (CSV)](downloads/performance/data/spy-webull-daily.csv) · [ผลคำนวณ JSON](downloads/optimization-results.json) · [Markdown บทนี้](downloads/content/chapters/15-parameter-optimization-overfitting.md)

`lesson_15.py` ใช้แค่ Python standard library อ่านราคา SPY จากไฟล์ `data/spy-webull-daily.csv` ชุดเดียวกับบทที่ 4 ถ้าดาวน์โหลดแยกไฟล์ ให้วาง CSV ไว้ในโฟลเดอร์ `data` ข้างสคริปต์ ไม่เรียก API ใหม่ รันกี่ครั้งก็ได้ตัวเลขเดิม ใช้เวลาไม่ถึง 10 วินาที

```shell
python3 lesson_15.py --output-dir output
```

ลองแก้แล้วรันใหม่ทีละอย่าง

1. เปลี่ยน `IN_SAMPLE` และ `OUT_OF_SAMPLE` ให้แบ่งที่ปี 2011 หรือ 2021 คู่ที่ชนะช่วงฝึกเปลี่ยนไปไหม และอันดับในช่วงทดสอบเป็นอย่างไร
2. ใน `build_results()` เปลี่ยน `best(ins)` เป็น `best(ins, 'cagr')` เพื่อเลือกด้วย CAGR แทน Sharpe คู่ที่ได้ต่างจากเดิมแค่ไหน
3. เพิ่ม `FEE` เป็น 0.002 แล้วดูว่าคู่ที่ EMA เร็วสั้น ๆ อย่าง 5/90 ซึ่งเทรด 46 ครั้งใน 20 ปี เสียเปรียบขึ้นเท่าไร
4. เปลี่ยน `WALK_TRAIN_YEARS` จาก 5 เป็น 3 หรือ 8 คู่ที่ถูกเลือกกระโดดน้อยลงหรือมากขึ้น

::: quiz

## แหล่งข้อมูลและขอบเขต {#sources}

1. **Yves Hilpisch, *Python for Algorithmic Trading: From Idea to Cloud Deployment*, O'Reilly, 2020.** ใช้ไฟล์ `python for algorithmic trading book (js).pdf` ที่แนบมา หน้า PDF 87–88 เรื่อง `optimize_parameters` แบบ brute force กับ SMA บน EUR/USD, หน้า 100–101 เรื่อง Data Snooping and Overfitting และหน้า 118 เรื่องการ fit ในช่วง in-sample แล้วประเมินใน out-of-sample เลขหน้าเป็นลำดับหน้า PDF เริ่มที่ 1
2. White (2000) *A Reality Check for Data Snooping*, Bailey, Borwein, López de Prado และ Zhu (2015) *The Probability of Backtest Overfitting* และบทความเรื่อง overfitting ของ Man Institute อ้างผ่านหนังสือของ Hilpisch หน้า PDF 100–101 เท่านั้น เรายังไม่ได้อ่านต้นฉบับของงานเหล่านี้
3. [Webull Thailand · List Stock Historical Bars](https://developer.webull.co.th/apis/docs/reference/trade-api/historical-bars) ที่มาของราคา SPY รายวัน ดึงเมื่อ 28 กันยายน 2026 ตามรายละเอียดและ checksum ในบทที่ 4 บทนี้ใช้ไฟล์เดิมโดยไม่ดึงใหม่
4. โครงหัวข้อของคอร์ส [Foundations of Alpha: The Theory Behind Algorithmic Trading](https://www.udemy.com/course/foundations-of-alpha-the-theory-behind-algorithmic-trading/) (Hudson and Thames) ส่วน Research และ Risks in Models และคอร์ส [Algorithmic Trading A-Z with Python, Machine Learning & AWS](https://www.udemy.com/course/algorithmic-trading-with-python-and-machine-learning/) ส่วน Finding the optimal SMA Strategy, Overfitting และ Out-Sample Forward Testing ใช้เป็นแนวในการเรียงเนื้อหา ตรวจจากหน้าแนะนำคอร์สเมื่อ 29 กันยายน 2026 ไม่ได้ใช้เนื้อหาในวิดีโอ

การทดลองทั้งหมดในบทนี้ ทั้ง grid 484 คู่ การแบ่งช่วง ตลาดสุ่ม 60 โลก และ walk-forward เป็นการออกแบบใหม่สำหรับบทนี้ ตัวเลขทุกตัวคำนวณจาก `lesson_15.py` ภาพเรื่องเล่า Quantara สร้างด้วย Codex CLI โดยใช้ภาพเรื่องเล่าของบทที่ 3 เป็นต้นแบบสไตล์ เป็นภาพประกอบเรื่องสมมติ ไม่ได้แสดงข้อมูลใด ๆ
