---
title: ข้อจำกัดของ Diversification — อ่านน้ำหนักและ Correlation ตามเวลา
description: เรียนรู้ความเสี่ยงร่วม น้ำหนักตามมูลค่าตลาด การใช้ข้อมูลที่รู้จริงก่อนลงทุน และ Rolling Correlation ด้วย Python สำหรับผู้เริ่มต้น
---

# ข้อจำกัดของ Diversification

<p class="lead">ถ้าเพิ่มสินทรัพย์ในพอร์ตจาก 5 ตัวเป็น 50 ตัว ความเสี่ยงจะลดลงสิบเท่าหรือไม่?</p>

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

บทนี้เริ่มหัวข้อ Beyond Diversification โดยต่อจากการคำนวณ [ผลตอบแทนพอร์ต](returns.html) และการใช้ covariance ในการจัดพอร์ต เราจะสร้างพอร์ตตามมูลค่าตลาด ตรวจว่าน้ำหนักเกิดขึ้นก่อนผลตอบแทนที่นำมาคูณ แล้วติดตาม correlation ด้วยหน้าต่างข้อมูลที่เลื่อนไปตามเวลา จากนั้นจึงไปเรียน [CPPI](cppi-dynamic-allocation.html) ซึ่งเปลี่ยนสัดส่วนสินทรัพย์ตามเงินที่พอร์ตยังยอมเสี่ยงได้

ข้อมูลทั้งหมดในหน้านี้เป็น **ตัวอย่างสมมติที่สร้างขึ้นเพื่อเรียนรู้** ไม่มีผลการลงทุนจริง โค้ดใช้ NumPy และ pandas เปิด Notebook ใหม่แล้วรัน Python ทุกช่องจากบนลงล่างได้โดยไม่ต้องใช้ CSV ของคอร์ส

| ช่วงเรียน | สิ่งที่จะทำได้ |
|---|---|
| [ความเสี่ยงร่วม](#common-risk) | อธิบายว่าเหตุใดเพิ่มจำนวนสินทรัพย์แล้วความเสี่ยงอาจยังเหลือ |
| [น้ำหนักตามมูลค่าตลาด](#cap-weights) | แปลงมูลค่าตลาดเป็นน้ำหนักที่รวมกันได้ 100% |
| [เวลาและข้อมูล](#weight-timing) | ใช้น้ำหนักที่รู้ก่อนเริ่มงวด และตรวจข้อมูลที่ขาด |
| [Rolling Correlation](#rolling-correlation) | อ่านผลของหน้าต่างย้อนหลังและเฉลี่ยเฉพาะคู่ที่ไม่ซ้ำ |
| [สิ่งที่ข้อมูลยังไม่ตอบ](#interpretation) | แยกความสัมพันธ์ในตัวอย่างออกจากคำทำนายหรือเหตุและผล |

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

<span id="common-risk"></span>

## ความเสี่ยงที่เฉลี่ยออกได้ กับความเสี่ยงที่มาพร้อมกัน

สมมติว่าผลตอบแทนของสินทรัพย์ตัวที่ $i$ ประกอบด้วยสองส่วน

$$
R_i=M+\varepsilon_i
$$

$M$ คือส่วนที่ทุกสินทรัพย์ได้รับร่วมกัน เช่น ความเปลี่ยนแปลงของเศรษฐกิจ ส่วน $\varepsilon_i$ คือส่วนเฉพาะของสินทรัพย์นั้น เช่น เหตุการณ์ในบริษัท ในแบบจำลองเล็กนี้เรากำหนดให้ทุกตัวรับ $M$ ด้วยขนาดเท่ากัน และส่วนเฉพาะไม่สัมพันธ์กันเองหรือกับ $M$ สมมติฐานเหล่านี้ช่วยให้แยกผลของการเพิ่มจำนวนสินทรัพย์ได้ ไม่ใช่ข้อเท็จจริงของหุ้นทุกตัว

ถ้าลงทุนเท่ากัน $n$ ตัว ส่วนร่วมยังคงเป็น $M$ เพราะน้ำหนักรวมกันได้ 1 แต่ส่วนเฉพาะถูกนำมาเฉลี่ย หากแต่ละส่วนเฉพาะมี variance เท่ากัน ความแปรปรวนของพอร์ตเป็น

$$
\sigma_p^2=\sigma_M^2+\frac{\sigma_\varepsilon^2}{n}
$$

ตัวอย่างกำหนด SD ของส่วนร่วมเป็น 12% ต่อปี และ SD ของส่วนเฉพาะเป็น 20% ต่อปี เมื่อมีสินทรัพย์หนึ่งตัว SD รวมคือ $\sqrt{0.12^2+0.20^2}=23.32\%$ เมื่อมีสี่ตัว ส่วนเฉพาะเหลือ variance $0.20^2/4$ จึงได้ SD รวม 15.62% ต่อปี ไม่ใช่ $23.32\%/4$

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

asset_counts = np.array([1, 4, 25, 100])
common_sd = 0.12
specific_sd = 0.20
portfolio_sd = np.sqrt(common_sd**2 + specific_sd**2 / asset_counts)
print(pd.DataFrame({
    "Number of assets": asset_counts,
    "Annual SD (%)": portfolio_sd * 100
}).round(4))
```

| จำนวนสินทรัพย์ | SD พอร์ตต่อปี |
|---:|---:|
| 1 | 23.3238% |
| 4 | 15.6205% |
| 25 | 12.6491% |
| 100 | 12.1655% |

`np.array` เก็บจำนวนสินทรัพย์เป็นชุดตัวเลข `**2` คือยกกำลังสอง และ `np.sqrt` คือถอดรากที่สอง NumPy คำนวณสูตรเดียวกันให้ทุกจำนวนในชุด ส่วน `round(4)` ปัดเฉพาะตารางที่แสดง ไม่ได้แก้ตัวแปรต้นฉบับ

เมื่อ $n$ มากขึ้น ส่วน $\sigma_\varepsilon^2/n$ เข้าใกล้ศูนย์ แต่ SD ของพอร์ตในแบบจำลองนี้เข้าใกล้ 12% ความเสี่ยงร่วมยังอยู่ หากสินทรัพย์รับปัจจัยร่วมด้วยน้ำหนักต่างกัน หรือส่วนเฉพาะยังสัมพันธ์กัน สูตรจะต้องเปลี่ยนตามโครงสร้างนั้น ประเด็นนี้สอดคล้องกับบท [Limits of diversification](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/nwiWd/limits-of-diversification) ของคอร์ส

<span id="cap-weights"></span>

## สร้างน้ำหนักตามมูลค่าตลาดทีละคอลัมน์

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

ตัวอย่างมีสามกลุ่มชื่อ A, B และ C หน่วยเงินคือ **ล้านบาท**

| กลุ่ม | จำนวนบริษัท | มูลค่าตลาดเฉลี่ยต่อบริษัท | มูลค่าตลาดรวม | น้ำหนัก |
|---|---:|---:|---:|---:|
| A | 2 | 50 | 100 | 50% |
| B | 3 | 20 | 60 | 30% |
| C | 5 | 8 | 40 | 20% |
| รวม | 10 | — | 200 | 100% |

กลุ่ม C มีบริษัทมากที่สุด แต่ไม่ได้มีน้ำหนักมากที่สุด เพราะน้ำหนักนี้อิงมูลค่าตลาด ไม่ใช่จำนวนบริษัท สูตรสำหรับกลุ่ม $i$ คือ

$$
w_i=\frac{\text{มูลค่าตลาดของกลุ่ม }i}{\text{มูลค่าตลาดรวมทุกกลุ่ม}}
$$

```python
firms = pd.Series({"A": 2, "B": 3, "C": 5})
average_cap = pd.Series({"A": 50.0, "B": 20.0, "C": 8.0})
industry_cap = firms * average_cap
cap_weights = industry_cap / industry_cap.sum()
print(cap_weights)
print("Sum of weights:", cap_weights.sum())
```

`Series` เป็นคอลัมน์ข้อมูลที่มีชื่อกำกับ ค่า A ใน `firms` จึงคูณกับค่า A ใน `average_cap` ตามชื่อ ไม่ใช่เพียงตำแหน่ง หากชื่อสะกดไม่ตรงกัน pandas จะจัดแนวแล้วอาจเกิด `NaN` ซึ่งหมายถึงค่าที่ไม่มี ห้ามเปลี่ยน `NaN` เป็นศูนย์อัตโนมัติเพียงเพื่อให้สูตรรันผ่าน

สมมติว่าต้นเดือนเรารู้น้ำหนัก 50%, 30%, 20% และผลตอบแทนตลอดเดือนของสามกลุ่มเป็น +10%, −5%, +2% ผลตอบแทนพอร์ตเท่ากับ

$$
R_p=0.50(0.10)+0.30(-0.05)+0.20(0.02)=0.039=3.9\%
$$

```python
one_month_returns = pd.Series({"A": 0.10, "B": -0.05, "C": 0.02})
contribution = cap_weights * one_month_returns
print("Contribution in percentage points:")
print((contribution * 100).round(2))
print("Portfolio return (%):", contribution.sum() * 100)
```

กลุ่ม A ช่วยผลตอบแทนพอร์ต 5 จุดเปอร์เซ็นต์ กลุ่ม B ลดลง 1.5 จุดเปอร์เซ็นต์ และ C เพิ่ม 0.4 จุดเปอร์เซ็นต์ รวมเป็น 3.9% คำว่า contribution จึงมีหน่วยเป็นส่วนของผลตอบแทนพอร์ต ไม่ใช่ผลตอบแทนของกลุ่มนั้นโดยลำพัง

<span id="weight-timing"></span>

## น้ำหนักที่จะใช้ ต้องรู้ก่อนผลตอบแทนงวดนั้น

หลังเดือนที่เพิ่งคำนวณ ในตัวอย่างนี้กำหนดว่าไม่มีเงินปันผลหรือการจ่ายเงินออก จำนวนหุ้นและองค์ประกอบไม่เปลี่ยน จึงให้ price return เท่ากับ total return และมูลค่าตลาดสามกลุ่มจะเป็น 110, 57 และ 40.8 ล้านบาท น้ำหนักปลายเดือนจึงเปลี่ยนเป็นประมาณ 52.94%, 27.43%, 19.63% เพราะกลุ่ม A โตเร็วกว่า การเปลี่ยนน้ำหนักลักษณะนี้เกิดจากราคาได้เอง การถือพอร์ตตามมูลค่าตลาดจึงต่างจากการขายซื้อเพื่อกลับไปเป็น 50/30/20 ทุกเดือน

ถ้านำน้ำหนัก **ปลายเดือน** กลับไปคูณผลตอบแทนของ **เดือนที่เพิ่งผ่านไป** เราใช้ข้อมูลที่รู้หลังเกิดผลตอบแทนแล้ว ตัวอย่างนี้จะได้ 4.3147% แทน 3.9% ความคลาดเคลื่อนชนิดนี้เรียกว่า **look-ahead bias** เมื่อเป็นส่วนหนึ่งของการทดสอบกลยุทธ์ย้อนหลัง

```python
end_cap = industry_cap * (1 + one_month_returns)
end_weights = end_cap / end_cap.sum()
wrong_same_month_return = (end_weights * one_month_returns).sum()
print("End weights (%):")
print((end_weights * 100).round(4))
print("Wrong same-month result (%):", round(wrong_same_month_return * 100, 4))
```

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

```python
cap_dates = pd.period_range("2025-12", periods=4, freq="M")
end_month_cap = pd.DataFrame(
    [[100.0, 60.0, 40.0],
     [110.0, 57.0, 40.8],
     [104.5, 59.85, 40.8],
     [108.68, 61.047, 39.576]],
    index=cap_dates,
    columns=["A", "B", "C"]
)
monthly_returns = pd.DataFrame(
    [[0.10, -0.05, 0.02],
     [-0.05, 0.05, 0.00],
     [0.04, 0.02, -0.03]],
    index=cap_dates[1:],
    columns=end_month_cap.columns
)

end_month_weights = end_month_cap.div(end_month_cap.sum(axis=1), axis=0)
start_month_weights = end_month_weights.shift(1).reindex(monthly_returns.index)
print(start_month_weights.round(4))
```

`pd.period_range(..., freq="M")` สร้างชื่อช่วงเวลาเป็นเดือน `axis=1` ใน `sum` ให้บวกข้ามคอลัมน์ A, B, C เพื่อหามูลค่ารวมของแต่ละเดือน ส่วน `div(..., axis=0)` จับยอดรวมนั้นกับแถวเดือนเดียวกัน แล้วหารทุกคอลัมน์ในแถว

`shift(1)` ย้าย **ค่าของน้ำหนัก** ลงหนึ่งแถว โดยคงป้ายเดือนเดิมไว้ น้ำหนักที่เคยอยู่แถวธันวาคมจึงมาอยู่แถวมกราคม ส่วนน้ำหนักแถวแรกกลายเป็น `NaN` เพราะไม่มีเดือนก่อนหน้าให้ย้ายมา จากนั้น `reindex` เลือกและจัดเรียงให้ตรงกับเดือนในตารางผลตอบแทน ดูรายละเอียดพฤติกรรมของ `shift` ได้ใน [เอกสาร pandas](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.shift.html)

ก่อนคูณให้ตรวจว่าทุกแถวมีข้อมูลครบและน้ำหนักรวมได้ 1 แล้วจึงรวม contribution ของแต่ละเดือน

```python
assert start_month_weights.index.equals(monthly_returns.index)
assert start_month_weights.columns.equals(monthly_returns.columns)
assert not start_month_weights.isna().any().any()
assert not monthly_returns.isna().any().any()
assert np.allclose(start_month_weights.sum(axis=1), 1.0)

weighted_returns = start_month_weights * monthly_returns
market_returns = weighted_returns.sum(axis=1, min_count=3)
print((market_returns * 100).round(4))
```

ผลตอบแทนเดือนมกราคม กุมภาพันธ์ และมีนาคมคือ 3.9000%, −1.2753%, 2.0244% ตามลำดับ `assert` เป็นคำสั่งให้หยุดพร้อมข้อความผิดพลาดเมื่อเงื่อนไขไม่จริง `np.allclose` ตรวจว่าตัวเลขใกล้กันมากพอ แทนการบังคับให้ทศนิยมจากคอมพิวเตอร์เท่ากันทุกหลัก ส่วน `min_count=3` กำหนดให้แถวที่รวมต้องมีค่าครบสามกลุ่ม จึงไม่เผลอรายงานผลรวมจากเพียงบางกลุ่ม

การเลื่อนหนึ่งแถวเหมาะกับตารางนี้เพราะเราระบุแล้วว่าข้อมูลมูลค่าตลาดเป็นค่าปลายเดือน หากแหล่งข้อมูลให้ **น้ำหนักต้นเดือน** มาอยู่แล้ว การเลื่อนอีกครั้งจะทำให้ใช้ข้อมูลเก่าเกินไป และถ้าข้อมูลประกาศล่าช้ากว่าวันอ้างอิง ต้องอิงวันที่ผู้ลงทุนเข้าถึงข้อมูลได้จริง การเขียน `shift(1)` จึงต้องตามหลังการอ่านนิยามและเวลาของข้อมูล

<span id="rolling-correlation"></span>

## Rolling Correlation: คำนวณใหม่ด้วยข้อมูลย้อนหลังแต่ละช่วง

**Correlation** วัดความสัมพันธ์เชิงเส้นของผลตอบแทนสองชุด มีค่าระหว่าง −1 ถึง +1 เมื่อคำนวณได้ ค่าบวกสูงหมายถึงเดือนที่ชุดหนึ่งสูงหรือต่ำกว่าค่าเฉลี่ย มักเป็นเดือนที่อีกชุดอยู่ด้านเดียวกันด้วย ค่า 0 ไม่ได้ยืนยันว่าทั้งสองชุดเป็นอิสระจากกัน

ค่า correlation ตลอดสิบปีรวมทุกช่วงเข้าเป็นตัวเลขเดียว ส่วน **rolling correlation** เลือกข้อมูลย้อนหลังจำนวนคงที่ เช่น 36 เดือน คำนวณหนึ่งครั้ง แล้วเลื่อนหน้าต่างไปหนึ่งเดือน การเปลี่ยนค่าที่เห็นอาจมาจากทั้งความสัมพันธ์ที่เปลี่ยนจริงและความไม่แน่นอนของตัวอย่าง

เพื่อให้ตรวจได้ง่าย เราจะใช้ข้อมูลสมมติ 12 เดือน และหน้าต่างเพียง 4 เดือน ช่วงท้ายตั้งใจสร้างให้สามชุดเคลื่อนไหวคล้ายกันมาก หน้าต่างสั้นนี้เป็นสื่อฝึกอ่านโค้ด ไม่ใช่ข้อเสนอว่าควรประมาณ correlation จริงด้วยข้อมูลสี่จุด

```python
corr_returns = pd.DataFrame({
    "A": [0.02, -0.01, 0.03, 0.00, 0.01, -0.02,
          0.04, -0.03, 0.02, -0.04, 0.01, -0.02],
    "B": [-0.01, 0.03, 0.00, 0.02, -0.02, 0.01,
          0.03, -0.02, 0.025, -0.035, 0.015, -0.015],
    "C": [0.01, 0.02, -0.01, 0.03, 0.00, -0.01,
          0.035, -0.025, 0.015, -0.045, 0.005, -0.025]
}, index=pd.period_range("2025-01", periods=12, freq="M"))

rolling_ab = corr_returns["A"].rolling(4, min_periods=4).corr(corr_returns["B"])
print(rolling_ab.round(4))
```

ค่าของเดือนเมษายนใช้มกราคมถึงเมษายน ค่าเดือนพฤษภาคมใช้กุมภาพันธ์ถึงพฤษภาคม สามเดือนแรกเป็น `NaN` เพราะยังมีข้อมูลไม่ครบสี่เดือน มกราคมถึงเมษายนให้ correlation A–B เท่ากับ −0.9 ส่วนกันยายนถึงธันวาคมให้ +1.0 เพราะช่วงท้ายเรากำหนด B ให้เท่ากับ A บวก 0.005 ทุกเดือน การบวกค่าคงที่ไม่ได้เปลี่ยน correlation

ผลของเดือนเมษายนรู้ได้หลังมีข้อมูลเดือนเมษายนครบแล้ว หากจะนำค่านี้ไปกำหนดน้ำหนักเดือนถัดไป ต้องเลื่อนให้ตรงจังหวะการตัดสินใจด้วย หลักเวลาเดียวกับตาราง cap weights ยังใช้กับตัวชี้วัดนี้

### มีสามสินทรัพย์ เราต้องเฉลี่ยกี่คู่?

สามสินทรัพย์มีคู่ไม่ซ้ำสามคู่ คือ A–B, A–C และ B–C ตาราง correlation เป็นตารางสมมาตร จึงมี B–A ซ้ำกับ A–B และมี A–A, B–B, C–C บนแนวทแยงซึ่งเท่ากับ 1 เมื่อชุดนั้นมี variance ไม่เป็นศูนย์

การเฉลี่ยทั้งเก้าช่องจะใส่ค่าหนึ่งของแนวทแยงเข้ามา ทำให้ตัวเลขสูงกว่าการเฉลี่ยเฉพาะคู่ระหว่างสินทรัพย์ เราจะเลือกครึ่งบนของตารางและเว้นแนวทแยง

```python
first_window_corr = corr_returns.iloc[:4].corr()
upper_mask = np.triu(np.ones((3, 3), dtype=bool), k=1)
unique_pairs = first_window_corr.to_numpy()[upper_mask]
print(first_window_corr.round(4))
print("Unique pairs:", np.round(unique_pairs, 4))
print("Mean of unique pairs:", round(unique_pairs.mean(), 4))
print("Mean of all nine cells:", round(first_window_corr.to_numpy().mean(), 4))
```

ค่าเฉลี่ยของสามคู่ในหน้าต่างแรกเท่ากับ −0.3713 แต่ถ้าเฉลี่ยทุกช่องจะได้ +0.0858 ซึ่งเปลี่ยนแม้กระทั่งเครื่องหมาย `iloc[:4]` เลือกสี่แถวแรกตามตำแหน่ง `to_numpy()` นำตัวเลขออกจากตาราง pandas และ `upper_mask` เป็นตาราง True/False ที่เลือกเฉพาะช่องด้านบน คำสั่ง `np.triu(..., k=1)` ขยับแนวเลือกขึ้นไปหนึ่งระดับเพื่อไม่เอาแนวทแยง

หากต้องการค่าเฉลี่ยนี้ทุกเดือน เราเริ่มจากวนหน้าต่างทีละช่วงได้ การเขียนตรง ๆ ช่วยให้ตรวจว่าแต่ละค่ามาจากข้อมูลเดือนไหน

```python
window_size = 4
average_corr_values = []
average_corr_dates = []
for end in range(window_size, len(corr_returns) + 1):
    window = corr_returns.iloc[end - window_size:end]
    matrix = window.corr().to_numpy()
    average_corr_values.append(matrix[upper_mask].mean())
    average_corr_dates.append(corr_returns.index[end - 1])

average_corr = pd.Series(average_corr_values, index=average_corr_dates)
print(average_corr.round(4))
```

`range(4, 13)` ให้เลข 4 ถึง 12 โดยไม่รวม 13 และ slice `iloc[0:4]` รวมตำแหน่ง 0, 1, 2, 3 ไม่รวมตำแหน่ง 4 ดังนั้นรอบแรกใช้สี่เดือนแรกและติดป้ายที่เดือนที่สี่ `append` เพิ่มผลหนึ่งค่าไปท้าย list เพื่อประกอบเป็น Series เมื่อครบทุกช่วง

ใน lab ของคอร์สจะพบ `.rolling(...).corr()` ที่คืนหลายตารางซ้อนกัน โดย index มีทั้งเดือนและชื่อสินทรัพย์เรียกว่า **MultiIndex** ตัวอย่างนี้ดึงตารางเดือนเมษายนออกมาเทียบกับที่เราคำนวณเอง

```python
all_rolling_matrices = corr_returns.rolling(4, min_periods=4).corr()
april_matrix = all_rolling_matrices.xs(pd.Period("2025-04", freq="M"), level=0)
assert np.allclose(april_matrix, first_window_corr)
print(april_matrix.round(4))
```

`xs(..., level=0)` เลือกป้ายเดือนจากระดับแรกของ index เหลือแถว A, B, C ภายในเดือนนั้น pandas อธิบายการคำนวณหน้าต่างและผลลัพธ์แบบ pairwise ไว้ใน [Windowing operations](https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html)

<span id="interpretation"></span>

## อ่านผลโดยไม่ให้ correlation ตอบเกินข้อมูล

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

แม้เห็น correlation สูงขึ้นในช่วงผันผวน ก็ยังสรุปไม่ได้ทันทีว่าเกิดการส่งต่อวิกฤตจากสินทรัพย์หนึ่งไปอีกสินทรัพย์หนึ่ง ความแปรปรวนที่เปลี่ยนและการเลือกช่วงข้อมูลมีผลต่อการเปรียบเทียบ correlation ประเด็นนี้ศึกษาโดย Forbes และ Rigobon ใน [No Contagion, Only Interdependence](https://www.nber.org/papers/w7267)

ก่อนนำตารางนี้ไปใช้กับข้อมูลจริง ให้ตรวจสิ่งที่มีผลต่อความหมายของตัวเลขโดยตรง

| จุดตรวจ | ถ้าปล่อยผ่านจะเกิดอะไร |
|---|---|
| ผลตอบแทนใช้วันหรือเดือนเดียวกันหรือไม่ | นำข้อมูลคนละเวลาไปจับคู่แล้วได้ความสัมพันธ์คลาดเคลื่อน |
| ใช้ total return และสกุลเงินเดียวกันหรือไม่ | ปันผลและอัตราแลกเปลี่ยนอาจทำให้กำลังวัดคนละสิ่ง |
| มีค่าขาดหรือสินทรัพย์เพิ่งเริ่มต้นหรือไม่ | แต่ละคู่ใน `.corr()` อาจใช้จำนวนข้อมูลไม่เท่ากัน |
| มีคอลัมน์คงที่ตลอดหน้าต่างหรือไม่ | SD เป็นศูนย์ จึงนิยาม correlation ไม่ได้และได้ `NaN` |
| หน้าต่างสั้นแค่ไหน | ตัวเลขเปลี่ยนมากจากข้อมูลใหม่เพียงจุดเดียวได้ |
| นำค่าของเดือนนี้ไปตัดสินใจเมื่อไร | ใช้ข้อมูลที่ยังไม่รู้ในวันลงทุนได้โดยไม่ตั้งใจ |

ตัวอย่างโค้ดด้านบนไม่มีค่าขาดและทุกคอลัมน์เปลี่ยนแปลง ในงานจริงหากเลือกใช้เฉพาะแถวที่ครบทุกสินทรัพย์ด้วย `.dropna()` ต้องรายงานด้วยว่าเสียข้อมูลไปกี่แถว เพราะการลบแถวอาจทำให้เหลือเฉพาะช่วงเวลาบางประเภท การเติมผลตอบแทนเป็นศูนย์มีความหมายว่า “ราคาหรือมูลค่าไม่เปลี่ยนในงวดนั้น” จึงใช้แทน “เราไม่มีข้อมูล” ไม่ได้

การกระจายสินทรัพย์เป็นการเปลี่ยนโครงสร้างความเสี่ยงของพอร์ต แต่ยังไม่ได้กำหนดว่าพอร์ตยอมสูญเสียได้กี่บาท ในบท [CPPI และ Dynamic Allocation](cppi-dynamic-allocation.html) เราจะเริ่มจากจำนวนเงินขั้นต่ำที่อยากรักษา แล้วแปลงส่วนที่เกินจากเกณฑ์นั้นเป็นวงเงินลงทุนในสินทรัพย์เสี่ยง

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

## แบบฝึกหัด: ตรวจน้ำหนัก เวลา และคู่ข้อมูล

แบบฝึกหัดต่อไปนี้ผู้เขียนสร้างขึ้นใหม่ ให้ลองคำนวณก่อนเปิดคำตอบ

**1.** ถ้ามูลค่าตลาดสามกลุ่มเท่ากับ 80, 60 และ 60 ล้านบาท น้ำหนักเป็นเท่าไร? หากผลตอบแทนเดือนหน้าเท่ากับ −10%, +5%, +5% ผลตอบแทนพอร์ตเป็นเท่าไร?

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

มูลค่ารวม 200 ล้านบาท จึงได้น้ำหนัก 40%, 30%, 30% ผลตอบแทนพอร์ตคือ $0.4(-0.10)+0.3(0.05)+0.3(0.05)=-0.01$ หรือ −1% โดยต้องเป็นน้ำหนักที่มีอยู่ก่อนเริ่มเดือนผลตอบแทน

</details>

**2.** หากมีสินทรัพย์ห้าตัว ตาราง correlation มี 25 ช่อง แล้วคู่ที่ไม่ซ้ำและไม่รวมแนวทแยงมีกี่คู่?

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

มี $5(5-1)/2=10$ คู่ ใช้สูตร $n(n-1)/2$ เพราะแต่ละตัวจับคู่กับอีก $n-1$ ตัว แล้วหารสองเพื่อตัดคู่กลับด้านที่ซ้ำกัน

</details>

**3.** เก็บน้ำหนักไว้ที่แถว “มกราคม” โดยผู้ให้ข้อมูลระบุว่าเป็นน้ำหนัก ณ เช้าวันซื้อขายแรกของมกราคม จำเป็นต้อง `shift(1)` ก่อนคูณผลตอบแทนมกราคมหรือไม่?

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

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

</details>

**4.** ในแบบจำลองส่วนร่วม SD 12% และส่วนเฉพาะ SD 20% หากเพิ่มสินทรัพย์เป็นหนึ่งล้านตัว SD พอร์ตจะเข้าใกล้ศูนย์หรือไม่?

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

ไม่เข้าใกล้ศูนย์ ภายใต้สมมติฐานของตัวอย่าง SD จะเข้าใกล้ 12% เพราะการเฉลี่ยส่วนเฉพาะไม่ได้ลบส่วนร่วม $M$

</details>

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

## แหล่งประกอบบทและเส้นทางในคอร์ส

หัวข้อในหน้านี้สอดคล้องกับ [Limits of diversification](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/nwiWd/limits-of-diversification), [Lab Part 1](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/tek9i/lab-session-limits-of-diversification-part1) และ [Lab Part 2](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/6M9d3/lab-session-limits-of-diversification-part-2) ของ EDHEC บน Coursera โดยตรวจแนวคิดร่วมกับ Notebook `lab_118` ที่แนบมากับคอร์ส ตัวอย่างข้อมูล โค้ด และแบบฝึกหัดในหน้านี้เขียนขึ้นใหม่

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