การสร้างพอร์ต · บทย่อย 1
เริ่มจัดพอร์ตจากสินทรัพย์สองตัว
ถ้าหุ้น A ผันผวน 20% และสินทรัพย์ B ผันผวน 10% การแบ่งเงินลงทุนจะทำให้พอร์ตผันผวนเท่าไร?
คำตอบขึ้นอยู่กับทั้งสัดส่วนเงินที่ใส่และการเคลื่อนไหวของสินทรัพย์สองตัวในช่วงเดียวกัน เราจะเริ่มจากเงิน 10,000 บาท คำนวณผลตอบแทนเป็นบาทก่อน แล้วค่อยดูว่าทำไมการนำความผันผวนมาเฉลี่ยตรง ๆ จึงมักไม่ตรงกับความผันผวนของพอร์ต
เนื้อหานี้เรียบเรียงประกอบหัวข้อการกระจายการลงทุนและ Lab 107–108 ตัวเลขทั้งหมดเป็นตัวอย่างสมมติ โค้ดสร้างข้อมูลเองและรันได้โดยไม่ใช้ CSV ของคอร์ส อ่านพื้นฐานผลตอบแทนและ Python ก่อนหากยังไม่คุ้นกับตัวแปร Series และ DataFrame
ตัวอย่างช่วงแรกใช้ผลตอบแทนรายเดือนที่เกิดขึ้นแล้วในสถานการณ์สมมติ ส่วนช่วงท้ายใช้ expected return และ covariance ต่อปีที่ตั้งเป็นข้อมูลเข้าแบบจำลอง เราจะแยกชื่อชุดข้อมูลให้ชัด และไม่ถือว่าค่าเฉลี่ยจากข้อมูลไม่กี่เดือนเป็นผลตอบแทนในอนาคตที่ทราบแน่นอน
ดาวน์โหลด Notebook ของบทนี้ เพื่อรันตัวอย่างตามลำดับและเทียบผลลัพธ์ที่บันทึกไว้
น้ำหนักพอร์ตคือสัดส่วนเงินต้นช่วง
สมมติว่ามีเงิน 10,000 บาท ซื้อ A มูลค่า 6,000 บาทและ B มูลค่า 4,000 บาท น้ำหนักของ A คือ wA=6,000/10,000=0.6w_A=6{,}000/10{,}000=0.6 และน้ำหนักของ B คือ wB=0.4w_B=0.4 ตัว ww ย่อจาก 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% อีกวิธีหนึ่งคือถ่วงน้ำหนักผลตอบแทนของแต่ละตัวด้วยน้ำหนักต้นเดือน:
ตัวห้อย pp หมายถึง portfolio หรือพอร์ต ตัวเลขผลตอบแทนในสูตรต้องเป็นทศนิยม เช่น 10% ใช้ 0.10 ผลตอบแทนแต่ละตัวต้องครอบคลุมช่วงเวลาเดียวกันและนับปันผลหรือกระแสเงินสดอย่างสอดคล้องกัน ตัวอย่างนี้ไม่มีฝากถอน ภาษี ค่าธรรมเนียม หรือซื้อขายระหว่างเดือน
เปิด Notebook ใหม่แล้วรันช่องตามลำดับ pd.Series เก็บค่าพร้อมชื่อ A/B และ .sum() รวมค่าทั้งชุด การคูณ Series จะจับคู่ตามชื่อสินทรัพย์
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 แสดงจำนวนเงินโดยใส่จุลภาคคั่นหลักพันและทศนิยมสองตำแหน่ง ค่าที่ใช้คำนวณยังคงเป็นตัวเลขเดิม
น้ำหนักรวมหนึ่ง และความหมายของการขายชอร์ต
ในบทนี้เริ่มจากข้อกำหนด wA+wB=1w_A+w_B=1 และ wA,wB≥0w_A,w_B\geq0 เรียกว่า fully invested, long-only: ใช้เงินทั้งหมดกับสินทรัพย์ที่อยู่ในรายการและไม่มีสถานะขายชอร์ต น้ำหนักศูนย์ทำได้ แปลว่าไม่ได้ถือสินทรัพย์นั้น หากมีเงินสดต้องใส่เงินสดเป็นอีกสินทรัพย์หนึ่ง หรือระบุแยกว่ารายการที่กำลังพิจารณาเป็นเพียงส่วนหนึ่งของพอร์ต
น้ำหนัก 1.21.2 กับ −0.2-0.2 รวมเป็นหนึ่งเช่นกัน แต่ความหมายเปลี่ยนไป เราถือสินทรัพย์แรก 120% ของเงินทุนและมีสถานะ short สินทรัพย์ที่สอง 20% การขายชอร์ตต้องมีเงื่อนไขการยืม หลักประกัน และต้นทุนเพิ่มเติม ข้อกำหนดน้ำหนักรวมหนึ่งเพียงข้อเดียวจึงยังไม่ได้ห้ามการลงทุนเกินเงินทุน
เดือนถัดไปน้ำหนักเปลี่ยน แม้เราไม่ได้ซื้อขาย
จากตารางเมื่อสิ้นเดือน A มีมูลค่า 6,600 บาทและ B 3,800 บาท น้ำหนักใหม่ของ A จึงเป็น 6,600/10,400≈63.46%6{,}600/10{,}400\approx63.46\% และ B ประมาณ 36.54% สินทรัพย์ที่ขึ้นมากกว่าจะกินสัดส่วนของพอร์ตเพิ่มเอง
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.6RA+0.4RB0.6R_A+0.4R_B ทุกเดือนจึงแฝงสมมติฐานว่าปรับพอร์ตกลับสู่น้ำหนักเดิมทุกต้นเดือน หรือ rebalancing หากตั้งใจจำลอง buy and hold ต้องปล่อยให้น้ำหนักเปลี่ยนตามมูลค่า ห้ามนำสองนโยบายมาปนกันเพราะชื่อพอร์ตตอนเริ่มเหมือนกัน
ความผันผวนต้องดูผลตอบแทนที่เกิดในเดือนเดียวกัน
เปลี่ยนมาใช้ข้อมูลสมมติอีกชุดสี่เดือน เพื่อคำนวณความเสี่ยงด้วยมือได้ครบทุกแถว แต่ละคอลัมน์คือสินทรัพย์ แต่ละแถวคือเดือนเดียวกันสำหรับทั้ง A และ B
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=3n-1=3 จะได้ SD ของ A ประมาณ 2.5820% และ B ประมาณ 1.4142% ต่อเดือน
ถ้าเอา SD มาถ่วงน้ำหนักตรง ๆ จะได้ 0.6(2.5820%)+0.4(1.4142%)≈2.1149%0.6(2.5820\%)+0.4(1.4142\%)\approx2.1149\% แต่ SD ที่คำนวณจากผลตอบแทนพอร์ตสี่เดือนจริงได้เพียง 1.2% ต่อเดือน ส่วนต่างเกิดจากการเคลื่อนไหวร่วมกันของสินทรัพย์: ในบางเดือนกำไรของตัวหนึ่งช่วยหักล้างขาดทุนของอีกตัว
Covariance เริ่มจากผลคูณของส่วนต่างจากค่าเฉลี่ย
เอาผลตอบแทนแต่ละเดือนลบค่าเฉลี่ยของสินทรัพย์นั้นก่อน ส่วนต่างที่เป็นบวกหมายถึงเดือนนั้นดีกว่าค่าเฉลี่ยของตัวเอง ส่วนต่างติดลบหมายถึงต่ำกว่าค่าเฉลี่ย ไม่จำเป็นต้องขาดทุนเป็นบาท
| เดือน | RA−RˉAR_A-\bar R_A | RB−RˉBR_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−1n-1:
ตัว tt บอกเดือน ส่วน RˉA\bar R_A และ RˉB\bar R_B คือค่าเฉลี่ยของแต่ละคอลัมน์ Covariance มีหน่วยผลตอบแทนยกกำลังสอง เมื่อข้อมูลเป็นทศนิยม อย่าอ่าน −0.000266667 ว่าเป็นผลตอบแทนขาดทุน −0.0267% มันเป็นตัววัดการเคลื่อนไหวร่วมกัน
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 ที่ได้มีลักษณะนี้:
แนวทแยง A/A และ B/B คือ variance ของสินทรัพย์นั้น เพราะเป็นการคูณส่วนต่างกับตัวเอง ช่อง A/B กับ B/A เท่ากันเพราะสลับลำดับการคูณได้ เมื่อนำ covariance ของทุกคู่มาเรียงเป็นตาราง เราเรียกตารางนี้ว่า covariance matrix เครื่องหมายหมวกบน Σ^\widehat\Sigma เตือนว่านี่เป็นค่าประมาณจากตัวอย่าง
DataFrame.cov() ใช้ข้อมูลที่จับคู่ได้ในแต่ละคู่คอลัมน์ หากมีข้อมูลหาย จำนวนเดือนของแต่ละคู่จึงอาจต่างกัน การใช้ covariance matrix ที่สร้างจากคนละช่วงอาจทำให้คุณสมบัติที่ต้องการสำหรับการจัดพอร์ตเสียไป ในบทนี้ข้อมูลครบทุกแถว จึงใช้สี่เดือนเดียวกันทั้งหมด pandas: DataFrame.cov
Correlation ทำให้ความสัมพันธ์ไม่มีหน่วย
Covariance ขึ้นกับขนาดความผันผวนของทั้งสองตัว เพื่ออ่านความสัมพันธ์บนมาตราส่วนเดียวกัน เราหารด้วย SD ของ A คูณ SD ของ B:
อักษรกรีก ρ\rho อ่านว่า “โร” ค่าอยู่ระหว่าง −1 ถึง +1 เมื่อ SD ทั้งสองเป็นบวก จากตัวอย่างจะได้
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 เท่ากับศูนย์เพราะผลคูณส่วนต่างทางซ้ายและขวาหักล้างกัน ตัวอย่างนี้เป็นตัวเลขสาธิตความสัมพันธ์ ไม่ใช่ผลตอบแทนการลงทุน
x = np.array([-1.0, 0.0, 1.0])
y = x ** 2
print(np.corrcoef(x, y).round(6))
ช่องนอกแนวทแยงของตารางเป็นศูนย์ การเขียน ** 2 หมายถึงยกกำลังสองทุกสมาชิก และ np.corrcoef คืนตาราง correlation ของตัวแปรที่ส่งเข้าไป
คำนวณ variance ของพอร์ตสองสินทรัพย์
ผลตอบแทนพอร์ตเป็นผลบวกถ่วงน้ำหนัก แต่เมื่อยกผลบวกนั้นกำลังสอง จะเกิดพจน์ผลคูณไขว้ด้วย สูตร variance จึงเป็น
ใช้ความสัมพันธ์ Cov(RA,RB)=ρABσAσB\operatorname{Cov}(R_A,R_B)=\rho_{AB}\sigma_A\sigma_B จะได้อีกแบบหนึ่ง:
สองพจน์แรกคิดความผันผวนของแต่ละส่วนที่ถือ ส่วนพจน์สุดท้ายคิดว่าทั้งสองเคลื่อนร่วมกันอย่างไร หาก covariance ติดลบ พจน์นั้นช่วยลด variance ของผลรวม เราต้องคำนวณ variance ก่อนแล้วค่อยถอดรากที่สองกลับเป็น SD CFA Institute: Portfolio Mathematics
ลองตรวจสูตรกับข้อมูลสี่เดือน โดย .loc["A", "B"] เลือก covariance แถว A คอลัมน์ B ตามชื่อ
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 ได้
วางหน่วยเวลาให้ตรงกันก่อนเปรียบเทียบพอร์ต
ในแบบจำลองหนึ่งช่วงเวลา expected return และ covariance ต้องหมายถึงช่วงเดียวกัน หากแกนนอนเป็น volatility ต่อเดือนแต่แกนตั้งเป็น expected return ต่อปี ต้องเปลี่ยนให้สอดคล้องกันก่อนตีความความชันหรือคำนวณ Sharpe ratio
ภายใต้สมมติฐานว่าผลตอบแทนรายเดือนไม่มี covariance ข้ามเวลาและความเสี่ยงต่อเดือนคงที่ variance ของ ผลรวม สิบสองเดือนเท่ากับ 12 เท่าของ variance รายเดือน ดังนั้น convention annualization ที่ใช้ทั่วไปคือ
สำหรับ simple returns ที่ต้องทบต้น ผลตอบแทนหนึ่งปีเป็นผลคูณของ gross returns จึงไม่ได้มี variance เท่ากับสูตรผลรวมนี้อย่างแม่นยำเสมอ การคูณ 12 และ 12\sqrt{12} เป็น convention หรือการประมาณที่ต้องบอกสมมติฐาน หากมี autocorrelation ต้องนับ covariance ข้ามเวลาด้วย
ค่าเฉลี่ยเลขคณิตรายเดือนคูณ 12 ใช้แสดง mean ใน convention รายปีได้ ส่วน CAGR ตอบการเติบโตแบบทบต้นที่เกิดขึ้นจริง และไม่ได้เป็นตัวเดียวกับ expected arithmetic return ในสูตร μp=∑iwiμi\mu_p=\sum_iw_i\mu_i การเอา CAGR ของแต่ละสินทรัพย์มาถ่วงน้ำหนักจึงไม่รับประกันว่าจะได้ CAGR ของพอร์ต
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% ตัวเลขเหล่านี้แปลงจากตัวอย่างเพียงสี่เดือนเพื่อฝึกหน่วย ไม่มีข้อมูลครบหนึ่งปีหรือหลักฐานว่าพารามิเตอร์จะคงอยู่
ลองน้ำหนักหลายแบบบนแบบจำลองต่อปี
ตอนนี้เปลี่ยนจากข้อมูลสี่เดือนมาเป็น พารามิเตอร์สมมติต่อปี ชุดใหม่: 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%0.6(8\%)+0.4(4\%)=6.4\% และ variance
เมื่อถอดรากได้ SD ประมาณ 13.5647% ต่อปี ซึ่งต่ำกว่า SD เฉลี่ยถ่วงน้ำหนัก 0.6(20%)+0.4(10%)=16%0.6(20\%)+0.4(10\%)=16\% เพราะ correlation ต่ำกว่า +1 แต่ยังสูงกว่า SD 10% ของ B การกระจายการลงทุนจึงไม่ได้แปลว่าทุกพอร์ตผสมต้องเสี่ยงต่ำกว่าทุกสินทรัพย์เดี่ยว
เก็บสูตรไว้ในฟังก์ชันด้วย def ส่วน return ส่งผลลัพธ์กลับให้ผู้เรียก ตัวแปร weight_a เป็นน้ำหนักของ A แล้วน้ำหนัก B เท่ากับ 1 - weight_a
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 ทำคำสั่งที่ย่อหน้าเข้าไปซ้ำกับแต่ละน้ำหนัก
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 ต่ำสุดสำหรับสองสินทรัพย์ โดยยังไม่บังคับขอบน้ำหนัก หาได้จาก
สูตรนี้ได้จากหาจุดต่ำสุดของสมการกำลังสองใน wAw_A กรณีตัวอย่างได้ wA=0.125w_A=0.125 หรือ 12.5% และ B 87.5% ผลตอบแทนคาดหมายเท่ากับ 4.5% และ SD ประมาณ 9.6825% ต่อปี ต่ำกว่า B ล้วน หากสูตรให้น้ำหนักอยู่นอก 0 ถึง 1 แต่เราห้าม short ต้องหาจุดต่ำสุดภายในช่วงที่อนุญาตแทน
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 ในบทถัดไป
แบบฝึกหัดพร้อมเฉลย
1. พอร์ตเงิน 20,000 บาท ลงทุน A 5,000 บาทและ B 15,000 บาท เดือนเดียวกัน A ได้ +12% และ B ได้ −4% พอร์ตได้เท่าไร?
ดูเฉลย: เริ่มจากน้ำหนักต้นช่วง
น้ำหนักคือ 25% และ 75% ผลตอบแทนพอร์ตเท่ากับ 0.25(0.12)+0.75(−0.04)=00.25(0.12)+0.75(-0.04)=0 A กำไร 600 บาท และ B ขาดทุน 600 บาท เงินปลายเดือนยังเท่ากับ 20,000 บาท แต่น้ำหนัก A เพิ่มเป็น 5,600/20,000=28%5{,}600/20{,}000=28\% ก่อนเริ่มเดือนต่อไป
2. ใช้พอร์ต 60/40 ในแบบจำลอง A SD 20%, B SD 10% ถ้า correlation เปลี่ยนเป็น +1, 0 และ −1 จะได้ SD เท่าไร?
ดูเฉลย: เปลี่ยนเฉพาะพจน์ covariance
ได้ 16%, ประมาณ 12.6491% และ 8% ตามลำดับ ค่าต่ำสุดในสามกรณีนี้ยังไม่เป็นศูนย์ เพราะเมื่อ correlation −1 การหักล้างหมดต้องมี wAσA=wBσBw_A\sigma_A=w_B\sigma_B สำหรับ SD 20% และ 10% ต้องใช้น้ำหนัก A หนึ่งในสามและ B สองในสาม สมมติฐานสัมพันธ์เชิงเส้นทางลบสมบูรณ์และคงอยู่ตลอดเป็นเงื่อนไขของตัวอย่าง ไม่ใช่คำรับรองว่าจะสร้างพอร์ตไร้ความเสี่ยงในตลาดได้
3. มีราคาหุ้น A ทุกวัน แต่ราคาของ B มีเฉพาะสิ้นเดือน นำผลตอบแทนทั้งสองชุดมา .cov() ทันทีได้หรือไม่?
ดูเฉลย: จับช่วงเวลาให้ตรงก่อน
ต้องสร้างผลตอบแทนที่วัดช่วงเดียวกันก่อน เช่น เลือกราคาสิ้นเดือนของ A แล้วคำนวณผลตอบแทนรายเดือนให้ตรงกับ B ตรวจวันที่ หน่วยสกุลเงิน ปันผลและข้อมูลหายด้วย การจับค่ารายวันกับรายเดือนเพียงเพราะมีวันตรงกันบางแถวไม่ได้ทำให้ทั้งสองแทนผลตอบแทนช่วงเดียวกัน
แหล่งอ้างอิงและอ่านต่อ
- EDHEC Business School, The only free lunch in Finance และ Lab: Efficient Frontier Part 1, Part 2. ใช้ลำดับหัวข้อประกอบ Lab 107–108 แล้วสร้างตัวอย่างและโค้ดของบทนี้ใหม่
- CFA Institute: Portfolio Mathematics อธิบายผลตอบแทนและ variance ของพอร์ต
- pandas: Covariance ระบุ convention และข้อควรตรวจเมื่อมีข้อมูลหาย
ไปต่อที่ หลายสินทรัพย์และ Efficient Frontier เพื่อเปลี่ยนการลองน้ำหนักสองตัวเป็นปัญหาที่คอมพิวเตอร์ช่วยค้นหาได้