---
title: เริ่มจัดพอร์ตจากสินทรัพย์สองตัว
description: อ่านน้ำหนักพอร์ต คำนวณผลตอบแทน Covariance และ Correlation แล้วสำรวจพอร์ตสองสินทรัพย์ด้วย Python ตั้งแต่พื้นฐาน
---

# เริ่มจัดพอร์ตจากสินทรัพย์สองตัว

<p class="lead">ถ้าหุ้น A ผันผวน 20% และสินทรัพย์ B ผันผวน 10% การแบ่งเงินลงทุนจะทำให้พอร์ตผันผวนเท่าไร?</p>

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

เนื้อหานี้เรียบเรียงประกอบหัวข้อการกระจายการลงทุนและ Lab 107–108 ตัวเลขทั้งหมดเป็นตัวอย่างสมมติ โค้ดสร้างข้อมูลเองและรันได้โดยไม่ใช้ CSV ของคอร์ส อ่าน[พื้นฐานผลตอบแทนและ Python](returns.html#python-basics) ก่อนหากยังไม่คุ้นกับตัวแปร `Series` และ `DataFrame`

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

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

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

## น้ำหนักพอร์ตคือสัดส่วนเงินต้นช่วง

สมมติว่ามีเงิน 10,000 บาท ซื้อ A มูลค่า 6,000 บาทและ B มูลค่า 4,000 บาท น้ำหนักของ A คือ $w_A=6{,}000/10{,}000=0.6$ และน้ำหนักของ B คือ $w_B=0.4$ ตัว $w$ ย่อจาก weight หน่วยเป็นสัดส่วนของมูลค่าพอร์ต ไม่ใช่จำนวนหุ้น

ถ้า A ราคาหุ้นละ 200 บาทและ B หุ้นละ 20 บาท เราจะถือ A 30 หุ้นและ B 200 หุ้น จำนวนหุ้น B มากกว่าไม่ได้แปลว่ามีน้ำหนักมากกว่า ต้องคูณจำนวนหุ้นด้วยราคาก่อนแล้วจึงเทียบมูลค่า

| สินทรัพย์ | เงินลงทุนต้นเดือน | น้ำหนัก | ผลตอบแทนเดือนนั้น | มูลค่าปลายเดือน |
|---|---:|---:|---:|---:|
| A | 6,000 บาท | 60% | +10% | 6,600 บาท |
| B | 4,000 บาท | 40% | −5% | 3,800 บาท |
| รวม | 10,000 บาท | 100% | +4% | 10,400 บาท |

พอร์ตได้กำไร 400 บาทเทียบเงินต้น 10,000 บาท ผลตอบแทนจึงเป็น 4% อีกวิธีหนึ่งคือถ่วงน้ำหนักผลตอบแทนของแต่ละตัวด้วยน้ำหนักต้นเดือน:

$$
R_p=w_AR_A+w_BR_B
=0.6(0.10)+0.4(-0.05)=0.04.
$$

ตัวห้อย $p$ หมายถึง portfolio หรือพอร์ต ตัวเลขผลตอบแทนในสูตรต้องเป็นทศนิยม เช่น 10% ใช้ `0.10` ผลตอบแทนแต่ละตัวต้องครอบคลุมช่วงเวลาเดียวกันและนับปันผลหรือกระแสเงินสดอย่างสอดคล้องกัน ตัวอย่างนี้ไม่มีฝากถอน ภาษี ค่าธรรมเนียม หรือซื้อขายระหว่างเดือน

เปิด Notebook ใหม่แล้วรันช่องตามลำดับ `pd.Series` เก็บค่าพร้อมชื่อ A/B และ `.sum()` รวมค่าทั้งชุด การคูณ Series จะจับคู่ตามชื่อสินทรัพย์

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

start_values = pd.Series({"A": 6000.0, "B": 4000.0})
one_month = pd.Series({"A": 0.10, "B": -0.05})
weights = start_values / start_values.sum()
end_values = start_values * (1 + one_month)
portfolio_return = (weights * one_month).sum()
print(f"Portfolio return: {portfolio_return:.2%}")
print(f"End value: {end_values.sum():,.2f}")
```

ได้ `4.00%` และ `10,400.00` รูปแบบ `:.2%` แสดงทศนิยมเป็นเปอร์เซ็นต์สองตำแหน่ง ส่วน `:,.2f` แสดงจำนวนเงินโดยใส่จุลภาคคั่นหลักพันและทศนิยมสองตำแหน่ง ค่าที่ใช้คำนวณยังคงเป็นตัวเลขเดิม

### น้ำหนักรวมหนึ่ง และความหมายของการขายชอร์ต

ในบทนี้เริ่มจากข้อกำหนด $w_A+w_B=1$ และ $w_A,w_B\geq0$ เรียกว่า fully invested, long-only: ใช้เงินทั้งหมดกับสินทรัพย์ที่อยู่ในรายการและไม่มีสถานะขายชอร์ต น้ำหนักศูนย์ทำได้ แปลว่าไม่ได้ถือสินทรัพย์นั้น หากมีเงินสดต้องใส่เงินสดเป็นอีกสินทรัพย์หนึ่ง หรือระบุแยกว่ารายการที่กำลังพิจารณาเป็นเพียงส่วนหนึ่งของพอร์ต

น้ำหนัก $1.2$ กับ $-0.2$ รวมเป็นหนึ่งเช่นกัน แต่ความหมายเปลี่ยนไป เราถือสินทรัพย์แรก 120% ของเงินทุนและมีสถานะ short สินทรัพย์ที่สอง 20% การขายชอร์ตต้องมีเงื่อนไขการยืม หลักประกัน และต้นทุนเพิ่มเติม ข้อกำหนดน้ำหนักรวมหนึ่งเพียงข้อเดียวจึงยังไม่ได้ห้ามการลงทุนเกินเงินทุน

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

## เดือนถัดไปน้ำหนักเปลี่ยน แม้เราไม่ได้ซื้อขาย

จากตารางเมื่อสิ้นเดือน A มีมูลค่า 6,600 บาทและ B 3,800 บาท น้ำหนักใหม่ของ A จึงเป็น $6{,}600/10{,}400\approx63.46\%$ และ B ประมาณ 36.54% สินทรัพย์ที่ขึ้นมากกว่าจะกินสัดส่วนของพอร์ตเพิ่มเอง

```python
drifted_weights = end_values / end_values.sum()
next_returns = pd.Series({"A": -0.10, "B": 0.05})
next_buy_hold = (drifted_weights * next_returns).sum()
next_rebalanced = (weights * next_returns).sum()
print((drifted_weights * 100).round(4))
print(f"Next month, buy and hold: {next_buy_hold:.4%}")
print(f"Next month, reset to 60/40: {next_rebalanced:.4%}")
```

น้ำหนักกลายเป็น A 63.4615% และ B 36.5385% ถ้าเดือนถัดไป A ได้ −10% และ B +5% การถือเดิมให้ผลตอบแทนเดือนนั้น −4.5192% ส่วนการขายบางส่วนของ A แล้วซื้อ B กลับไป 60/40 ก่อนเริ่มเดือนให้ −4.0000% ก่อนต้นทุนซื้อขาย

การคำนวณ $0.6R_A+0.4R_B$ ทุกเดือนจึงแฝงสมมติฐานว่าปรับพอร์ตกลับสู่น้ำหนักเดิมทุกต้นเดือน หรือ **rebalancing** หากตั้งใจจำลอง buy and hold ต้องปล่อยให้น้ำหนักเปลี่ยนตามมูลค่า ห้ามนำสองนโยบายมาปนกันเพราะชื่อพอร์ตตอนเริ่มเหมือนกัน

<span id="two-asset-data"></span>

## ความผันผวนต้องดูผลตอบแทนที่เกิดในเดือนเดียวกัน

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

```python
monthly = pd.DataFrame({
    "A": [0.02, 0.04, -0.02, 0.00],
    "B": [0.01, 0.00, 0.03, 0.00]
}, index=["M1", "M2", "M3", "M4"])
monthly_weights = pd.Series({"A": 0.60, "B": 0.40})
monthly_portfolio = monthly.mul(monthly_weights, axis="columns").sum(axis=1)
print((monthly * 100).round(2))
print((monthly_portfolio * 100).round(2))
```

`.mul(..., axis="columns")` ให้คูณน้ำหนักตามชื่อคอลัมน์ และ `.sum(axis=1)` รวมข้ามคอลัมน์ในแต่ละแถว จึงได้ผลตอบแทนพอร์ต M1 ถึง M4 เป็น 1.6%, 2.4%, 0%, 0% เราถือว่ารีบาลานซ์กลับ 60/40 ทุกต้นเดือนและไม่มีต้นทุน

A และ B มีค่าเฉลี่ย 1% ต่อเดือนเหมือนกัน แต่ A ขึ้นลงมากกว่า เมื่อใช้ sample SD ซึ่งหาร variance ด้วย $n-1=3$ จะได้ SD ของ A ประมาณ 2.5820% และ B ประมาณ 1.4142% ต่อเดือน

ถ้าเอา SD มาถ่วงน้ำหนักตรง ๆ จะได้ $0.6(2.5820\%)+0.4(1.4142\%)\approx2.1149\%$ แต่ SD ที่คำนวณจากผลตอบแทนพอร์ตสี่เดือนจริงได้เพียง 1.2% ต่อเดือน ส่วนต่างเกิดจากการเคลื่อนไหวร่วมกันของสินทรัพย์: ในบางเดือนกำไรของตัวหนึ่งช่วยหักล้างขาดทุนของอีกตัว

<span id="covariance"></span>

## Covariance เริ่มจากผลคูณของส่วนต่างจากค่าเฉลี่ย

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

| เดือน | $R_A-\bar R_A$ | $R_B-\bar R_B$ | ผลคูณส่วนต่าง |
|---|---:|---:|---:|
| M1 | 0.01 | 0.00 | 0.0000 |
| M2 | 0.03 | −0.01 | −0.0003 |
| M3 | −0.03 | 0.02 | −0.0006 |
| M4 | −0.01 | −0.01 | 0.0001 |
| รวม | 0 | 0 | −0.0008 |

ถ้าส่วนต่างทั้งคู่มีเครื่องหมายเดียวกัน ผลคูณเป็นบวก หากเครื่องหมายตรงข้าม ผลคูณเป็นลบ sample covariance จึงเท่ากับผลรวมดังกล่าวหารด้วย $n-1$:

$$
s_{AB}=\frac{1}{n-1}\sum_{t=1}^n
(R_{A,t}-\bar R_A)(R_{B,t}-\bar R_B)
=\frac{-0.0008}{3}\approx-0.000266667.
$$

ตัว $t$ บอกเดือน ส่วน $\bar R_A$ และ $\bar R_B$ คือค่าเฉลี่ยของแต่ละคอลัมน์ Covariance มีหน่วยผลตอบแทนยกกำลังสอง เมื่อข้อมูลเป็นทศนิยม อย่าอ่าน −0.000266667 ว่าเป็นผลตอบแทนขาดทุน −0.0267% มันเป็นตัววัดการเคลื่อนไหวร่วมกัน

```python
centered = monthly - monthly.mean()
manual_covariance = (centered["A"] * centered["B"]).sum() / (len(monthly) - 1)
cov_monthly = monthly.cov(ddof=1)
print(f"Manual covariance: {manual_covariance:.9f}")
print(cov_monthly.round(9))
```

ตาราง covariance ที่ได้มีลักษณะนี้:

$$
\widehat\Sigma_m=
\begin{bmatrix}
0.000666667 & -0.000266667\\
-0.000266667 & 0.000200000
\end{bmatrix}.
$$

แนวทแยง A/A และ B/B คือ variance ของสินทรัพย์นั้น เพราะเป็นการคูณส่วนต่างกับตัวเอง ช่อง A/B กับ B/A เท่ากันเพราะสลับลำดับการคูณได้ เมื่อนำ covariance ของทุกคู่มาเรียงเป็นตาราง เราเรียกตารางนี้ว่า **covariance matrix** เครื่องหมายหมวกบน $\widehat\Sigma$ เตือนว่านี่เป็นค่าประมาณจากตัวอย่าง

`DataFrame.cov()` ใช้ข้อมูลที่จับคู่ได้ในแต่ละคู่คอลัมน์ หากมีข้อมูลหาย จำนวนเดือนของแต่ละคู่จึงอาจต่างกัน การใช้ covariance matrix ที่สร้างจากคนละช่วงอาจทำให้คุณสมบัติที่ต้องการสำหรับการจัดพอร์ตเสียไป ในบทนี้ข้อมูลครบทุกแถว จึงใช้สี่เดือนเดียวกันทั้งหมด [pandas: DataFrame.cov](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.cov.html)

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

## Correlation ทำให้ความสัมพันธ์ไม่มีหน่วย

Covariance ขึ้นกับขนาดความผันผวนของทั้งสองตัว เพื่ออ่านความสัมพันธ์บนมาตราส่วนเดียวกัน เราหารด้วย SD ของ A คูณ SD ของ B:

$$
\rho_{AB}=\frac{s_{AB}}{s_As_B}.
$$

อักษรกรีก $\rho$ อ่านว่า “โร” ค่าอยู่ระหว่าง −1 ถึง +1 เมื่อ SD ทั้งสองเป็นบวก จากตัวอย่างจะได้

$$
\rho_{AB}
=\frac{-0.000266667}{0.025819889\times0.014142136}
\approx-0.730297.
$$

```python
asset_sd = monthly.std(ddof=1)
manual_correlation = manual_covariance / (asset_sd["A"] * asset_sd["B"])
print(f"Correlation: {manual_correlation:.6f}")
print(monthly.corr().round(6))
```

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

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

### ตัวอย่างสั้นที่ correlation ศูนย์แต่ยังสัมพันธ์กัน

ให้ A มีค่า −1, 0, 1 โดยมีโอกาสเท่ากัน และกำหนด B เท่ากับ A ยกกำลังสอง ดังนั้น B เป็น 1, 0, 1 ถ้ารู้ A จะรู้ B ทันที จึงไม่เป็นอิสระ แต่ correlation เท่ากับศูนย์เพราะผลคูณส่วนต่างทางซ้ายและขวาหักล้างกัน ตัวอย่างนี้เป็นตัวเลขสาธิตความสัมพันธ์ ไม่ใช่ผลตอบแทนการลงทุน

```python
x = np.array([-1.0, 0.0, 1.0])
y = x ** 2
print(np.corrcoef(x, y).round(6))
```

ช่องนอกแนวทแยงของตารางเป็นศูนย์ การเขียน `** 2` หมายถึงยกกำลังสองทุกสมาชิก และ `np.corrcoef` คืนตาราง correlation ของตัวแปรที่ส่งเข้าไป

<span id="portfolio-variance"></span>

## คำนวณ variance ของพอร์ตสองสินทรัพย์

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

$$
\sigma_p^2
=w_A^2\sigma_A^2+w_B^2\sigma_B^2
+2w_Aw_B\operatorname{Cov}(R_A,R_B).
$$

ใช้ความสัมพันธ์ $\operatorname{Cov}(R_A,R_B)=\rho_{AB}\sigma_A\sigma_B$ จะได้อีกแบบหนึ่ง:

$$
\sigma_p=\sqrt{
 w_A^2\sigma_A^2+w_B^2\sigma_B^2
 +2w_Aw_B\rho_{AB}\sigma_A\sigma_B}.
$$

สองพจน์แรกคิดความผันผวนของแต่ละส่วนที่ถือ ส่วนพจน์สุดท้ายคิดว่าทั้งสองเคลื่อนร่วมกันอย่างไร หาก covariance ติดลบ พจน์นั้นช่วยลด variance ของผลรวม เราต้องคำนวณ variance ก่อนแล้วค่อยถอดรากที่สองกลับเป็น SD [CFA Institute: Portfolio Mathematics](https://www.cfainstitute.org/insights/professional-learning/refresher-readings/2026/portfolio-mathematics)

ลองตรวจสูตรกับข้อมูลสี่เดือน โดย `.loc["A", "B"]` เลือก covariance แถว A คอลัมน์ B ตามชื่อ

```python
wa, wb = 0.60, 0.40
variance_by_formula = (
    wa ** 2 * cov_monthly.loc["A", "A"]
    + wb ** 2 * cov_monthly.loc["B", "B"]
    + 2 * wa * wb * cov_monthly.loc["A", "B"]
)
sd_by_formula = np.sqrt(variance_by_formula)
sd_from_returns = monthly_portfolio.std(ddof=1)
print(f"Formula: {sd_by_formula:.4%}")
print(f"Direct calculation: {sd_from_returns:.4%}")
print(np.isclose(sd_by_formula, sd_from_returns))
```

ทั้งสองวิธีได้ 1.2000% ต่อเดือน และ `True` จาก `np.isclose` ซึ่งเทียบตัวเลขภายในความคลาดเคลื่อนทศนิยมของคอมพิวเตอร์ การคำนวณจากผลตอบแทนพอร์ตโดยตรงจึงใช้ตรวจคำตอบจากสูตร covariance ได้

<span id="frequency-consistency"></span>

## วางหน่วยเวลาให้ตรงกันก่อนเปรียบเทียบพอร์ต

ในแบบจำลองหนึ่งช่วงเวลา expected return และ covariance ต้องหมายถึงช่วงเดียวกัน หากแกนนอนเป็น volatility ต่อเดือนแต่แกนตั้งเป็น expected return ต่อปี ต้องเปลี่ยนให้สอดคล้องกันก่อนตีความความชันหรือคำนวณ Sharpe ratio

ภายใต้สมมติฐานว่าผลตอบแทนรายเดือนไม่มี covariance ข้ามเวลาและความเสี่ยงต่อเดือนคงที่ variance ของ ผลรวม สิบสองเดือนเท่ากับ 12 เท่าของ variance รายเดือน ดังนั้น convention annualization ที่ใช้ทั่วไปคือ

$$
\widehat\Sigma_{\mathrm{ann}}=12\widehat\Sigma_m,
\qquad
\widehat\sigma_{\mathrm{ann}}=\sqrt{12}\widehat\sigma_m.
$$

สำหรับ simple returns ที่ต้องทบต้น ผลตอบแทนหนึ่งปีเป็นผลคูณของ gross returns จึงไม่ได้มี variance เท่ากับสูตรผลรวมนี้อย่างแม่นยำเสมอ การคูณ 12 และ $\sqrt{12}$ เป็น convention หรือการประมาณที่ต้องบอกสมมติฐาน หากมี autocorrelation ต้องนับ covariance ข้ามเวลาด้วย

ค่าเฉลี่ยเลขคณิตรายเดือนคูณ 12 ใช้แสดง mean ใน convention รายปีได้ ส่วน CAGR ตอบการเติบโตแบบทบต้นที่เกิดขึ้นจริง และไม่ได้เป็นตัวเดียวกับ expected arithmetic return ในสูตร $\mu_p=\sum_iw_i\mu_i$ การเอา CAGR ของแต่ละสินทรัพย์มาถ่วงน้ำหนักจึงไม่รับประกันว่าจะได้ CAGR ของพอร์ต

```python
mean_annual_convention = monthly.mean() * 12
cov_annual_convention = cov_monthly * 12
sd_annual_convention = monthly.std(ddof=1) * np.sqrt(12)
portfolio_sd_annual = monthly_portfolio.std(ddof=1) * np.sqrt(12)
print((mean_annual_convention * 100).round(4))
print((sd_annual_convention * 100).round(4))
print(f"Portfolio annualized SD: {portfolio_sd_annual:.4%}")
```

ค่าเฉลี่ยตาม convention เท่ากับ 12% ต่อปีทั้งสองตัว SD ของ A ประมาณ 8.9443% และ B 4.8990% ต่อปี ส่วนพอร์ต 4.1569% ตัวเลขเหล่านี้แปลงจากตัวอย่างเพียงสี่เดือนเพื่อฝึกหน่วย ไม่มีข้อมูลครบหนึ่งปีหรือหลักฐานว่าพารามิเตอร์จะคงอยู่

<span id="two-asset-frontier"></span>

## ลองน้ำหนักหลายแบบบนแบบจำลองต่อปี

ตอนนี้เปลี่ยนจากข้อมูลสี่เดือนมาเป็น พารามิเตอร์สมมติต่อปี ชุดใหม่: A มี expected arithmetic return 8% และ SD 20%, B มี expected return 4% และ SD 10%, correlation เท่ากับ 0.25 เราใช้ตัวเลขเหล่านี้เป็นข้อมูลเข้า ไม่ได้ประมาณมาจากสี่เดือนด้านบน

พอร์ต 60/40 จึงมี expected return $0.6(8\%)+0.4(4\%)=6.4\%$ และ variance

$$
0.6^2(0.20)^2+0.4^2(0.10)^2
+2(0.6)(0.4)(0.25)(0.20)(0.10)=0.0184.
$$

เมื่อถอดรากได้ SD ประมาณ 13.5647% ต่อปี ซึ่งต่ำกว่า SD เฉลี่ยถ่วงน้ำหนัก $0.6(20\%)+0.4(10\%)=16\%$ เพราะ correlation ต่ำกว่า +1 แต่ยังสูงกว่า SD 10% ของ B การกระจายการลงทุนจึงไม่ได้แปลว่าทุกพอร์ตผสมต้องเสี่ยงต่ำกว่าทุกสินทรัพย์เดี่ยว

เก็บสูตรไว้ในฟังก์ชันด้วย `def` ส่วน `return` ส่งผลลัพธ์กลับให้ผู้เรียก ตัวแปร `weight_a` เป็นน้ำหนักของ A แล้วน้ำหนัก B เท่ากับ `1 - weight_a`

```python
mu_a, mu_b = 0.08, 0.04
sigma_a, sigma_b, rho_ab = 0.20, 0.10, 0.25

def two_asset_metrics(weight_a):
    weight_b = 1 - weight_a
    expected = weight_a * mu_a + weight_b * mu_b
    variance = (weight_a * sigma_a) ** 2 + (weight_b * sigma_b) ** 2
    variance += 2 * weight_a * weight_b * rho_ab * sigma_a * sigma_b
    return expected, np.sqrt(variance)

print(two_asset_metrics(0.60))
```

ได้ expected return `0.064` และ SD ประมาณ `0.1356466` การคืนค่าสองตัวคั่นด้วยจุลภาคทำให้รับกลับเป็นคู่ได้ `+=` เป็นคำสั่งบวกค่าทางขวาเข้าไปในตัวแปรเดิม

สำหรับสองสินทรัพย์ เราลองน้ำหนักตั้งแต่ 0 ถึง 1 ได้โดยตรง `np.linspace(0, 1, 5)` สร้าง 0, 0.25, 0.5, 0.75 และ 1 ส่วน `for` ทำคำสั่งที่ย่อหน้าเข้าไปซ้ำกับแต่ละน้ำหนัก

```python
candidate_rows = []
for weight_a in np.linspace(0, 1, 5):
    expected, volatility = two_asset_metrics(weight_a)
    candidate_rows.append({"Weight A": weight_a, "Mean": expected, "SD": volatility})
two_asset_table = pd.DataFrame(candidate_rows)
print((two_asset_table * 100).round(4))
```

| น้ำหนัก A | Expected return ต่อปี | SD ต่อปี |
|---:|---:|---:|
| 0% | 4.00% | 10.0000% |
| 25% | 5.00% | 10.0000% |
| 50% | 6.00% | 12.2474% |
| 75% | 7.00% | 15.8114% |
| 100% | 8.00% | 20.0000% |

ในชุดสมมตินี้พอร์ต A 25% / B 75% ให้ expected return 5% ที่ SD 10% ขณะที่ B ล้วนให้ expected return 4% ที่ SD 10% เท่ากัน หากตัดสินด้วย mean และ variance เพียงสองตัว พอร์ต B ล้วนจึงมีพอร์ตอีกแบบที่ดีกว่าโดยไม่เพิ่มความเสี่ยง

### จุดที่ความผันผวนต่ำสุดอาจอยู่ระหว่างสินทรัพย์

น้ำหนัก A ที่ทำให้ variance ต่ำสุดสำหรับสองสินทรัพย์ โดยยังไม่บังคับขอบน้ำหนัก หาได้จาก

$$
w_A^{\mathrm{min}}
=\frac{\sigma_B^2-\rho_{AB}\sigma_A\sigma_B}
{\sigma_A^2+\sigma_B^2-2\rho_{AB}\sigma_A\sigma_B}.
$$

สูตรนี้ได้จากหาจุดต่ำสุดของสมการกำลังสองใน $w_A$ กรณีตัวอย่างได้ $w_A=0.125$ หรือ 12.5% และ B 87.5% ผลตอบแทนคาดหมายเท่ากับ 4.5% และ SD ประมาณ 9.6825% ต่อปี ต่ำกว่า B ล้วน หากสูตรให้น้ำหนักอยู่นอก 0 ถึง 1 แต่เราห้าม short ต้องหาจุดต่ำสุดภายในช่วงที่อนุญาตแทน

```python
cov_ab = rho_ab * sigma_a * sigma_b
min_weight_a = (sigma_b ** 2 - cov_ab) / (sigma_a ** 2 + sigma_b ** 2 - 2 * cov_ab)
min_expected, min_sd = two_asset_metrics(min_weight_a)
print(f"Weight A: {min_weight_a:.2%}")
print(f"Expected return: {min_expected:.2%}; SD: {min_sd:.4%}")
```

เมื่อนำ SD วางบนแกนนอนและ expected return วางบนแกนตั้ง ทุกน้ำหนักเป็นจุดหนึ่งในพื้นที่ของพอร์ตที่สร้างได้ น้ำหนักบางแบบถูกพอร์ตอื่นครอบงำ ตัวอย่างเช่นจุดที่อยู่ใต้จุด variance ต่ำสุดอาจให้ผลตอบแทนน้อยลงพร้อมกับเสี่ยงมากขึ้น เราจะเรียกเฉพาะส่วนที่ไม่มีพอร์ตอื่นให้ mean สูงกว่าโดยไม่เพิ่ม variance ว่า **efficient frontier** ใน[บทถัดไป](efficient-frontier.html)

<figure class="course-figure">
<picture>
<source media="(max-width: 600px)" srcset="assets/charts/course-diversification-mobile.svg">
<img src="assets/charts/course-diversification.svg" alt="ผลของ Correlation ต่อ SD ของพอร์ตสองสินทรัพย์" width="720" height="560" loading="lazy">
</picture>
<figcaption>แกนนอนเป็นน้ำหนักของ A ตั้งแต่ 0% ถึง 100% แกนตั้งเป็น SD ต่อปี ใช้ SD ของ A 20% และ B 10% เหมือนตัวอย่าง แต่เปลี่ยน correlation เป็น +1, 0.25 และ −1 เส้นที่ต่ำกว่าหมายถึง variance ของผลรวมลดลงจากการเคลื่อนไหวร่วมกันที่ต่างไป ทุกเส้นเป็นแบบจำลองสมมติ</figcaption>
</figure>

<span id="portfolio-basics-exercises"></span>

## แบบฝึกหัดพร้อมเฉลย

**1. พอร์ตเงิน 20,000 บาท ลงทุน A 5,000 บาทและ B 15,000 บาท เดือนเดียวกัน A ได้ +12% และ B ได้ −4% พอร์ตได้เท่าไร?**

<details>
<summary>ดูเฉลย: เริ่มจากน้ำหนักต้นช่วง</summary>

น้ำหนักคือ 25% และ 75% ผลตอบแทนพอร์ตเท่ากับ $0.25(0.12)+0.75(-0.04)=0$ A กำไร 600 บาท และ B ขาดทุน 600 บาท เงินปลายเดือนยังเท่ากับ 20,000 บาท แต่น้ำหนัก A เพิ่มเป็น $5{,}600/20{,}000=28\%$ ก่อนเริ่มเดือนต่อไป

</details>

**2. ใช้พอร์ต 60/40 ในแบบจำลอง A SD 20%, B SD 10% ถ้า correlation เปลี่ยนเป็น +1, 0 และ −1 จะได้ SD เท่าไร?**

<details>
<summary>ดูเฉลย: เปลี่ยนเฉพาะพจน์ covariance</summary>

ได้ 16%, ประมาณ 12.6491% และ 8% ตามลำดับ ค่าต่ำสุดในสามกรณีนี้ยังไม่เป็นศูนย์ เพราะเมื่อ correlation −1 การหักล้างหมดต้องมี $w_A\sigma_A=w_B\sigma_B$ สำหรับ SD 20% และ 10% ต้องใช้น้ำหนัก A หนึ่งในสามและ B สองในสาม สมมติฐานสัมพันธ์เชิงเส้นทางลบสมบูรณ์และคงอยู่ตลอดเป็นเงื่อนไขของตัวอย่าง ไม่ใช่คำรับรองว่าจะสร้างพอร์ตไร้ความเสี่ยงในตลาดได้

</details>

**3. มีราคาหุ้น A ทุกวัน แต่ราคาของ B มีเฉพาะสิ้นเดือน นำผลตอบแทนทั้งสองชุดมา `.cov()` ทันทีได้หรือไม่?**

<details>
<summary>ดูเฉลย: จับช่วงเวลาให้ตรงก่อน</summary>

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

</details>

## แหล่งอ้างอิงและอ่านต่อ

- EDHEC Business School, [The only free lunch in Finance](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/aZJj3/the-only-free-lunch-in-finance) และ [Lab: Efficient Frontier Part 1](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/gQcpq/lab-session-efficient-frontier-part-1), [Part 2](https://www.coursera.org/learn/introduction-portfolio-construction-python/lecture/FcNiC/lab-session-asset-efficient-frontier-part-2). ใช้ลำดับหัวข้อประกอบ Lab 107–108 แล้วสร้างตัวอย่างและโค้ดของบทนี้ใหม่
- [CFA Institute: Portfolio Mathematics](https://www.cfainstitute.org/insights/professional-learning/refresher-readings/2026/portfolio-mathematics) อธิบายผลตอบแทนและ variance ของพอร์ต
- [pandas: Covariance](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.cov.html) ระบุ convention และข้อควรตรวจเมื่อมีข้อมูลหาย

ไปต่อที่ [หลายสินทรัพย์และ Efficient Frontier](efficient-frontier.html) เพื่อเปลี่ยนการลองน้ำหนักสองตัวเป็นปัญหาที่คอมพิวเตอร์ช่วยค้นหาได้
