---
title: CPPI จากศูนย์ — ปรับเงินลงทุนตาม Floor และ Cushion
description: คำนวณ CPPI ด้วยมือก่อนเขียน Python เรียนรู้เวลาปรับพอร์ต Gap Risk และ Floor ที่ขยับตามมูลค่าสูงสุดของพอร์ต
---

# CPPI จากศูนย์

<p class="lead">ถ้ามีเงิน 1,000 บาท และอยากรักษาเงินไว้ 800 บาท เราควรนำเงินไปเสี่ยงเท่าไร และจะปรับอย่างไรเมื่อพอร์ตขาดทุน?</p>

บท [ข้อจำกัดของ Diversification](diversification-limits.html) อธิบายว่าการเพิ่มสินทรัพย์ยังอาจเหลือความเสี่ยงจากเหตุการณ์ร่วม บทนี้เปลี่ยนคำถามมาเป็นการจัดสรรเงินระหว่างส่วนเสี่ยงกับส่วนที่ใช้รองรับเกณฑ์ขั้นต่ำ เราจะกำหนดกติกา คำนวณหนึ่งงวดด้วยมือ แล้วใช้ Python ทำซ้ำตามเวลา

**CPPI — Constant Proportion Portfolio Insurance** เป็นกติกาที่ผูกเงินในสินทรัพย์เสี่ยงเข้ากับส่วนต่างระหว่างมูลค่าพอร์ตกับ floor ชื่อ “insurance” ไม่ได้แปลว่ามีบริษัทประกันรับชำระส่วนที่ขาดให้ และ floor ในการจำลองแบบปรับพอร์ตเป็นงวดอาจถูกทะลุได้ ตัวอย่างท้ายบทจะแสดงเหตุการณ์นั้นด้วยตัวเลข

บทนี้เป็นจุดเริ่มเรียน CPPI: Floor, Cushion, การปรับวงเงิน และ Gap risk รวมอยู่ที่นี่ เมื่อคำนวณตามได้แล้ว อ่าน [Portfolio Insurance: เปรียบเทียบวิธีป้องกันพอร์ต](portfolio-insurance.html) เพื่อเทียบกับการซื้อ Option การยก Floor แบบ TIPP และตัวคูณที่ปรับได้ หรือไป [Monte Carlo](monte-carlo.html) เพื่อทดสอบ CPPI หลายเส้นทาง ข้อมูลทุกชุดในหน้านี้เป็นตัวอย่างสมมติ ไม่ใช่ผลการลงทุนจริงหรือคำแนะนำให้เลือกค่า multiplier ใด

| เส้นทางเรียน | คำถาม |
|---|---|
| [Floor และ Cushion](#cppi-basics) | เงินส่วนใดเป็นเกณฑ์ขั้นต่ำ และส่วนใดใช้รับความเสี่ยง? |
| [คำนวณทีละเดือน](#cppi-worked-example) | หลังขึ้นหรือลง ต้องเปลี่ยนวงเงินอย่างไร? |
| [เขียนฟังก์ชัน](#cppi-python) | เก็บสถานะของพอร์ตให้เวลาถูกต้องอย่างไร? |
| [Gap Risk](#cppi-gap-risk) | ตลาดต้องลงแรงแค่ไหนจึงทะลุ floor ได้? |
| [ทดสอบ Floor แบบ TIPP](#cppi-drawdown) | ฟังก์ชันยก Floor ตามจุดสูงสุดที่รู้แล้วจริงหรือไม่? |

[ดาวน์โหลด Notebook ของบทนี้](notebooks/cppi-dynamic-allocation.ipynb) เพื่อรันตัวอย่างตามลำดับและเทียบผลลัพธ์ที่บันทึกไว้

<span id="cppi-basics"></span>

## ตั้งชื่อให้เงินแต่ละส่วนก่อนเขียนสูตร

ใช้เงินตั้งต้น 1,000 บาทและ floor คงที่ 800 บาท ดังนั้นเงินส่วนเกินจาก floor เท่ากับ 200 บาท เราเรียกส่วนนี้ว่า **cushion** หากกำหนด multiplier หรือ **ตัวคูณ** เท่ากับ 3 กติกาจะให้ลงทุนในสินทรัพย์เสี่ยงสามเท่าของ cushion หรือ 600 บาท แล้วเก็บอีก 400 บาทในส่วนปลอดภัยของแบบจำลอง

| สัญลักษณ์ | ความหมาย ณ เวลาที่กำลังจัดพอร์ต | ค่าเริ่มต้น | หน่วย |
|---|---|---:|---|
| $V_t$ | มูลค่าพอร์ตทั้งหมด | 1,000 | บาท |
| $F_t$ | floor หรือเกณฑ์ขั้นต่ำขณะนั้น | 800 | บาท |
| $C_t=V_t-F_t$ | cushion | 200 | บาท |
| $m$ | ตัวคูณ cushion | 3 | ไม่มีหน่วย |
| $E_t$ | จำนวนเงินในสินทรัพย์เสี่ยง | 600 | บาท |
| $V_t-E_t$ | จำนวนเงินในส่วนปลอดภัย | 400 | บาท |

ในรูปแบบที่ยังไม่จำกัดการกู้ยืม สูตรเงินเสี่ยงคือ $E_t=mC_t$ แต่ในบทนี้จะ **ไม่กู้เงินและไม่ขายชอร์ต** จึงจำกัด $E_t$ ไว้ระหว่าง 0 กับมูลค่าพอร์ตทั้งหมด

$$
E_t=\min\bigl(\max(m(V_t-F_t),0),V_t\bigr),
\qquad w_t=\frac{E_t}{V_t}
$$

$w_t$ คือน้ำหนักในสินทรัพย์เสี่ยง ตัวอย่างเริ่มต้นได้ 60% คำว่า constant proportion ในชื่อ CPPI หมายถึงการใช้ตัวคูณกับ cushion ตามกติกา ไม่ได้หมายความว่าน้ำหนักสินทรัพย์เสี่ยงจะคงที่ 60% ตลอดเวลา และเมื่อชนขอบ 0% หรือ 100% จำนวนเงินจริงจะไม่เท่ากับ $mC_t$ เสมอ

สมมติว่าสินทรัพย์เสี่ยงให้ผลตอบแทน $R_{t+1}$ และส่วนปลอดภัยให้ $R^s_{t+1}$ ระหว่างจุดปรับพอร์ตสองจุด มูลค่าพอร์ตปลายงวดคือ

$$
V_{t+1}=E_t(1+R_{t+1})+(V_t-E_t)(1+R^s_{t+1})
$$

น้ำหนักตัดสินใจที่เวลา $t$ ส่วนผลตอบแทนเกิดตลอดงวดที่ตามมา ตอนกำหนด $E_t$ เรายังไม่รู้ $R_{t+1}$ การคำนวณ CPPI ต้องรักษาลำดับนี้ไว้ แนวคิดพื้นฐานและรูปแบบ floor หลายชนิดอธิบายอยู่ใน [CPPI Part 1](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/TYWck/an-introduction-to-cppi-part-1) และ [CPPI Part 2](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/MNaTt/an-introduction-to-cppi-part-2) ของคอร์ส

### “ส่วนปลอดภัย” ต้องสัมพันธ์กับสิ่งที่ต้องการรักษา

ตัวอย่างแรกให้เงินสดมีผลตอบแทน 0% ทุกเดือน จึงไม่เสี่ยงต่อ floor ที่กำหนดเป็นเงินบาทคงที่ แต่ในการใช้งานอื่น สินทรัพย์ที่เหมาะกับเป้าหมายอาจต่างกัน เช่น เงินที่จะต้องจ่าย ณ วันหนึ่งในอนาคตต้องพิจารณามูลค่าปัจจุบันและอายุของสินทรัพย์รองรับด้วย กองทุนตราสารหนี้ที่ราคาขึ้นลงไม่ได้กลายเป็นเงินสดไร้ความเสี่ยงเพียงเพราะเรียกว่า safe asset อ่านต่อได้ที่ [Floor และ Cushion](portfolio-insurance.html#floor-and-cushion)

<span id="cppi-worked-example"></span>

## สองเดือนแรก: คิดด้วยมือให้ทันกติกา

เดือนแรกสินทรัพย์เสี่ยงให้ผลตอบแทน +8% เงิน 600 บาทจึงกลายเป็น 648 บาท ขณะที่เงินสด 400 บาทคงเดิม พอร์ตปลายเดือนเป็น 1,048 บาท จากนั้นจึงเตรียมการลงทุนเดือนที่สอง

$$
C_1=1{,}048-800=248,
\qquad E_1=3(248)=744
$$

พอร์ตจึงเพิ่มเงินเสี่ยงจากมูลค่าที่ถืออยู่ 648 บาทเป็น 744 บาท ณ จุดปรับพอร์ต โดยใช้เงินสดเพิ่ม 96 บาท เดือนที่สองสมมติสินทรัพย์เสี่ยงให้ −12% จึงได้

$$
V_2=744(1-0.12)+(1{,}048-744)=958.72
$$

เดือนที่สามจึงเริ่มด้วย cushion $958.72-800=158.72$ บาท และวงเงินเสี่ยง $3(158.72)=476.16$ บาท กติกานี้เพิ่มวงเงินเสี่ยงหลังพอร์ตโตและลดวงเงินหลังพอร์ตลง ภายใต้ floor คงที่และตัวคูณคงที่

เราจะต่อเป็นหกเดือน โดยกำหนดผลตอบแทนสินทรัพย์เสี่ยงเท่ากับ +8%, −12%, +6%, −25%, +10%, +4% ตามลำดับ เงินสดยังให้ 0% และไม่มีค่าธรรมเนียม ภาษี หรือข้อจำกัดการซื้อขายเศษหน่วย

| เดือน | มูลค่าพอร์ตต้นเดือน | เงินเสี่ยงต้นเดือน | ผลตอบแทนสินทรัพย์เสี่ยง | มูลค่าพอร์ตปลายเดือน |
|---:|---:|---:|---:|---:|
| 1 | 1,000.0000 | 600.0000 | +8% | 1,048.0000 |
| 2 | 1,048.0000 | 744.0000 | −12% | 958.7200 |
| 3 | 958.7200 | 476.1600 | +6% | 987.2896 |
| 4 | 987.2896 | 561.8688 | −25% | 846.8224 |
| 5 | 846.8224 | 140.4672 | +10% | 860.8691 |
| 6 | 860.8691 | 182.6074 | +4% | 868.1734 |

หลังเดือนที่สี่ พอร์ตเหลือ cushion เพียง 46.8224 บาท จึงให้เงินเสี่ยงในเดือนที่ห้าประมาณ 140.4672 บาท เมื่อสินทรัพย์เสี่ยงฟื้น +10% พอร์ตจึงไม่ได้ฟื้น +10% ทั้งก้อน แต่เพิ่มประมาณ 14.0467 บาท เพราะส่วนใหญ่ย้ายไปอยู่ในเงินสดแล้ว

<span id="cppi-python"></span>

## เขียนฟังก์ชันโดยแยก “ก่อนงวด” กับ “หลังงวด”

เปิด Notebook ใหม่แล้วรันทุกช่องในหน้านี้ตามลำดับ โค้ดใช้ NumPy สำหรับตัวเลขและ pandas สำหรับตาราง ไม่ต้องเปิด Notebook ของบทอื่นก่อน

```python
import numpy as np
import pandas as pd

cppi_risky_returns = np.array([0.08, -0.12, 0.06, -0.25, 0.10, 0.04])
cppi_start = 1000.0
cppi_floor_fraction = 0.80
cppi_multiplier = 3.0
```

`0.80` ในที่นี้หมายถึง floor เริ่มต้น 80% ของเงินตั้งต้น ไม่ได้แปลว่าลงทุนปลอดภัย 80% ส่วน `0.08` เป็นผลตอบแทน +8% ของหนึ่งเดือน เราจะเก็บตัวเลขด้วยหน่วยทศนิยมตลอดการคำนวณ แล้วค่อยแปลงเพื่อแสดงผล

เริ่มจากฟังก์ชันที่รับผลตอบแทนทีละงวด เราให้ผู้เรียกบอกจำนวนงวดต่อปีด้วย เพื่อแปลงอัตราเงินสดต่อปีให้เป็นอัตราต่องวดอย่างสอดคล้อง หากเป็น **อัตราผลตอบแทนต่อปีแบบทบต้น** $g_s$ สูตรคือ

$$
R^s=(1+g_s)^{1/k}-1
$$

$k$ คือจำนวนงวดต่อปี เช่น 12 สำหรับรายเดือน หรือ 4 สำหรับรายไตรมาส อัตราเงินสด 3% ต่อปีจึงเป็นประมาณ 0.2466% ต่อเดือน การหาร 12 ตรง ๆ เป็นอีก convention หนึ่งหรือเป็นค่าประมาณ ต้องไม่สลับใช้โดยไม่ระบุ

```python
def cppi_path(risky_returns, start=1000.0, floor_fraction=0.8,
              multiplier=3.0, cash_annual=0.0, periods_per_year=12,
              drawdown=None):
    risky = np.asarray(risky_returns, dtype=float)
    if risky.ndim != 1 or not np.isfinite(risky).all() or (risky < -1).any():
        raise ValueError("Use a finite 1D array of simple returns, all >= -1.")
    if start <= 0 or not 0 <= floor_fraction <= 1 or multiplier < 0:
        raise ValueError("Check start, floor_fraction and multiplier.")
    if cash_annual <= -1 or periods_per_year <= 0:
        raise ValueError("Check cash_annual and periods_per_year.")
    if drawdown is not None and not 0 <= drawdown <= 1:
        raise ValueError("drawdown must be between 0 and 1.")

    cash_return = (1 + cash_annual)**(1 / periods_per_year) - 1
    wealth = float(start)
    peak = float(start)
    floor = start * floor_fraction if drawdown is None else start * (1 - drawdown)
    records = [{"step": 0, "wealth": wealth, "floor": floor,
                "floor_before": np.nan, "risky_before": np.nan,
                "weight_before": np.nan}]

    for step, risky_return in enumerate(risky, start=1):
        if drawdown is not None:
            floor = (1 - drawdown) * peak
        floor_before = floor
        cushion = wealth - floor_before
        risky_amount = np.clip(multiplier * cushion, 0, wealth)
        risky_weight = risky_amount / wealth if wealth > 0 else 0.0
        safe_amount = wealth - risky_amount
        wealth = risky_amount * (1 + risky_return) + safe_amount * (1 + cash_return)
        peak = max(peak, wealth)
        if drawdown is not None:
            floor = (1 - drawdown) * peak
        records.append({"step": step, "wealth": wealth, "floor": floor,
                        "floor_before": floor_before,
                        "risky_before": risky_amount,
                        "weight_before": risky_weight})

    return pd.DataFrame(records).set_index("step")

cppi_fixed = cppi_path(cppi_risky_returns)
print(cppi_fixed.round(4))
```

ไม่ต้องจำทั้งฟังก์ชันในรอบเดียว อ่านตามลำดับการเงินได้ดังนี้

1. `np.asarray(..., dtype=float)` แปลง list หรือ array ที่รับมาเป็นตัวเลขทศนิยมหนึ่งชุด `ndim != 1` ตรวจว่าเผลอส่งตารางหลายคอลัมน์มาหรือไม่ ส่วน `np.isfinite` กันค่าขาดหรือค่าที่ไม่สิ้นสุด
2. `records` เก็บผลแต่ละจุดเวลา โดยเริ่มจากแถว 0 ซึ่งเป็นเงินตั้งต้น แถวนี้ไม่มีผลตอบแทนงวดก่อน จึงใช้ `NaN` ในคอลัมน์วงเงินก่อนงวด
3. `for ... in enumerate(..., start=1)` อ่านผลตอบแทนทีละค่า พร้อมเลขงวดเริ่มที่ 1 ต้องคำนวณตามเวลา เพราะวงเงินรอบใหม่ขึ้นอยู่กับมูลค่าที่ได้จากรอบก่อน
4. `np.clip(..., 0, wealth)` จำกัดเงินเสี่ยงไม่ให้ต่ำกว่า 0 หรือสูงกว่ามูลค่าพอร์ต จากนั้นคำนวณเงินปลายงวด แล้วจึงเก็บผล
5. `return` ส่งตารางออกจากฟังก์ชัน คอลัมน์ `wealth` และ `floor` คือสถานะหลังจบงวด ส่วนชื่อที่ลงท้าย `_before` คือค่าที่ใช้ก่อนรับผลตอบแทนงวดนั้น

พารามิเตอร์ `drawdown=None` หมายถึงยังไม่เปิดกติกายก floor ตามจุดสูงสุด เราจะใช้ส่วนนี้ในหัวข้อถัดไป หากไม่ส่งค่าบางตัวตอนเรียก ฟังก์ชันจะใช้ค่าหลังเครื่องหมาย `=` ในบรรทัดประกาศ เช่น `multiplier=3.0`

### เก็บเงินตั้งต้นไว้ เพื่อไม่ทำผลตอบแทนงวดแรกหาย

เพราะตารางมีแถวเริ่มต้น 1,000 บาท `.pct_change()` จึงคำนวณเดือนแรกเป็น $1{,}048/1{,}000-1=4.8\%$ ได้ หากเก็บเฉพาะปลายเดือนแล้วใช้คำสั่งเดียวกัน จะไม่เหลือฐานสำหรับคำนวณผลตอบแทนเดือนแรก

```python
cppi_portfolio_returns = cppi_fixed["wealth"].pct_change().dropna()
cppi_buy_hold = cppi_start * np.r_[1.0, np.cumprod(1 + cppi_risky_returns)]
cppi_comparison = pd.DataFrame({
    "CPPI": cppi_fixed["wealth"],
    "Risky buy and hold": cppi_buy_hold,
    "Fixed floor": cppi_start * cppi_floor_fraction
})
print("Portfolio returns (%):")
print((cppi_portfolio_returns * 100).round(4))
print(cppi_comparison.round(4))
```

`np.cumprod(1 + returns)` คูณ gross return สะสม `np.r_[1.0, ...]` เติมค่า 1 ด้านหน้าเพื่อแทนจุดเริ่มต้น จากนั้นคูณเงินตั้งต้นทั้งชุด ทั้งสองกลยุทธ์จึงเริ่มจาก 1,000 บาทและมีเจ็ดจุดเวลาเหมือนกัน

เมื่อจบตัวอย่าง CPPI มีประมาณ 868.17 บาท ส่วนการถือสินทรัพย์เสี่ยงเต็มพอร์ตตลอดมีประมาณ 864.37 บาท ผลจากหกเดือนชุดนี้ยังใช้จัดอันดับว่ากลยุทธ์ใดดีกว่าเสมอไม่ได้ โดยเฉพาะ CPPI ที่เปลี่ยนวงเงินตามลำดับเหตุการณ์ บท [Monte Carlo](monte-carlo.html) จะเปรียบเทียบหลายเส้นทางภายใต้สมมติฐานที่ระบุไว้

<figure class="course-figure">
<picture>
<source media="(max-width: 600px)" srcset="assets/charts/course-cppi-mobile.svg">
<img src="assets/charts/course-cppi.svg" alt="มูลค่า CPPI เทียบกับการถือสินทรัพย์เสี่ยงและ floor ตลอดหกเดือนสมมติ" width="720" height="560" loading="lazy">
</picture>
<figcaption>ใช้ผลตอบแทนหกเดือนเดียวกับตารางและโค้ด เส้นเชื่อมจุดสิ้นเดือน ไม่ได้แสดงราคาทุกขณะระหว่างเดือน เงินสดให้ผลตอบแทนศูนย์และไม่มีต้นทุน การที่ CPPI อยู่เหนือ floor บนเส้นทางนี้ยังไม่ครอบคลุมกรณีขาดทุนแรงในหัวข้อถัดไป</figcaption>
</figure>

<span id="cppi-gap-risk"></span>

## Gap Risk: ราคาข้าม floor ก่อนถึงเวลาปรับพอร์ต

กลับไปเริ่มใหม่ที่ 1,000 บาท floor 800 บาท ตัวคูณ 3 เงินเสี่ยงจึงเป็น 600 บาท ถ้าระหว่างจุดปรับพอร์ตสินทรัพย์เสี่ยงลง 40% เงินเสี่ยงจะเหลือ 360 บาท บวกเงินสด 400 บาท พอร์ตเหลือ 760 บาท ต่ำกว่า floor 40 บาท

```python
cppi_gap = cppi_path([-0.40])
print(cppi_gap[["wealth", "floor", "risky_before"]])
print("Shortfall:", max(800.0 - cppi_gap.loc[1, "wealth"], 0.0))
```

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

ในกรณี floor คงที่ เงินสดให้ 0% ไม่มีต้นทุน และยังไม่ชนขอบวงเงิน 100% เราแทน $E=mC$ ลงในสูตรได้

$$
C_{t+1}=C_t+E_tR_{t+1}
=C_t(1+mR_{t+1})
$$

เมื่อ $C_t>0$ พอร์ตจะต่ำกว่า floor หาก $R_{t+1}<-1/m$ สำหรับ $m=3$ คือขาดทุนเกิน 33.33% ในงวดนั้น ถ้าลงพอดีหนึ่งในสาม พอร์ตจะสัมผัส floor สูตรนี้ขึ้นกับเงื่อนไขที่เพิ่งระบุ เมื่อวงเงินจริงถูกจำกัด อัตราดอกเบี้ยเปลี่ยน หรือ floor เคลื่อนตามภาระผูกพัน ต้องกลับไปคำนวณจากเงินเสี่ยงและส่วนปลอดภัยจริง

ตัวอย่าง $m=1$ ลงทุนเสี่ยงเท่ากับ cushion 200 บาทและกันเงินสด 800 บาทไว้ ภายใต้ floor คงที่ เงินสดไม่ลดมูลค่า ไม่มีต้นทุน และสินทรัพย์เสี่ยงขาดทุนได้ไม่เกิน 100% แม้สินทรัพย์เสี่ยงเหลือศูนย์ พอร์ตก็ยังมี 800 บาท นี่เป็นผลจากสมมติฐานเฉพาะของตัวอย่าง ไม่ใช่คำรับรองสำหรับสินทรัพย์ปลอดภัยหรือ floor ทุกแบบ

การลดตัวคูณทำให้เริ่มต้นเสี่ยงน้อยลง ส่วนการปรับพอร์ตถี่ขึ้นช่วยลดช่วงเวลาที่วงเงินค้างอยู่ แต่การซื้อขายถี่ขึ้นมีต้นทุนและยังอาจมีช่องว่างราคา หลักประกันในแบบจำลองอุดมคติที่ซื้อขายต่อเนื่องจึงต้องแยกจากผลของโค้ดที่ปรับเป็นงวด ดู [Gap Risk](portfolio-insurance.html#gap-risk) และบทคอร์ส [Designing and calibrating CPPI strategies](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/I3kmk/designing-and-calibrating-cppi-strategies)

<span id="ยก-floor-ตามม-ลค-าส-งส-ดท-เคยม"></span>

<span id="cppi-drawdown"></span>

## ใช้โค้ดเดิมกับ Floor แบบ TIPP

[บท TIPP](portfolio-insurance.html#tipp) อธิบายการยก Floor ตามจุดสูงสุด พร้อมเปรียบเทียบกับ CPPI ที่คง Floor ไว้ ในที่นี้เราจะตรวจว่าส่วน `drawdown` ของฟังก์ชันทำตามกฎนั้นหรือไม่ โดยใช้ Floor เท่ากับ 80% ของมูลค่าพอร์ตสูงสุดที่สังเกตแล้ว หรือ $F_t=0.8P_t$ เมื่อ $P_t$ คือจุดสูงสุดของพอร์ตนี้เอง

เรียก `drawdown=0.20` กับผลตอบแทนหกเดือนชุดเดิม แล้วใช้ `.cummax()` คำนวณจุดสูงสุดจากตารางผลลัพธ์แยกอีกครั้ง หลังเดือนแรกพอร์ตมี 1,048 บาท จึงต้องได้ Floor 838.40 บาท คำสั่ง `assert` ท้ายช่องตรวจ Floor ทุกแถวกับสูตรนี้

```python
cppi_trailing = cppi_path(cppi_risky_returns, drawdown=0.20)
cppi_trailing["peak"] = cppi_trailing["wealth"].cummax()
cppi_trailing["drawdown"] = cppi_trailing["wealth"] / cppi_trailing["peak"] - 1
print(cppi_trailing[["wealth", "floor", "risky_before", "drawdown"]].round(4))
assert np.allclose(cppi_trailing["floor"], 0.8 * cppi_trailing["peak"])
```

พอร์ตนี้จบที่ประมาณ 896.02 บาท และ drawdown ลึกสุดในเจ็ดจุดเวลาประมาณ −16.22% การตั้งเป้าหมาย 20% ไม่ได้ป้องกันการทะลุระหว่างจุดปรับพอร์ต ตัวอย่าง −40% จากหัวข้อก่อนยังทำให้ floor เริ่มต้น 800 บาทถูกทะลุได้

ในฟังก์ชัน เราอัปเดต `peak` หลังคำนวณมูลค่าปลายงวด และใช้มันกำหนด Floor สำหรับรอบถัดไป จึงไม่ใช้จุดสูงสุดจากอนาคต

เมื่อใช้ `drawdown` ฟังก์ชันนี้จะให้ floor เริ่มต้นจาก `(1-drawdown)*start` และใช้กติกา peak แทน `floor_fraction` ตลอดเส้นทาง จึงไม่ควรส่งสองเป้าหมายที่ขัดกันแล้วเข้าใจว่าฟังก์ชันกำลังบังคับใช้ทั้งคู่

### ถ้ากำหนดเพดานความมั่งคั่งด้วย

คอร์สยังต่อยอดไปถึงการลดความเสี่ยงเมื่อพอร์ตเข้าใกล้เพดานที่ตั้งไว้ สมมติ floor 800 บาท และเพดาน $U=1{,}400$ บาท รูปแบบตัวอย่างที่ใช้ตัวคูณเท่ากันทั้งสองด้านอาจกำหนดวงเงินเป็น

$$
E(V)=\min\bigl(m(V-F),m(U-V)\bigr)
$$

เมื่อ $F\leq V\leq U$ แล้วจำกัดวงเงินจริงให้อยู่ในช่วง 0 ถึง $V$ อีกครั้ง ด้วย $m=3$ ที่ $V=900$ จะเสี่ยง 300 บาท ที่ $V=1{,}100$ เสี่ยง 900 บาท และที่ $V=1{,}300$ กลับมาเสี่ยง 300 บาท จุดที่สองสูตรเท่ากันคือ $(F+U)/2=1{,}100$ บาท

ส่วนนี้เป็นการแสดงกติกาเพิ่มเติม ไม่ได้รวมอยู่ใน `cppi_path` ด้านบน และเมื่อปรับพอร์ตเป็นงวดก็อาจทะลุขอบทั้งสองด้านได้เช่นกัน อย่าอ่าน “cap” ว่าเป็นการรับรองผลตอบแทนสูงสุดของการลงทุนจริง

<span id="cppi-practice"></span>

## แบบฝึกหัด: คำนวณก่อนรัน

คำถามต่อไปนี้เป็นโจทย์ฝึกที่สร้างขึ้นใหม่ ใช้เงินสดผลตอบแทน 0% และไม่มีต้นทุน เว้นแต่ระบุเป็นอย่างอื่น

**1.** พอร์ต 1,200 บาท floor 900 บาท และ $m=2$ จะถือเงินเสี่ยงกี่บาท? หากสินทรัพย์เสี่ยงให้ −10% พอร์ตจะเหลือเท่าไร และวงเงินเสี่ยงงวดถัดไปเป็นเท่าไร?

<details><summary>เปิดคำตอบข้อ 1</summary>

cushion 300 บาท จึงเสี่ยง 600 บาทและถือเงินสด 600 บาท หลังขาดทุนได้ $600(0.9)+600=1{,}140$ บาท cushion ใหม่ 240 บาท จึงเสี่ยง 480 บาทในงวดถัดไป

</details>

**2.** พอร์ต 1,000 บาท floor 800 บาท ตัวคูณ 8 สูตรดิบให้เสี่ยง 1,600 บาท แต่ฟังก์ชันในบทจะใช้เท่าไร?

<details><summary>เปิดคำตอบข้อ 2</summary>

ใช้ 1,000 บาท เพราะจำกัดวงเงินเสี่ยงไม่ให้เกินมูลค่าพอร์ต จึงถือสินทรัพย์เสี่ยง 100% และไม่กู้เพิ่ม ในกรณีนี้จะใช้เกณฑ์ $-1/m$ จากสูตรที่ยังไม่ชนขอบโดยตรงไม่ได้ การลงเกิน 20% จากวงเงินนี้ทำให้ต่ำกว่า 800 บาทแล้ว

</details>

**3.** ใช้ drawdown เป้าหมาย 15% พอร์ตเริ่ม 1,000 บาท โตไป 1,200 บาท แล้วลดมา 1,100 บาท floor หลังแต่ละจุดเป็นเท่าไร?

<details><summary>เปิดคำตอบข้อ 3</summary>

เริ่มที่ 850 บาท จากนั้นยกเป็น 1,020 บาท เมื่อพอร์ตลดมา 1,100 บาท peak ยังเป็น 1,200 บาท floor จึงยังเป็น 1,020 บาท ไม่ลดกลับไปเป็น 935 บาท

</details>

**4.** ถ้าจำลองหนึ่งพันเส้นทางแล้วไม่มีเส้นใดทะลุ floor พอจะสรุปได้หรือไม่ว่ากลยุทธ์ไม่มี gap risk?

<details><summary>เปิดคำตอบข้อ 4</summary>

ยังสรุปไม่ได้ เหตุการณ์อาจมีโอกาสต่ำจนไม่ปรากฏในตัวอย่าง และแบบจำลองอาจไม่สร้างเหตุการณ์บางประเภทเลย ต้องดูทั้งจำนวนเส้นทาง ความถี่ปรับพอร์ต การแจกแจง สมมติฐานสินทรัพย์ปลอดภัย และต้นทุน บท Monte Carlo จะวัดผลจากหลายเส้นทางโดยแยกความไม่แน่นอนเหล่านี้

</details>

<span id="cppi-sources"></span>

## เรียนต่อและตรวจเทียบกับ Notebook ของคอร์ส

หัวข้อในหน้านี้ใช้ [CPPI Part 1](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/TYWck/an-introduction-to-cppi-part-1), [Part 2](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/MNaTt/an-introduction-to-cppi-part-2), [Lab CPPI Part 1](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/3YEcN/lab-session-cppi-and-drawdown-constraints-part1) และ [Part 2](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/mQ8uF/lab-session-cppi-and-drawdown-constraints-part2) เป็นเส้นทางประกอบ และตรวจแนวคิดกับ `lab_119` ของคอร์ส ตัวอย่างและฟังก์ชันในหน้านี้เขียนขึ้นใหม่

เมื่อตรวจเทียบโค้ด ให้ดู convention ก่อนเปรียบเทียบผล โค้ดในหน้านี้มีแถวเงินตั้งต้น คำนวณเงินสดจากอัตราต่อปีแบบทบต้น และเก็บทั้ง floor ก่อนงวดกับหลังงวด เพื่อให้ตรวจลำดับเวลาได้ ผลจึงอาจต่างจากการตัดแถวแรกหรือใช้อัตราต่อปีหาร 12

ขั้นถัดไปคือ [Monte Carlo: สร้างหลายเส้นทางและวัด Shortfall](monte-carlo.html) ซึ่งจะใช้กติกา CPPI เดิมกับผลตอบแทนที่จำลองขึ้น โดยรายงานว่าผลขึ้นกับแบบจำลองอย่างไร
