Portfolio Management Toolkit Notes
Portfolio Management Toolkit Notes

Beyond Diversification · บทย่อย 1

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

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

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

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

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

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

ดาวน์โหลด Notebook ของบทนี้ เพื่อรันตัวอย่างตามลำดับและเทียบผลลัพธ์ที่บันทึกไว้

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

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

Ri=M+εiR_i=M+\varepsilon_i

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

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

σp2=σM2+σε2n\sigma_p^2=\sigma_M^2+\frac{\sigma_\varepsilon^2}{n}

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

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) ปัดเฉพาะตารางที่แสดง ไม่ได้แก้ตัวแปรต้นฉบับ

เมื่อ nn มากขึ้น ส่วน σε2/n\sigma_\varepsilon^2/n เข้าใกล้ศูนย์ แต่ SD ของพอร์ตในแบบจำลองนี้เข้าใกล้ 12% ความเสี่ยงร่วมยังอยู่ หากสินทรัพย์รับปัจจัยร่วมด้วยน้ำหนักต่างกัน หรือส่วนเฉพาะยังสัมพันธ์กัน สูตรจะต้องเปลี่ยนตามโครงสร้างนั้น ประเด็นนี้สอดคล้องกับบท Limits of diversification ของคอร์ส

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

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

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

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

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

wi=มูลค่าตลาดของกลุ่ม iมูลค่าตลาดรวมทุกกลุ่มw_i=\frac{\text{มูลค่าตลาดของกลุ่ม }i}{\text{มูลค่าตลาดรวมทุกกลุ่ม}}
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% ผลตอบแทนพอร์ตเท่ากับ

Rp=0.50(0.10)+0.30(−0.05)+0.20(0.02)=0.039=3.9%R_p=0.50(0.10)+0.30(-0.05)+0.20(0.02)=0.039=3.9\%
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 จึงมีหน่วยเป็นส่วนของผลตอบแทนพอร์ต ไม่ใช่ผลตอบแทนของกลุ่มนั้นโดยลำพัง

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

หลังเดือนที่เพิ่งคำนวณ ในตัวอย่างนี้กำหนดว่าไม่มีเงินปันผลหรือการจ่ายเงินออก จำนวนหุ้นและองค์ประกอบไม่เปลี่ยน จึงให้ 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 เมื่อเป็นส่วนหนึ่งของการทดสอบกลยุทธ์ย้อนหลัง

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))

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

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

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

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) จึงต้องตามหลังการอ่านนิยามและเวลาของข้อมูล

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

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

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

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

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 ไม่เป็นศูนย์

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

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) ขยับแนวเลือกขึ้นไปหนึ่งระดับเพื่อไม่เอาแนวทแยง

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

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 ตัวอย่างนี้ดึงตารางเดือนเมษายนออกมาเทียบกับที่เราคำนวณเอง

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

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

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

แม้เห็น correlation สูงขึ้นในช่วงผันผวน ก็ยังสรุปไม่ได้ทันทีว่าเกิดการส่งต่อวิกฤตจากสินทรัพย์หนึ่งไปอีกสินทรัพย์หนึ่ง ความแปรปรวนที่เปลี่ยนและการเลือกช่วงข้อมูลมีผลต่อการเปรียบเทียบ correlation ประเด็นนี้ศึกษาโดย Forbes และ Rigobon ใน No Contagion, Only Interdependence

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

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

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

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

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

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

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

เปิดคำตอบข้อ 1

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

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

เปิดคำตอบข้อ 2

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

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

เปิดคำตอบข้อ 3

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

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

เปิดคำตอบข้อ 4

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

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

หัวข้อในหน้านี้สอดคล้องกับ Limits of diversification, Lab Part 1 และ Lab Part 2 ของ EDHEC บน Coursera โดยตรวจแนวคิดร่วมกับ Notebook lab_118 ที่แนบมากับคอร์ส ตัวอย่างข้อมูล โค้ด และแบบฝึกหัดในหน้านี้เขียนขึ้นใหม่

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

ค้นหาในหนังสือ