# Introduction of Backtest

ผมสามารถหา Strategy ที่มีกำไรได้เสมอ และ Backtest ที่ดูดีแทบไม่บออกอะไรอณาคตเลย  แล้วเหตุใดเราถึงต้องทำ Back test ในบทนี้เราจะมาทำความเข้าใจการใช้งาน Backtest  ข้อจำกัด และประโยชน์ของมัน 

## Backtest ตอบคำถามอะไร {#what-is-backtest}

Backtest คือการนำกฎซื้อขายและบริหารความเสี่ยงมาจำลองกับข้อมูลในอดีต เพื่อศึกษาพฤติกรรมของกลยุทธ์  เราต้องกำหนดด้วยว่าจะซื้อขายเมื่อไร ใช้ราคาใด และเสียต้นทุนเท่าไร แต่สิ่งที่ผู้คนเข้าใจผิดคือผลยุทธ์ที่ได้ผล  Backtest ที่ดีจะเป็นกลยุทธ์ที่ดี 
Backtest คือการทดสอบกลยุทธ์กับข้อมูลอดีต ไม่ใช่หลักฐานว่าผลลัพธ์ในอนาคตจะเหมือนเดิม ปัญหาหลักคือข้อมูลอดีต 

"ผมสามารถหากลยุทธ์ที่ดีในอดีตได้เสนอ แต่ในอนาคตไม่แน่"

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


## หลักคิด: กระบวนการวิจัย {#research-before-backtest}

> Backtesting is not a research tool.  If a strategy does not perform well in a backtest, do not tweak it (overfit) until the backtest looks good.  Instead, investigate how the research process misled you into backtesting a false strategy. Fix the research process, not the strat.

Backtesting มีความสำคัญมากในกระบวนการพัฒนาและประเมิน กลยุทธ์การลงทุนเพื่อให้เราเข้าใจถึงผลตอบแทนและความเสี่ยงสำหรับการเพิ่มประสิทธิภาพและสามารถปรับปรุงความแม่นยำของกลยุทธ์หรือแบบจำลองได้โดยการระบุจุดอ่อนหรืออคติที่อาจส่งผลให้ประสิทธิภาพไม่ดีในอดีต

อย่างไรก็ตาม Backtesting ไม่ใช่ Academic Research Tool เราทำการ Backtest เพื่อเข้าใจพฤติกรรมของกลยุทธ์เท่านั้น เราไม่ได้ใช้การ Backtes เพื่อทดสอบสมมุติฐานของกลยุทธ์ของเรา เรายังต้องพึงเครื่องอื่นๆ เช่นการทดสอบความเชื่อมั่น ทฤษฏีหรือแนวคิดที่ใช้ทดสอบกลยุทธ์ของเรา 

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

เมื่อเราใช้ผลชุดทดสอบกลับไปเลือกกฎ ชุดนั้นก็เป็นส่วนหนึ่งของการพัฒนาแล้ว การทดสอบกฎที่แก้ใหม่กับข้อมูลเดิมจึงยังบอกไม่ได้ว่าจะได้ผลกับข้อมูลที่ไม่เคยเห็น

## Backtest สามประเภท กับเครื่องมือสองแบบ {#types-of-backtests}

เราแบ่ง backtest ได้ทั้งตามข้อมูลที่นำมาทดสอบ และตามกลไกของการจำลอง

### แกนที่หนึ่ง: เราทดสอบกับเส้นทางแบบใด

| ประเภทตาม The Three Types of Backtests | วิธีคิด | สิ่งที่ยังตอบไม่ได้ |
| --- | --- | --- |
| Historical / walk-forward | ใช้ข้อมูลในอดีตจริง| ประวัติศาสตร์มีเส้นทางเดียว สิ่งที่เคยได้ผลอาจไม่เกิดซ้ำ |
| Resampling | นำข้อมูลในอดีตจริงเดิมมาจัดชุดใหม่ เช่น bootstrap หรือ cross-validation เพื่อดูผลหลายชุด | ต้องออกแบบให้เคารพความสัมพันธ์ตามเวลาและข้อมูลที่ทับซ้อน ไม่ใช่สุ่มสลับแถวได้เสมอ |
| Monte Carlo | จำลองเส้นทางจากกระบวนการกำเนิดข้อมูลที่สมมติขึ้น | ผลผูกกับความสมเหตุสมผลของแบบจำลอง จำนวนเส้นทางมากขึ้นไม่ได้พิสูจน์ว่าแบบจำลองตลาดถูกต้อง |


### Historical / walk-forward: เลื่อนช่วงฝึกและทดสอบไปตามเวลา

วิธีนี้ใช้ข้อมูลในอดีตจริง ภาพด้านล่างแสดงตัวอย่าง sliding window: แต่ละรอบใช้ข้อมูล 3 ช่วงก่อนหน้าเพื่อฝึก แล้วทดสอบกับ 1 ช่วงถัดไป จากนั้นเลื่อนหน้าต่างไปข้างหน้า ทุกแถวยังอยู่บนประวัติศาสตร์ชุดเดียว 

![Historical / walk-forward: เจ็ดรอบของหน้าต่างฝึกสามช่วง ตามด้วยช่วงทดสอบหนึ่งช่วง เลื่อนไปตามเวลา](images/backtest-type-historical.svg)

สีม่วงคือช่วงฝึก ส่วนสีเขียวคือช่วงทดสอบ การกำหนดกฎและขั้นตอนปรับโมเดลควรทำไว้ก่อน การย้อนกลับไปแก้กฎหลังดูผลช่วงทดสอบทำให้ช่วงนั้นมีส่วนในการพัฒนาแล้ว

### Resampling: จัดชุดใหม่จากข้อมูลเดิม

ภาพนี้แบ่งผลตอบแทนสมมติ 12 ช่วงเป็นบล็อก A–D บล็อกละ 3 ช่วง แล้วหยิบบล็อกมาเรียงเป็นสามชุด โดยหยิบซ้ำได้ เช่น ชุดใหม่ 1 มี D สองครั้ง ส่วนชุดใหม่ 2 มี C สองครั้ง ลำดับข้อมูลภายในแต่ละบล็อกยังเหมือนเดิม

![Resampling: ข้อมูลเดิมแบ่งเป็นบล็อก A B C D จัดใหม่เป็น BDDA CABC และ ADCB พร้อมตัวอย่างช่วงฝึกและทดสอบในแต่ละชุด](images/backtest-type-resampling.svg)

แถบใต้แต่ละชุดแสดงตัวอย่างการแบ่งช่วงฝึกและทดสอบบนลำดับที่จัดใหม่ เส้นเวลาตรงนี้จึงเป็นลำดับของชุด resample ไม่ใช่ประวัติศาสตร์จริงอีกเส้นทาง รอยต่อระหว่างบล็อกเปลี่ยนไป และการแบ่งช่วงเพียงอย่างเดียวไม่ได้รับประกันว่าไม่มีข้อมูลรั่ว โดยเฉพาะเมื่อข้อมูลต้นทางเดียวกันถูกหยิบซ้ำมาอยู่ทั้งช่วงฝึกและทดสอบ

นี่เป็นตัวอย่าง block bootstrap ส่วน cross-validation ใช้แผนแบ่งข้อมูลเพื่อพัฒนาและประเมิน ไม่จำเป็นต้องหยิบบล็อกซ้ำอย่างในภาพ ทั้งสองวิธีต้องออกแบบให้เหมาะกับความสัมพันธ์ตามเวลาและข้อมูลที่ทับซ้อน 

### Monte Carlo: สร้างแบบจำลองจากสมมติ
Monte Carlo Backtesting หรือ Monte Carlo Simulation ในการทดสอบกลยุทธ์ คือกระบวนการทางสถิติที่นำผลลัพธ์จากการทดสอบย้อนหลัง (Backtest) มาสุ่มสลับลำดับหรือจำลองซ้ำเป็นพัน ๆ ครั้ง เพื่อประเมินความเสี่ยงและหาความเป็นไปได้ของผลตอบแทนในอนาคตภายใต้ความไม่แน่นอน

ดูเหมือนว่าเครื่องมือนี้ถูกออกแบบมาเพื่อ 'คาดการณ์' ความผันผวนของมูลค่าพอร์ต (equity) ที่อาจเกิดขึ้นเปรียบเทียบกันระหว่างกลยุทธ์ต่างๆ (เช่น กลยุทธ์นั้นล้มเหลวบ่อยแค่ไหน มีโอกาสเกิดผลกำไรต่อเนื่องกันมากน้อยเพียงใด และมีค่าเฉลี่ยของช่วงที่พอร์ตติดลบหรือ Drawdown เป็นอย่างไร) นี่คือความเข้าใจของผมเกี่ยวกับวิธีการใช้งานเครื่องมือนี้ กล่าวคือ มีการตั้งสมมติฐานเกี่ยวกับกลยุทธ์ไว้มากมาย (โดยอาศัยข้อมูลจากการทดสอบย้อนหลังหรือ backtesting) จากนั้นจึงนำกลยุทธ์ทั้งหมดมาเปรียบเทียบกันเพื่อเก็บข้อมูลของแต่ละกลยุทธ์

โดยพื้นฐานแล้ว วิธีการแบบ Monte Carlo นั้นถูกออกแบบมาเพื่อเปรียบเทียบกระบวนการสุ่ม (stochastic processes) รวมถึงตัวแปรสุ่มและฟังก์ชันการแจกแจงความน่าจะเป็นเพื่อทดสอบกลยุทธ์ทั้งหมดเพื่อหาค่าคาดหวังของผลกำไร (gain expectancy หรือ expectancy ratio) หรือที่เรียกกันว่า "สมการของเทรดเดอร์" (trader's equation)

จากนั้น เมื่อทราบแล้วว่ากลยุทธ์ใดให้ผลลัพธ์ที่ดี (จากการทดสอบย้อนหลัง) ก็จะใช้วิธี Monte Carlo เพื่อจำลองผลลัพธ์การเทรดที่อาจเกิดขึ้น โดยตั้งสมมติฐานว่ากลยุทธ์เหล่านั้นจะทำงานได้เหมือนกับที่เห็นในการทดสอบย้อนหลัง และสมมติฐานรวมถึงการคำนวณทั้งหมดของคุณนั้นถูกต้องแม่นยำ ตลอดจนเชื่อมั่นว่ากลยุทธ์ดังกล่าวจะยังคงใช้ได้ผลดีในสถานการณ์ตลาดจริงในอนาคต

ตัวอย่างนี้เริ่มดัชนีราคาที่ 100 แล้วสมมติว่าแต่ละช่วงขึ้น 2% หรือลง 2% ด้วยโอกาสเท่ากัน และแต่ละช่วงเป็นอิสระต่อกัน เมื่อสุ่มซ้ำได้ 100 เส้นทาง เส้นละ 12 ช่วง เราจึงเห็นผลหลายแบบจากกฎเดียวกัน บางเส้นซ้อนกันเพราะแต่ละช่วงมีผลตอบแทนให้เลือกเพียงสองค่า

![Monte Carlo: เส้นทางราคาสมมติ 100 เส้น เริ่มที่ 100 และสุ่มขึ้นหรือลง 2 เปอร์เซ็นต์ต่อช่วง](images/backtest-type-monte-carlo.svg)

หากเพิ่มจำนวนเส้นทาง เราจะเห็นผลจากกฎนี้มากขึ้น ส่วนข้อสมมติว่าราคาขึ้นลง 2% และแต่ละช่วงเป็นอิสระ ยังต้องตรวจแยกว่าตรงกับตลาดที่ต้องการศึกษาหรือไม่ [2, หน้า PDF 3–5]

[ภาพ Historical (PNG)](images/backtest-type-historical.png) · [ภาพ Resampling (PNG)](images/backtest-type-resampling.png) · [ภาพ Monte Carlo (PNG)](images/backtest-type-monte-carlo.png)

### แกนที่สอง: กลไกการเขียน code 

**Vectorized backtest** คำนวณสัญญาณ สถานะ และผลตอบแทนเป็นชุดด้วยตารางหรือ array เหมาะกับกฎที่เขียนความสัมพันธ์ตามเวลาได้ชัดเจน ใส่ต้นทุนตามมูลค่าซื้อขายได้ เช่น ตัวอย่างในหนังสือ [1, หน้า PDF 75–76, 94–95]

**Event-based หรือ event-driven backtest** ประมวลผลเหตุการณ์ตามลำดับ เช่น ข้อมูลมาถึง เกิดสัญญาณ ส่งคำสั่ง จับคู่ และอัปเดตเงินสด/หุ้น เหมาะเมื่อการทำงานขึ้นกับสถานะสะสมหรือรายละเอียดคำสั่ง แต่ความสมจริงยังขึ้นกับแบบจำลองการจับคู่ สภาพคล่อง และข้อจำกัดที่ใส่ไว้ [1, หน้า PDF 148–152]

สองมิตินี้ใช้ร่วมกันได้ เช่น ทดสอบข้อมูล historical ด้วยเครื่องจำลอง event-based การเปลี่ยนสถาปัตยกรรมอย่างเดียวไม่ทำให้ bias หายไป

## ออกแบบการทดสอบ {#test-design}

เริ่มจากคำอธิบายที่หักล้างได้ เช่น “กฎนี้คาดว่าจะได้ประโยชน์จากแนวโน้มที่ต่อเนื่อง หลังรวมต้นทุนที่กำหนด” แล้วเขียนล่วงหน้าว่าข้อมูล สัญญาณ การถือสถานะ และหลักฐานแบบใดจะทำให้ไม่เชื่อสมมติฐานนี้

**Split window** แบ่งข้อมูลตามเวลา: ชุด A ใช้พัฒนา ส่วนชุด B ที่ตามมาทีหลังใช้ประเมินกฎที่กำหนดไว้แล้ว **Sliding window** เลื่อนช่วงฝึกและทดสอบไปข้างหน้า แต่ละรอบต้องเรียนรู้จากข้อมูลก่อนช่วงที่ประเมินเท่านั้น การปรับโมเดลตามตารางที่กำหนดไว้ล่วงหน้าเป็นคนละเรื่องกับการย้อนแก้กฎหลังเห็นคำตอบ 

ในงานที่มีการเลือกพารามิเตอร์ อาจแยก train สำหรับเรียนรู้, validation สำหรับเลือกแบบจำลอง และ final test สำหรับตรวจท้ายสุด รายการที่ต้อง fit เช่น scaler หรือการเลือกตัวแปร ต้องอยู่ในข้อมูลฝึกของรอบนั้นด้วย หากดู final test แล้วปรับกฎ ชุดนั้นถูกใช้พัฒนาไปแล้ว 
ก่อนรัน ให้บันทึกอย่างน้อย:

1. เหตุผลของกลยุทธ์ กฎเข้า/ออก และวิธีวัดว่าผ่านหรือไม่ผ่าน
2. สินทรัพย์ ช่วงข้อมูล ความถี่ เขตเวลา และวิธีจัดการข้อมูลหายหรือแก้ย้อนหลัง
3. เวลาที่รู้ข้อมูล เวลาส่งคำสั่ง ราคา fill และข้อจำกัดการถือสถานะ
4. ต้นทุน เกณฑ์เปรียบเทียบ จำนวนการทดลอง และวิธีเก็บผลที่ไม่ผ่านด้วย

การปรับพารามิเตอร์ระหว่างพัฒนา ส่วน protocol กำหนดให้แยกผลจากข้อมูลพัฒนาออกจากผลบนข้อมูลที่ยังไม่เคยเห็น 

## จาก Signal ไปสู่ Position {#signal-and-position}

**Signal** คือสัญญาณซื้อขายจากกฎ เช่น อยากถือหุ้นหรืออยากถือเงินสด ส่วน **position** คือสถานะที่มีอยู่จริงและรับผลตอบแทนในช่วงนั้น ระหว่างสองสิ่งนี้ยังมีการส่งคำสั่งและการจับคู่ หรือ **fill**

แยกการตัดสินใจเป็นสองส่วนได้:

- **Side** บอกทิศทาง ในบทนี้ใช้ $s_t \in \{0,1\}$ คือเงินสดหรือ long
- **Size** บอกขนาดของเงิน  $a_t \in [0,1]$ เป็นสัดส่วนพอร์ต

น้ำหนักเป้าหมายจึงเป็น $w_t=s_t a_t$ สัญญาณเหมือนกันไม่ได้แปลว่าความเสี่ยงเหมือนกัน หากกลยุทธ์หนึ่งลงเงิน 100% แต่อีกกลยุทธ์ลงเพียง 40%

ตัวอย่างสมมติ: ก่อนเริ่มช่วงราคา 100 → 110 เราถือเงินสด ผลจากหุ้นของพอร์ตจึงเป็นศูนย์ ต่อให้เมื่อสิ้นช่วง กฎเพิ่งให้สัญญาณซื้อ การนำสัญญาณใหม่นั้นไปคูณผลตอบแทน +10% ที่ผ่านไปแล้วก็คือการให้ระบบย้อนเวลา

ในทางกลับกัน หากถือ short น้ำหนัก −1 ก่อนช่วงเดียวกัน ผลตอบแทนจากหุ้นจะเป็น −10% ก่อนต้นทุน ไม่ใช่กำไรเพราะสัญญาณตอนท้ายเพิ่งเปลี่ยนเป็น long ตัวอย่าง short นี้ใช้สอนบัญชีผลตอบแทนเท่านั้น ห้องทดลองด้านล่างไม่เปิด short

## Look-ahead bias: รู้คำตอบเร็วเกินไป {#look-ahead}

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

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

**Look-ahead bias** เกิดเมื่อการตัดสินใจในอดีตใช้ข้อมูลที่ยังไม่พร้อม ณ เวลานั้น ไม่ว่าจะมาจากราคาปิดของวันเดียวกัน งบที่ยังไม่ประกาศ หรือสถิติที่คำนวณจากข้อมูลทั้งชุด [2, หน้า PDF 7; 4, หน้า PDF 16]

ในบทนี้กำหนดลำดับเวลาให้ชัดเจน:

| เวลา | สิ่งที่ทำได้ในตัวอย่าง |
| --- | --- |
| หลังตลาดปิด session $t-1$ | ใช้ราคาปิดถึง $t-1$ คำนวณ SMA สัญญาณ และความผันผวน |
| ราคาเปิด session $t$ | สมมติว่าคำสั่งที่เตรียมไว้จับคู่ได้ที่ราคาเปิด แล้วปรับน้ำหนักพอร์ต |
| ราคาเปิด session $t+1$ | วัดผลจากช่วงเปิดถึงเปิด และเตรียมรอบถัดไป |

ให้ $O_t$ คือราคาเปิดของ session $t$ ผลตอบแทนของสินทรัพย์ในช่วงถือครองคือ

$$
R_{t\to t+1}=\frac{O_{t+1}}{O_t}-1.
$$

น้ำหนักที่ใช้รับผลตอบแทนนี้ต้องอาศัยข้อมูลที่พร้อมก่อนเปิด $t$ ในกรณีปกติของเรา จึงใช้สัญญาณจากราคาปิด $t-1$ ส่วนกรณีผิดในห้องทดลองจะใช้ราคาปิด $t$ เพื่อซื้อที่ราคาเปิด $t$ ซึ่งยังรู้ไม่ได้

ใน pandas มักเห็น `signal.shift(1)` แต่ต้องอ่านความหมายของ index และผลตอบแทนก่อน ตัวอย่างโครงสร้างสำหรับ convention ข้างต้นคือ:

```python
# close and open are aligned by session index.
side_at_close = (close.rolling(20).mean() > close.rolling(50).mean()).astype(float)
weight_at_open = side_at_close.shift(1)
open_to_next_open = open.shift(-1) / open - 1
# Gross return only: warm-up, sizing, costs and liquidation are handled separately.
gross = weight_at_open * open_to_next_open
```

แม้เลื่อนสัญญาณหนึ่งแถวแล้ว หากใช้ volatility ของอนาคตในการกำหนด size ก็ยังมีข้อมูลอนาคตรั่วเข้ามา และแม้ใช้ข้อมูลทันเวลา สมมติฐานว่า fill ได้ที่ราคาเปิดยังต้องตรวจในระบบซื้อขายจริง ทั้งเรื่องเวลาตัดรับคำสั่ง สภาพคล่อง และ slippage

### ลองเปิดข้อมูลตามเวลาที่รู้ได้

แบบจำลองย่อนี้ใช้กฎ **ราคาปิดล่าสุดมากกว่า 100 ให้ Long มิฉะนั้นถือเงินสด** เพื่อให้เห็นเรื่องเวลาอย่างเดียว จึงแยกจากกฎ SMA 20/50 ในห้องทดลองใหญ่ ลองเดินจากตอนเปิด session 1 ไปจนถึงเปิด session 2 แล้วดูว่าข้อมูลใดเพิ่งปรากฏ และสถานะใดรับผลตอบแทนไปแล้ว

::: timing-viz

## จากผลตอบแทนสินทรัพย์สู่มูลค่าพอร์ต {#portfolio-accounting}

สำหรับพอร์ต long กับเงินสดที่ให้ดอกเบี้ยศูนย์ หากน้ำหนักหุ้นหลังปรับพอร์ตคือ $w_t$ และยังไม่คิดต้นทุน ผลตอบแทนพอร์ตในหนึ่งช่วงคือ $w_tR_{t\to t+1}$ สูตรนี้ใช้ simple return หรือผลตอบแทนเทียบกับราคาเริ่มต้นของช่วง

เมื่อต้องเสียค่าใช้จ่าย ให้คิดสองขั้นตอน:

$$
V_t^{+}=V_t^{-}-C_t,
\qquad
V_{t+1}^{-}=V_t^{+}\left(1+w_tR_{t\to t+1}\right).
$$

$V_t^{-}$ คือมูลค่าก่อนซื้อขาย, $C_t$ คือต้นทุน, $V_t^{+}$ คือมูลค่าหลังจ่ายต้นทุน และ $w_t$ คือสัดส่วนหุ้นของมูลค่าหลังจ่ายต้นทุนนั้น

**Turnover** คือมูลค่าที่ซื้อขายเทียบกับมูลค่าพอร์ต ห้องทดลองคิดต้นทุนตาม notional ที่ซื้อขายจริงในแบบจำลอง รวมการซื้อครั้งแรก การปรับน้ำหนัก และการขายออกตอนจบ โดยคำนึงว่าน้ำหนักหุ้นเคลื่อนตามราคา แม้ target weight จะเท่าเดิม

หน่วยต้นทุนใช้ **basis point (bp)**: 1 bp = 0.01% ดังนั้น 5 bps = 0.05% ต่อมูลค่าซื้อขาย ไม่ใช่ 5% และไม่ใช่ค่าธรรมเนียมคงที่ต่อวัน

หากซื้อขายมูลค่า 10,000 หน่วยด้วยอัตรา 5 bps ต้นทุนเท่ากับ 5 หน่วย การกลับจาก short เต็มพอร์ตเป็น long เต็มพอร์ตมี notional โดยประมาณสองเท่าของการเปลี่ยนจากเงินสดเป็น long จึงไม่ควรนับเป็นต้นทุนเพียง “หนึ่งสัญญาณ”

อัตราในห้องทดลองเป็นสมมติฐานรวมแบบย่อ ไม่ใช่อัตราค่าธรรมเนียมของโบรกเกอร์ใด แบบจำลองนี้ยังไม่จำลอง market impact, partial fill, fixed commission หรือข้อจำกัดสภาพคล่อง จึงใช้ตรวจกลไกเบื้องต้น ไม่ใช่ execution simulator เต็มรูปแบบ [1, หน้า PDF 94–95, 151–152; 2, หน้า PDF 7–8]

## Volatility targeting: คุมขนาดความเสี่ยง {#volatility-targeting}

**Volatility targeting** คือการปรับขนาดสถานะตามความผันผวนที่ประเมินได้ เช่น เมื่อสินทรัพย์แกว่งแรงขึ้น ใช้เงินกับสถานะเดิมน้อยลง แนวคิดนี้แยกจากสัญญาณว่าจะซื้อหรือไม่ซื้อ

หาก $\sigma^*$ คือเป้าความผันผวนต่อปี และ $\widehat\sigma_{t-1}$ คือความผันผวนที่ประมาณจากข้อมูลถึงวันก่อนหน้า เรากำหนดขนาดอย่างง่ายเป็น

$$
a_t=\min\left(1,\frac{\sigma^*}{\widehat\sigma_{t-1}}\right),
\qquad w_t=s_{t-1}a_t.
$$

เพดาน 1 หมายถึงไม่ใช้ leverage ถ้าเป้าคือ 10% ต่อปี แต่สินทรัพย์มี estimated volatility 20% จะถือประมาณ 50% ของพอร์ตเมื่อ signal เป็น long หาก estimated volatility เป็น 5% สูตรดิบให้ 200% แต่เพดานในบทนี้จำกัดเหลือ 100%

ห้องทดลองใช้ sample standard deviation ของผลตอบแทนปิดถึงปิดย้อนหลัง 20 ช่วง แล้วคูณ $\sqrt{252}$ เพื่อแปลงเป็นรายปี การคำนวณทุกครั้งใช้หน้าต่างย้อนหลังของจุดเวลานั้น เริ่มประเมินหลังมีข้อมูลครบ และหาก volatility เป็นศูนย์จะไม่เปิดสถานะในโหมด targeting

เป้าความผันผวนไม่ใช่ drawdown limit และไม่รับประกันว่า volatility ที่เกิดจริงจะตรงเป้า โดยเฉพาะเมื่อพอร์ตสลับถือเงินสด มีเพดานน้ำหนัก หรือความผันผวนเปลี่ยนเร็ว การ targeting อาจลดทั้งกำไรและขาดทุน และเพิ่ม turnover จากการปรับ size แม้ side ไม่เปลี่ยน [5; 3, หน้า 10]

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

ลองคงสัญญาณ Long แล้วเพิ่มค่าความผันผวนด้านล่าง น้ำหนักหุ้นจะลดลงตามสูตร ส่วนที่เหลืออยู่ในเงินสด จากนั้นเปลี่ยนสัญญาณเป็น Cash แล้วดูว่าน้ำหนักหุ้นเหลือเท่าไร

::: volatility-sizing-viz

ในตัวอย่างนี้ หากปรับเป้าความผันผวนเป็น 20% แต่สินทรัพย์แกว่งเพียง 5% เพดานไม่ใช้ leverage จะทำให้ลงเงินได้สูงสุด 100% และความเสี่ยงตามสัดส่วนยังต่ำกว่าเป้า

## เวลาและขนาดสถานะเปลี่ยน Backtest อย่างไร {#lab}

ห้องทดลองใช้ราคาสมมติ 320 sessions ซึ่งสร้างด้วย seed 20260925 และใช้ชุดเดิมทุกครั้ง ราคาเป็นหน่วยสมมติ ไม่ได้อ้างถึงสินทรัพย์หรือวันที่ตลาดจริง

กฎคือ long เมื่อ SMA 20 ของราคาปิดสูงกว่า SMA 50 มิฉะนั้นถือเงินสด ค่าทั้งสองถูกกำหนดเพื่อสาธิตกลไก ไม่ได้เลือกจากผลตอบแทนที่ดีที่สุด ทุกกรณีเริ่มที่ราคาเปิด session 60 และจบที่ราคาเปิด session 319 รวม 259 ช่วงผลตอบแทน ใช้ทุนเริ่มต้น 100 หน่วย

| กรณี | ข้อมูลที่ใช้ตัดสินใจเปิด $t$ | ขนาดเมื่อ long |
| --- | --- | --- |
| Causal · fixed size | ราคาปิดถึง $t-1$ | 100% |
| Causal · vol target | ราคาปิดถึง $t-1$ | ปรับตาม volatility ถึง $t-1$ |
| Look-ahead · fixed size | แอบใช้ราคาปิด $t$ | 100% |
| Look-ahead · vol target | แอบใช้ราคาปิดและ volatility ถึง $t$ | ปรับตามข้อมูลที่ยังไม่รู้ |

กรณี Look-ahead ทั้งสอง ซื้อขายตามเวลาจริงไม่ได้ ใส่มาเพื่อให้เห็นผลของความผิดพลาด ไม่ใช่ตัวเลือกกลยุทธ์ ส่วน buy-and-hold ในตารางใช้ช่วงเปิดถึงเปิดเดียวกัน รวมต้นทุนเข้าและออกเช่นเดียวกัน

::: backtest-intro-lab

เริ่มจากเปรียบเทียบ fixed size กับ vol target ภายในกลุ่ม Causal ก่อน จากนั้นเทียบ Causal กับ Look-ahead โดยคงรูปแบบ size เดียวกัน วิธีนี้ช่วยไม่ให้สรุปผลจากสองสิ่งที่เปลี่ยนพร้อมกัน

ลองเพิ่มต้นทุนแล้วดูทั้งผลตอบแทนและ turnover การ targeting ไม่จำเป็นต้องได้ Sharpe สูงขึ้นทุกครั้ง และ look-ahead ก็ไม่จำเป็นต้องทำให้ทุก metric ดีขึ้นในทุกชุดข้อมูล ทั้งผลที่สูงขึ้นและต่ำลงยังใช้ประเมินการซื้อขายจริงไม่ได้ หากคำสั่งอาศัยข้อมูลที่ยังไม่เกิดขึ้น

การทดลองนี้เป็นตัวอย่างเชิงกลไก ไม่ใช่ out-of-sample validation ของ SMA และการเปลี่ยน cost/target ในหน้านี้ไม่ใช่หลักฐานสำหรับเลือกกลยุทธ์ลงทุน

## อ่าน Tear sheet: ผลตอบแทนควบคู่ความเสี่ยง {#tear-sheet}

**Tear sheet** คือรายงานย่อที่รวมผลตอบแทน ความเสี่ยง และพฤติกรรมของกลยุทธ์ไว้ด้วยกัน ก่อนอ่านตัวเลข ให้ดูช่วงเวลา ความถี่ จำนวนข้อมูล benchmark และนิยามต้นทุนก่อนเสมอ [5]

| ตัววัด | คำถามที่ตอบ | ข้อควรอ่านประกอบ |
| --- | --- | --- |
| Total return | เงินเริ่มต้นเปลี่ยนไปกี่เปอร์เซ็นต์ตลอดช่วง | ต้องดูระยะเวลาลงทุนและช่วงขาดทุนประกอบ |
| CAGR | หากเติบโตด้วยอัตราทบต้นคงที่ต่อปี จะเท่ากับเท่าไร | เป็นการย่อเส้นทาง ไม่ใช่ผลตอบแทนที่ได้รับเท่ากันทุกปี |
| Annualized volatility | ผลตอบแทนแกว่งมากเพียงใดเมื่อแสดงเป็นรายปี | ขึ้นกับความถี่และสมมติฐานการ annualize |
| Sharpe ratio | ได้ผลตอบแทนส่วนเกินต่อความผันผวนเท่าไร | ต้องระบุ risk-free rate, ช่วงข้อมูล และการ annualize |
| Maximum drawdown | จากจุดสูงสุดเดิม พอร์ตเคยตกลงลึกที่สุดเท่าไร | เป็นตัววัดเส้นทางในช่วงตัวอย่าง ไม่ annualize |
| Turnover | ต้องซื้อขายมากเพียงใด | บทนี้เป็นผลรวม notional / equity ก่อนซื้อขายแต่ละครั้ง ไม่ใช่จำนวน trades |

หากมี $N$ ช่วงผลตอบแทน และใช้สมมติฐาน 252 ช่วงต่อปี:

$$
\mathrm{CAGR}=\left(\frac{V_N}{V_0}\right)^{252/N}-1,
\qquad \sigma_{\mathrm{ann}}=s(r^{\mathrm{net}})\sqrt{252}.
$$

ห้องทดลองกำหนด risk-free rate เป็นศูนย์และใช้ sample standard deviation ดังนั้น

$$
\widehat{\mathrm{Sharpe}}=\sqrt{252}\frac{\overline{r^{\mathrm{net}}}}{s(r^{\mathrm{net}})}.
$$

Sharpe ในสูตรนี้ใช้ค่าเฉลี่ยผลตอบแทนรายช่วง ไม่ใช่นำ CAGR มาหาร volatility ถ้าความผันผวนเป็นศูนย์จะไม่รายงาน ratio การคูณรากของเวลาเป็น convention แบบย่อ ไม่ใช่ข้อยืนยันว่าผลตอบแทนไม่มี autocorrelation [1, หน้า PDF 85, 278]

นิยาม drawdown ที่ใช้ในกราฟคือ

$$
D_t=\frac{V_t}{\max_{0\leq u\leq t}V_u}-1,
\qquad \mathrm{MDD}=\min_t D_t.
$$

ห้องทดลองวัดจากจุดมูลค่าที่ราคาเปิดแต่ละ session จึงไม่ครอบคลุมการขาดทุนระหว่างวัน เรารายงานเป็นค่าติดลบหรือศูนย์ เช่น เส้นพอร์ตสมมติ 100 → 110 → 88 → 100 → 120 จบด้วย total return +20% แต่เคยมี drawdown −20% จาก 110 ลง 88 และต้องรอจนกลับถึงยอดเดิมจึงฟื้นตัวเต็มที่ [1, หน้า PDF 279]

### คำนวณผลตอบแทนที่ต้องได้เพื่อกลับทุน

จากยอด 100 ลงเหลือ 80 คือขาดทุน 20% แต่การบวกกลับ 20% ของ 80 ได้เพียง 16 จึงกลับมาแค่ 96 หากต้องการกลับถึงยอดเดิม ต้องเพิ่ม 20 จากฐาน 80 หรือ 25%

สำหรับสัดส่วนขาดทุน $d$ ที่อยู่ระหว่าง 0 กับ 1 อัตราฟื้นตัวที่ต้องการคือ

$$
\mathrm{Recovery}=\frac{1}{1-d}-1=\frac{d}{1-d}.
$$

::: drawdown-recovery-viz

กราฟนี้เป็นตัวอย่างเลขคณิตของมูลค่าพอร์ต ไม่ใช่การพยากรณ์ว่าพอร์ตจะฟื้นจริงหรือใช้เวลากี่วัน

อีกตัวอย่างหนึ่ง เงิน 100 เป็น 121 ในสองปี มี CAGR 10% ไม่ใช่ 10.5% การหารผลตอบแทนรวมด้วยจำนวนปีไม่ใช่การคิดทบต้น

ดูด้วยว่าพอร์ตอยู่ต่ำกว่าจุดสูงสุดนานเท่าไร กำไรกระจุกตัวอยู่ช่วงไหน และผลแต่ละช่วงต่างกันอย่างไร หากผลสูงผิดสังเกต ให้ตรวจ timing, costs, ความยาวตัวอย่าง และจำนวนการลอง หลีกเลี่ยงเกณฑ์เหมารวมว่า “Sharpe เกินเลขหนึ่งต้องผิด” เพราะเลขเดียวไม่บอกที่มาของผลทั้งหมด [2, หน้า PDF 8–10; 3]

### สร้างรายงานด้วย QuantStats

[QuantStats](https://github.com/ranaroussi/quantstats) มีส่วนคำนวณสถิติ วาดกราฟ และสร้าง HTML tear sheet โดยรับชุดผลตอบแทนเป็นข้อมูล จึงควรส่งผลตอบแทน สุทธิที่จัดเวลาไว้ถูกต้องแล้ว พร้อม benchmark ที่ตรงช่วงกัน

Win rate ของรายงานแบบ return series หมายถึงสัดส่วนช่วงเวลาที่ให้ผลบวก เช่น วันบวก ไม่จำเป็นต้องเท่ากับสัดส่วนรายการซื้อขายที่ชนะ หนึ่ง trade ที่ถือหลายวันอาจมีทั้งวันบวกและวันลบ [6]

Notebook หลักของบทนี้คำนวณด้วย Python standard library และรันได้โดยไม่ต้องใช้ QuantStats ตัวอย่างเสริมต่อไปนี้เป็นแนวทางเมื่อมี `pandas` และ `quantstats` ติดตั้งแล้ว ไม่ถูกเรียกใช้โดยหน้าเว็บ และไม่ได้ทดสอบการสร้างรายงาน QuantStats ในชุดตรวจบทนี้:

```python
import json
from pathlib import Path
import pandas as pd
import quantstats as qs

saved = json.loads(Path("backtest-intro-results.json").read_text())
net = saved["results"][0]["returns"]  # causal fixed, net of assumed costs
# Artificial dates for report formatting only; these are not market observations.
index = pd.bdate_range("2000-01-03", periods=len(net))
returns = pd.Series(net, index=index, name="Synthetic causal fixed")
benchmark = pd.Series(saved["benchmark"]["returns"], index=index,
                      name="Synthetic buy and hold")
qs.reports.html(returns, benchmark=benchmark, output="backtest-report.html")
```

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

## จุดที่ทำให้ผลดีเกินจริง {#biases}

นอกจาก look-ahead ยังมีจุดที่ควรตรวจร่วมกัน:

- **Survivorship bias:** เลือกเฉพาะสินทรัพย์ที่ยังอยู่ในปัจจุบัน แล้วลืมตัวที่ถูกถอดหรือหายไปในอดีต
- **Overfitting / selection bias:** ลองหลายกฎ หลายช่วงข้อมูล หรือหลายวิธี clean แล้วแสดงเฉพาะผู้ชนะ โดยไม่บอกจำนวนการลอง
- **ข้อมูลไม่ตรงเวลาที่ใช้ได้จริง:** ใช้ตัวเลขที่แก้ย้อนหลัง หรือถือว่างบการเงินพร้อมตั้งแต่วันที่สิ้นงวดแทนวันที่เปิดเผย
- **Execution ที่มองโลกดีเกินไป:** สมมติซื้อได้ครบทุกครั้ง ไม่คิด spread, slippage, impact หรือข้อจำกัดการยืมหุ้น
- **Drawdown tolerance:** ยอมรับความเสี่ยงบนกระดาษได้ แต่หยุดกลยุทธ์กลางทางเมื่อเจอการขาดทุนจริง ซึ่งทำให้เส้นทางใช้งานต่างจาก backtest

ดูรายละเอียดในเอกสาร [2, หน้า PDF 5–8; 3, หน้า 9–12; 4, หน้า PDF 15–17]

## ฝึกอ่านผลและรันซ้ำ {#practice}

[ดาวน์โหลด Notebook ที่รันแล้ว](downloads/robo-trade-13.ipynb) · [Python example](downloads/lesson_13.py) · [ข้อมูลสมมติ JSON](downloads/backtest-intro-data.json) · [ผลคำนวณ JSON](downloads/backtest-intro-results.json) · [Markdown บทนี้](downloads/content/chapters/13-introduction-backtest.md)

Python example ใช้ standard library จึงไม่ต้องติดตั้ง package หรือเชื่อมต่ออินเทอร์เน็ต ตัว Notebook บรรจุโค้ดและตัวอย่างครบในตัว ใช้ Python 3 รันเซลล์จากบนลงล่างได้ ส่วนไฟล์ข้อมูลและผล JSON ใช้ตรวจเทียบกับห้องทดลองเว็บ

```shell
python3 lesson_13.py
```

ลองตอบก่อนดูคำอธิบายในแบบทบทวน:

1. ถ้า signal ถูกเลื่อนแล้ว แต่ volatility ใช้ข้อมูลทั้งชุด การตัดสินใจยังมีข้อมูลอนาคตหรือไม่
2. เมื่อเพิ่มต้นทุนจาก 5 เป็น 10 bps กรณีไหนได้รับผลกระทบมาก และ turnover ช่วยอธิบายได้แค่ไหน
3. หาก targeting ลด drawdown แต่ลดผลตอบแทนด้วย ควรอ่าน metric ใดเพิ่มก่อนสรุปว่าดีกว่า
4. ถ้าผลช่วงทดสอบไม่ผ่าน คุณจะตรวจขั้นตอนใดก่อน และจะบันทึกการเปลี่ยนแปลงอย่างไร

::: quiz

## บันทึกผลก่อนทดลองรอบถัดไป {#summary}

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

## แหล่งที่ใช้ในบทนี้ {#sources}

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

1. **Yves Hilpisch, Python for Algorithmic Trading: From Idea to Cloud Deployment.** ไฟล์ `python for algorithmic trading book (js).pdf` ที่แนบ: บท 4 หน้า PDF 75–101 โดยเฉพาะ lag หน้า 84 และต้นทุนหน้า 94; บท 6 หน้า 148–152; ตัววัดหน้า 278–279 เลขเหล่านี้เป็นหน้า 1-based ของไฟล์ PDF ที่สร้างผ่าน browser ไม่ใช่เลขหน้าฉบับพิมพ์
2. **Jacques Joubert, Dragan Sestovic, Illya Barziy, Walter Distaso และ Marcos Lopez de Prado, The Three Types of Backtests**, เวอร์ชัน 29 July 2024, ไฟล์ `three+types+of+backtests.pdf` ที่แนบ: หน้า PDF 3–5 เรื่องประเภท, 5–8 เรื่องข้อผิดพลาด, 8–10 เรื่องตัววัด และ 16 เรื่องหน้าที่ของ backtest
3. **Rob Arnott, Campbell R. Harvey และ Harry Markowitz, A Backtesting Protocol in the Era of Machine Learning**, 29 October 2018, ไฟล์ `ssrn-3275654.pdf` ที่แนบ: หน้า 8–12 เรื่องสมมติฐาน ข้อมูล และการทดสอบ; หน้า 14–16 เรื่องวัฒนธรรมวิจัยและข้อจำกัด
4. **[Introduction to Backtesting](https://storage.googleapis.com/cloud-training/T-AIFORF-I/Course%201/Introduction%20to%20Backtesting.pdf)**, สไลด์ 19 หน้าในลิงก์ที่ให้: หน้า PDF 4–8 เรื่องแบ่งข้อมูลและนิยาม, 15–17 เรื่องข้อจำกัดและ bias
5. **Hudson Thames reading-group transcript ที่แนบและข้อความบรรยายที่ให้ในคำขอ**, เรื่อง vectorized backtesting, signal lag, volatility targeting และ tear sheet ใช้เป็นเส้นเรื่องการเรียนรู้ โดยไม่ยกคำกล่าวเรื่องผลตอบแทนหรือเกณฑ์ Sharpe ตายตัวมาเป็นข้อเท็จจริงทั่วไป
6. **[QuantStats repository](https://github.com/ranaroussi/quantstats)**, README ที่ลิงก์ให้มา ตรวจวันที่ 25 September 2026: การวิเคราะห์ return series, สถิติ กราฟ และ HTML tear sheet

ข้อความภาษาอังกฤษใน “หลักคิด: แก้กระบวนการวิจัย” คงตามข้อความที่กำหนดมา ไม่ระบุชื่อผู้กล่าวเพราะยังไม่ได้ยืนยันต้นทางคำต่อคำ ภาพที่แนบเป็นปกและคำรับรองหนังสือ *Advances in Financial Machine Learning* จึงไม่ใช้แทนหลักฐานว่าได้อ่านเนื้อหาหนังสือเล่มนั้น ไม่แจกจ่าย PDF ต้นฉบับหรือภาพจากหนังสือในชุดดาวน์โหลด
