Portfolio Management Toolkit Notes
Portfolio Management Toolkit Notes

Advanced · Portfolio Optimization in Practice · บทย่อย 1

Diversification: กระจายเงินด้วยวิธีใด

เมื่อใช้สินทรัพย์และ covariance ชุดเดียวกัน GMV, Maximum Decorrelation และ Maximum Diversification จะให้น้ำหนักต่างกันอย่างไร บทนี้เปรียบเทียบทั้งสามวิธีกับ CW/EW และเพิ่มข้อจำกัด ENC เพื่อดูต้นทุนของการบังคับให้เงินกระจายมากขึ้น

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

โค้ดใช้ NumPy และ SciPy รันเรียงจากบนลงล่างได้โดยไม่ต้องดาวน์โหลดข้อมูล หากยังไม่คุ้นกับน้ำหนักพอร์ตและเครื่องหมาย @ ให้ทบทวน เริ่มจัดพอร์ตจากสินทรัพย์สองตัว ก่อน ส่วน บท Expected Return อธิบายว่าทำไมค่าประมาณผลตอบแทนเฉลี่ยจึงทำให้พอร์ตเปลี่ยนมากได้ บทนี้จะเลือกเกณฑ์ที่ไม่ต้องป้อน expected return แต่ยังต้องตรวจข้อสมมติของข้อมูลความเสี่ยงที่ใช้

เตรียม CW และ EW เป็นพอร์ตอ้างอิง

ใช้กฎ Cap Weight และ Equal Weight จากบท Smart Beta กับข้อมูลสมมติชุดใหม่ A–D: CW แบ่งตามมูลค่าตลาด ส่วน EW ลงเงินตัวละ 1/41/4 ราคามีหน่วยบาทต่อหุ้นและจำนวนหุ้นมีหน่วยล้านหุ้น ผลคูณจึงเป็นมูลค่าตลาดหน่วยล้านบาท

np.array เก็บตัวเลขตามลำดับ A–D, * คูณตามตำแหน่ง และ .sum() รวมมูลค่าทั้งหมด ส่วน np.full สร้างน้ำหนักเท่ากัน และ np.round ปัดเฉพาะตอนแสดงผล

import numpy as np

asset_names = np.array(["A", "B", "C", "D"])
prices = np.array([70.0, 20.0, 10.0, 50.0])
shares_million = np.array([10.0, 5.0, 10.0, 2.0])
market_caps_million = prices * shares_million
cap_weights = market_caps_million / market_caps_million.sum()
equal_weights = np.full(len(asset_names), 1 / len(asset_names))
print("Market caps (million baht):", market_caps_million)
print("CW weights (%):", np.round(100 * cap_weights, 2))
print("EW weights (%):", np.round(100 * equal_weights, 2))

ได้มูลค่าตลาด [700, 100, 100, 100] ล้านบาท, CW [70, 10, 10, 10]% และ EW [25, 25, 25, 25]% มูลค่าตลาดรวม 1,000 ล้านบาทเป็นขนาดจักรวาลลงทุน แยกจากเงินผู้ลงทุน 1,000,000 บาทที่จะใช้ทดลองการซื้อขาย โดยสมมติให้ซื้อหุ้นเป็นเศษส่วนและไม่มีขนาดรายการขั้นต่ำ

การเปรียบเทียบหลักใช้ long-only wi≥0w_i\geq0 และลงทุนเต็มงบ ∑iwi=1\sum_iw_i=1 ทุกวิธี ดัชนีจริงอาจปรับจำนวนหุ้นตาม free float และมีกฎจำกัดน้ำหนักเพิ่มเติม ตาม S&P DJI: Methodology Matters

เตรียม ENC สำหรับตรวจและจำกัดการกระจุกตัว

ใช้ จำนวนสินทรัพย์เทียบเท่าจากน้ำหนัก หรือ ENC =1/∑iwi2=1/\sum_iw_i^2 เพื่อเปรียบเทียบพอร์ต และจะนำไปเป็นข้อจำกัดของ GMV ในช่วงถัดไป ฟังก์ชันรับ array หนึ่งมิติ ตรวจค่าจำกัด น้ำหนักไม่ติดลบ และผลรวมหนึ่งด้วย np.isclose ส่วน w @ w ให้ผลรวมกำลังสองน้ำหนัก หากข้อมูลผิดเงื่อนไข raise ValueError จะหยุดโดยไม่ปรับน้ำหนักแทนให้

def effective_number(weights):
    w = np.asarray(weights, dtype=float)
    if w.ndim != 1 or not np.all(np.isfinite(w)):
        raise ValueError("Weights must be a finite one-dimensional array.")
    if np.any(w < 0) or not np.isclose(w.sum(), 1):
        raise ValueError("Use nonnegative weights that sum to one.")
    return 1 / (w @ w)

print(f"CW ENC: {effective_number(cap_weights):.4f}")
print(f"EW ENC: {effective_number(equal_weights):.4f}")
print(f"One holding ENC: {effective_number([1.0]):.4f}")
print(f"Two equal labels ENC: {effective_number([0.5, 0.5]):.4f}")

ผลสี่บรรทัดคือ 1.9231, 4, 1 และ 2 ตามลำดับ สำหรับ long-only ที่ลงทุนเต็มงบ ENC อยู่ระหว่าง 1 กับ NN แต่การแยกกองทุนที่ถือสินทรัพย์เหมือนกันทุกอย่างเป็นสองชื่อทำให้ ENC เพิ่มจาก 1 เป็น 2 ได้โดยไม่เปลี่ยนผลตอบแทนพอร์ต จึงต้องกำหนดหน่วยของสินทรัพย์และตรวจการถือซ้ำด้วย

เงื่อนไข long-only เป็นส่วนหนึ่งของการตีความ หากแทนน้ำหนัก [2, -1] ในสูตรดิบจะได้ 0.2 ซึ่งอยู่นอกความหมายจำนวนสินทรัพย์เทียบเท่าที่ใช้ในบทนี้

น้ำหนักเท่ากัน แต่ความเสี่ยงเปลี่ยนตามความสัมพันธ์

กำหนด annual volatility ของ A–D เท่ากับ 20%, 15%, 10% และ 25% ตามลำดับ และให้ correlation เป็นตารางต่อไปนี้ ตัวเลขทั้งหมดเป็นพารามิเตอร์สมมติของผลตอบแทนหนึ่งปี จึงไม่ต้องนำไปคูณหรือหารด้วย 12 อีก

σ=[0.200.150.100.25]T,ρ=[10.500.200.650.5010.100.400.200.1010.150.650.400.151].\sigma=\begin{bmatrix}0.20&0.15&0.10&0.25\end{bmatrix}^{\mathsf T}, \qquad \rho=\begin{bmatrix} 1&0.50&0.20&0.65\\ 0.50&1&0.10&0.40\\ 0.20&0.10&1&0.15\\ 0.65&0.40&0.15&1 \end{bmatrix}.

Correlation ไม่มีหน่วย เช่น A กับ D มีค่า 0.65 ส่วน covariance ของคู่นี้คือ ΣAD=σAσDρAD=0.20(0.25)(0.65)=0.0325\Sigma_{AD}=\sigma_A\sigma_D\rho_{AD}=0.20(0.25)(0.65)=0.0325 ถ้าใช้ผลตอบแทนทศนิยม covariance ก็อยู่ในหน่วยทศนิยมยกกำลังสองของผลตอบแทนหนึ่งปี

ความผันผวนพอร์ตคำนวณจาก

σp=wTΣw,Σij=σiσjρij.\sigma_p=\sqrt{w^{\mathsf T}\Sigma w}, \qquad \Sigma_{ij}=\sigma_i\sigma_j\rho_{ij}.

np.outer(annual_vols, annual_vols) สร้างตารางที่ช่อง i,ji,j เป็น σiσj\sigma_i\sigma_j แล้ว * correlation คูณแต่ละช่องด้วย correlation คู่นั้น np.diag เมื่อรับ array หนึ่งมิติจะสร้างเมทริกซ์แนวทแยง ส่วน np.linalg.eigvalsh คืน eigenvalues ของเมทริกซ์สมมาตร เราใช้ตรวจว่า covariance ที่กำหนดเป็น positive definite ตามนิยามใน บท covariance

annual_vols = np.array([0.20, 0.15, 0.10, 0.25])
correlation = np.array([
    [1.00, 0.50, 0.20, 0.65],
    [0.50, 1.00, 0.10, 0.40],
    [0.20, 0.10, 1.00, 0.15],
    [0.65, 0.40, 0.15, 1.00],
])
covariance = np.outer(annual_vols, annual_vols) * correlation

def portfolio_sd(weights, matrix):
    return np.sqrt(weights @ matrix @ weights)

print("Covariance eigenvalues:", np.round(np.linalg.eigvalsh(covariance), 6))
for label, matrix in [
    ("Zero correlations", np.diag(annual_vols ** 2)),
    ("Specified correlations", covariance),
    ("All correlations one", np.outer(annual_vols, annual_vols)),
]:
    print(label, f"EW annual SD = {portfolio_sd(equal_weights, matrix):.4%}")

Eigenvalues ของ covariance ชุดหลักประมาณ [0.009421, 0.013293, 0.019948, 0.092338] เป็นบวกทั้งหมด ส่วน EW มี SD ต่อปีต่างกันตามสามกรณี:

Correlation ที่สมมติ ENC SD พอร์ตต่อปี
ทุกคู่นอกแนวทแยงเป็นศูนย์ 4 9.1856%
ตารางที่กำหนด 4 13.1933%
ทุกคู่เท่ากับหนึ่ง 4 17.5000%

ในกรณี correlation เท่ากับหนึ่ง ความผันผวนพอร์ตเท่ากับค่าเฉลี่ยถ่วงน้ำหนักของความผันผวนรายตัว คือ 0.25(20+15+10+25)=17.50.25(20+15+10+25)=17.5% เมื่อ correlation ต่ำลง สมาชิกบางตัวช่วยลดการแกว่งร่วมกันได้ แม้รายชื่อและน้ำหนักทุกตัวเหมือนเดิม ค่า correlation เป็นศูนย์เพียงอย่างเดียวยังไม่ยืนยันว่าผลตอบแทนเป็นอิสระต่อกัน

Covariance กรณีทุก correlation เท่ากับหนึ่งเป็นเมทริกซ์ singular เราคำนวณ wTΣww^{\mathsf T}\Sigma w ได้ตามปกติเพราะไม่ได้หา inverse แต่จะนำเมทริกซ์นี้ไปใช้สูตรที่ต้องมี inverse โดยไม่ตรวจเงื่อนไขไม่ได้

ตรวจน้ำหนักก่อนปรับพอร์ตและยอดซื้อขาย

ใช้ สูตร weight drift กับผลตอบแทนหนึ่งช่วงของ A–D ที่ 10%, −5%, 2% และ −8% กำหนดว่าไม่มีปันผลหรือเงินจ่ายออก จำนวนหุ้นบริษัทคงที่ สมาชิกจักรวาลเดิม ไม่มีเงินฝากถอนและไม่มีต้นทุน จึงใช้ total return อัปเดตทั้งมูลค่าที่ถือและ market cap ได้ตรงกัน

เราจะตรวจว่าพอร์ตที่เริ่มตรง CW ยังตรงกับ CW ใหม่หลังราคาเปลี่ยน แล้วคำนวณเงินซื้อขายเพื่อกลับไป EW สำหรับเงินต้น 1,000,000 บาท equal_rebalance_trades เป็นบวกเมื่อซื้อและลบเมื่อขาย ส่วน np.abs ใช้ขนาดโดยไม่สนเครื่องหมาย

one_period_returns = np.array([0.10, -0.05, 0.02, -0.08])
cap_drift = cap_weights * (1 + one_period_returns) / (1 + cap_weights @ one_period_returns)
equal_drift = equal_weights * (1 + one_period_returns) / (1 + equal_weights @ one_period_returns)
new_market_caps = prices * (1 + one_period_returns) * shares_million
new_cap_weights = new_market_caps / new_market_caps.sum()
initial_wealth = 1_000_000.0
equal_end_positions = initial_wealth * equal_weights * (1 + one_period_returns)
equal_end_wealth = equal_end_positions.sum()
equal_rebalance_trades = equal_end_wealth * equal_weights - equal_end_positions
equal_one_way_turnover = np.abs(equal_weights - equal_drift).sum() / 2
print("CW drift matches new CW:", np.allclose(cap_drift, new_cap_weights))
print("EW drift weights (%):", np.round(100 * equal_drift, 4))
print("EW rebalance trades (baht):", equal_rebalance_trades)
print(f"One-way turnover: {equal_one_way_turnover:.4%}")

np.allclose ให้ผล True ว่า CW หลัง drift ตรงกับ CW ใหม่ ส่วน EW เปลี่ยนเป็น [27.5689, 23.8095, 25.5639, 23.0576]% และเหลือเงิน 997,500 บาท เป้าหมาย EW รอบใหม่จึงเป็นตัวละ 249,375 บาท:

หุ้น มูลค่าก่อนปรับ (บาท) ซื้อเพิ่ม / ขายออก (บาท)
A 275,000 −25,625
B 237,500 +11,875
C 255,000 −5,625
D 230,000 +19,375

ยอดซื้อและขายด้านละ 31,250 บาท ทำให้ one-way turnover เท่ากับ 31,250/997,500≈3.132831{,}250/997{,}500\approx3.1328% หากคิดค่าธรรมเนียมทั้งซื้อและขาย ต้องใช้ฐานสองด้านตาม นิยามต้นทุนใน Smart Beta ตัวอย่างนี้ยังไม่หักค่าธรรมเนียม และข้อสรุป CW ไม่ต้องซื้อขายอาศัยสมมติฐานราคาเปลี่ยนอย่างเดียวที่ระบุไว้

GMV เลือกน้ำหนักให้ variance ต่ำที่สุด

Global minimum variance หรือ GMV เลือก ww ให้ wTΣww^{\mathsf T}\Sigma w ต่ำที่สุดภายใต้ข้อจำกัดที่กำหนด เนื่องจากการถอดรากเพิ่มตามค่าที่ไม่ติดลบ การทำให้ variance ต่ำที่สุดจึงทำให้ SD ต่ำที่สุดด้วย

ในโจทย์นี้เราป้อน covariance โดยไม่มี expected return จึงไม่ได้กำหนดว่าทุกสินทรัพย์ต้องมีผลตอบแทนคาดหวังเท่ากัน การสมมติ equal positive expected excess returns เป็นเงื่อนไขหนึ่งที่ทำให้ MSR ตรงกับ GMV ดังที่อธิบายใน บท Expected Return แต่การนิยามโจทย์ GMV เองไม่ต้องสมมติค่าเฉลี่ย

เริ่มจากปัญหาที่มีเพียงข้อจำกัดลงทุนเต็มงบและยังอนุญาตน้ำหนักติดลบ หาก Σ\Sigma เป็น positive definite จะได้คำตอบ

wGMV=Σ−111TΣ−11,w^{\mathrm{GMV}}=\frac{\Sigma^{-1}\mathbf1}{\mathbf1^{\mathsf T}\Sigma^{-1}\mathbf1},

โดย 1\mathbf1 คือ vector ที่ทุกตำแหน่งเป็นหนึ่ง ตัวเศษให้น้ำหนักก่อนปรับสเกล และตัวหารทำให้ผลรวมน้ำหนักเท่ากับหนึ่ง เราไม่ต้องสร้าง inverse ขึ้นมาจริง ๆ: np.linalg.solve(covariance, ones) แก้สมการ Σx=1\Sigma x=\mathbf1 แล้วนำ xx มาหารด้วยผลรวม

ones = np.ones(len(asset_names))
gmv_raw = np.linalg.solve(covariance, ones)
gmv_unconstrained = gmv_raw / gmv_raw.sum()
print("GMV with budget constraint only (%):", np.round(100 * gmv_unconstrained, 4))
print(f"Annual SD: {portfolio_sd(gmv_unconstrained, covariance):.4%}")
print("All weights nonnegative:", np.all(gmv_unconstrained >= 0))

น้ำหนักที่ได้ประมาณ [0.7343, 27.8110, 70.6822, 0.7726]% และ SD 8.6855% ต่อปี ในข้อมูลชุดนี้คำตอบทุกตัวเป็นบวกอยู่แล้ว จึงยังทำตามเงื่อนไข long-only ได้ คำตอบจาก covariance ชุดอื่นอาจมีน้ำหนักติดลบ ซึ่งแปลว่าขายชอร์ตสินทรัพย์นั้น

เราไม่แก้ปัญหาขายชอร์ตด้วยการตัดน้ำหนักลบเป็นศูนย์แล้วหารใหม่ เพราะน้ำหนักที่เหลืออาจไม่ใช่คำตอบที่ดีที่สุดภายใต้ข้อจำกัดใหม่ ต้องแก้โจทย์ที่ใส่ข้อจำกัดนั้นตั้งแต่ต้น ส่วนสูตรข้างต้นต้องมี covariance ที่ invertible ตามเงื่อนไขของ np.linalg.solve

ระบุข้อจำกัดก่อนเรียกตัวแก้โจทย์

สำหรับพอร์ต long-only เราแก้ปัญหา

min⁡wwTΣwภายใต้∑iwi=1,0≤wi≤1.\min_w w^{\mathsf T}\Sigma w \quad\text{ภายใต้}\quad \sum_iw_i=1,\quad 0\leq w_i\leq1.

ขอบบนหนึ่งเป็นผลตามมาของน้ำหนักไม่ติดลบและผลรวมหนึ่ง เราใส่ไว้ในโค้ดเพื่ออ่านขอบเขตแต่ละตัวได้ตรง ๆ ปัญหานี้เป็น convex quadratic optimization เมื่อ covariance เป็น positive semidefinite และ covariance ที่กำหนดในบทเป็น positive definite จึงมีคำตอบ GMV เดียวบนชุดข้อจำกัดนี้

เราจะเขียนฟังก์ชันเดียวให้รองรับข้อจำกัด ENC ที่จะใช้ในหัวข้อถัดไปด้วย min_enc=None หมายถึงยังไม่กำหนด ENC ขั้นต่ำ ถ้าให้ค่า KK เราเพิ่มเงื่อนไข ∑iwi2≤1/K\sum_iw_i^2\leq1/K ซึ่งมาจากการจัดรูป ENC⁡(w)≥K\operatorname{ENC}(w)\geq K

ใน minimize, fun คือค่าที่ต้องการลด, x0 คือน้ำหนักเริ่มต้น, bounds คือช่วงของน้ำหนักแต่ละตัว และ constraints คือเงื่อนไขส่วนรวม lambda w: ... สร้างฟังก์ชันสั้นที่รับน้ำหนักแล้วคืนค่าด้านขวา เงื่อนไข eq ต้องคืนศูนย์ ส่วน ineq ต้องคืนค่ามากกว่าหรือเท่ากับศูนย์ jac ให้ความชันแก่ตัวแก้โจทย์ โดยความชันของ wTΣww^{\mathsf T}\Sigma w คือ 2Σw2\Sigma w

from scipy.optimize import minimize

def solve_min_variance(matrix, min_enc=None):
    n = len(matrix)
    if min_enc is not None and not 1 <= min_enc <= n:
        raise ValueError("The ENC target must lie between 1 and N.")
    if min_enc == n:
        return np.full(n, 1 / n)
    constraints = [{"type": "eq", "fun": lambda w: w.sum() - 1,
                    "jac": lambda w: np.ones(n)}]
    if min_enc is not None:
        constraints.append({"type": "ineq", "fun": lambda w: 1 / min_enc - w @ w,
                            "jac": lambda w: -2 * w})
    result = minimize(
        lambda w: w @ matrix @ w, np.full(n, 1 / n),
        jac=lambda w: 2 * matrix @ w, method="SLSQP",
        bounds=[(0, 1)] * n, constraints=constraints,
        options={"ftol": 1e-12, "maxiter": 1000},
    )
    if not result.success:
        raise RuntimeError(result.message)
    w = result.x
    if abs(w.sum() - 1) > 1e-8 or np.any(w < 0):
        raise RuntimeError("The returned weights violate the portfolio constraints.")
    if min_enc is not None and w @ w > 1 / min_enc + 1e-8:
        raise RuntimeError("The returned weights violate the ENC constraint.")
    return w

gmv_weights = solve_min_variance(covariance)
print("Long-only GMV weights (%):", np.round(100 * gmv_weights, 4))
print(f"GMV ENC: {effective_number(gmv_weights):.4f}")
print("Agrees with formula:", np.allclose(gmv_weights, gmv_unconstrained, atol=2e-6))

ฟังก์ชันใช้ SLSQP ของ SciPy เป็นตัวแก้โจทย์เชิงตัวเลข ftol เป็นเกณฑ์ความละเอียดในการหยุด และ maxiter จำกัดจำนวนรอบ result.success บอกสถานะการจบ ส่วน result.x เก็บน้ำหนัก เราตรวจเงื่อนไขอีกครั้งก่อนคืนผล ไม่เปลี่ยน covariance หรือปรับน้ำหนักเมื่อพบปัญหา รายละเอียดสถานะและเกณฑ์หยุดอยู่ใน เอกสาร SLSQP

ตัวอย่างนี้เรียกฟังก์ชันด้วยเมทริกซ์ covariance หรือ correlation ที่ตรวจแล้ว ฟังก์ชันยังไม่ได้เป็นเครื่องมือตรวจความถูกต้องของข้อมูลทั่วไป เช่น สลับลำดับสินทรัพย์ผิดหรือป้อนเมทริกซ์ที่ไม่สมมาตร หากนำไปใช้กับข้อมูลใหม่ต้องตรวจข้อมูลเหล่านั้นก่อน

น้ำหนักตรงกับสูตร GMV ภายในความละเอียดที่ตรวจ และ ENC ประมาณ 1.7329 ต่ำกว่า CW ที่มี ENC 1.9231 แม้ SD ของ GMV ต่ำกว่ามาก C มี volatility ต่ำที่สุดและ correlation ค่อนข้างต่ำกับตัวอื่นจึงได้รับน้ำหนักสูงในตัวอย่างนี้ อย่างไรก็ตาม GMV อาจถือสินทรัพย์ที่ volatility สูงได้ ถ้าความสัมพันธ์กับส่วนอื่นช่วยลด variance รวม การพิจารณา volatility ทีละตัวจึงใช้แทน covariance ทั้งตารางไม่ได้

กำหนดให้ GMV กระจายเงินอย่างน้อยเท่าไร

ถ้าผู้ลงทุนไม่ต้องการให้น้ำหนักกระจุกเท่า GMV เดิม สามารถเพิ่มเงื่อนไข

ENC⁡(w)≥K⟺∑iwi2≤1K.\operatorname{ENC}(w)\geq K \quad\Longleftrightarrow\quad \sum_i w_i^2\leq\frac{1}{K}.

สำหรับสี่สินทรัพย์ KK อยู่ในช่วง 1 ถึง 4 ถ้า K=1K=1 ทุกพอร์ต long-only ที่ลงทุนเต็มงบทำได้อยู่แล้ว ถ้า K=4K=4 คำตอบที่ทำได้มีเพียง EW เราจึงคืน EW โดยตรงในบรรทัด if min_enc == n ของฟังก์ชัน ส่วน K>4K>4 ทำไม่ได้ในจักรวาลนี้และฟังก์ชันจะหยุดพร้อมคำอธิบาย

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

รูปแบบ [คำสั่ง for k in enc_targets] เรียกคำสั่งหนึ่งครั้งต่อค่า k แล้วเก็บผลเป็นรายการ np.vstack นำ array น้ำหนักในรายการนั้นมาต่อเป็นแถว และ zip ช่วยวนอ่านค่าเป้าหมายกับแถวน้ำหนักที่ตรงกัน เราทดลองข้อจำกัดหลายระดับโดยใช้ covariance และขอบเขตน้ำหนักเดิมทั้งหมด

enc_targets = np.array([1.0, 2.0, 3.0, 3.5, 4.0])
enc_constrained_weights = np.vstack([
    solve_min_variance(covariance, min_enc=k) for k in enc_targets
])
gmv_enc_weights = solve_min_variance(covariance, min_enc=3)
print("Target  Achieved ENC  Annual SD  Weights A/B/C/D (%)")
for target, w in zip(enc_targets, enc_constrained_weights):
    print(f"{target:4.1f}    {effective_number(w):8.4f}    "
          f"{portfolio_sd(w, covariance):.4%}   {np.round(100 * w, 4)}")
ENC ขั้นต่ำ ENC ที่ได้ SD ต่อปี น้ำหนัก C
1 1.7329 8.6855% 70.6822%
2 2.0000 8.7649% 64.2765%
3 3.0000 9.9124% 46.4790%
3.5 3.5000 10.8682% 38.7245%
4 4.0000 13.1933% 25.0000%

เมื่อกำหนด K=3K=3 น้ำหนัก A–D คือประมาณ [14.7334, 29.4864, 46.4790, 9.3012]% จึงลดเงินใน C และย้ายไปตัวอื่น ค่า SD สูงขึ้นจาก 8.6855% เป็น 9.9124% ต่อปีตาม covariance ที่ใช้

การเพิ่ม KK ทำให้ชุดพอร์ตที่อนุญาตเล็กลง พอร์ตบางชุดที่เคยเลือกได้ถูกตัดออก ค่าต่ำสุดของ variance จึงลดลงไม่ได้ภายใต้ covariance เดิม บางช่วงอาจคงที่หากข้อจำกัดยังไม่กระทบคำตอบ เช่น GMV เดิมมี ENC 1.7329 จึงผ่านข้อกำหนดขั้นต่ำ 1 อยู่แล้ว

ข้อจำกัดนี้ทำให้แลก variance ที่ต่ำที่สุดตามค่าประมาณกับการกระจายเงินมากขึ้น เมื่อ covariance ประมาณผิด ผลนอกตัวอย่างอาจต่างจากลำดับในตาราง การเพิ่ม ENC จึงไม่ได้รับประกันว่าจะลดความเสี่ยงจริงหรือเพิ่ม Sharpe ratio

Maximum Decorrelation ใช้ความสัมพันธ์เป็นเกณฑ์

Maximum decorrelation ในบทนี้หมายถึงการเลือกน้ำหนักให้ wTρww^{\mathsf T}\rho w ต่ำที่สุด โดยใช้ correlation matrix ρ\rho แทน covariance พร้อมข้อจำกัด long-only และลงทุนเต็มงบเดิม:

min⁡wwTρw,1Tw=1,wi≥0.\min_w w^{\mathsf T}\rho w, \qquad \mathbf1^{\mathsf T}w=1,\quad w_i\geq0.

มองได้ว่าเราสร้างโลกสมมติซึ่งสินทรัพย์ทุกตัวมี volatility เท่ากับหนึ่ง แล้วหา GMV ในโลกนั้น เมื่อระดับ volatility เท่ากัน ความแตกต่างระหว่างสินทรัพย์ในโจทย์จึงมาจาก correlation น้ำหนักที่ลด wTρww^{\mathsf T}\rho w จะพิจารณาทั้งน้ำหนักและความสัมพันธ์ทุกคู่ รวมช่องแนวทแยงที่เท่ากับหนึ่งด้วย ไม่ใช่เพียงเลือกคู่ที่มี correlation ต่ำที่สุด

พอร์ต MDec ที่ถือจริงยังต้องประเมินความเสี่ยงด้วย covariance เดิม เพราะ A–D มี annual volatility 20%, 15%, 10% และ 25% ไม่ได้เปลี่ยนเป็นหนึ่งจริง ๆ การนำ wTρw\sqrt{w^{\mathsf T}\rho w} มาแสดงเป็น annual volatility ของพอร์ตเงินจริงจึงใช้หน่วยผิด

max_decorrelation_weights = solve_min_variance(correlation)
decorrelation_objective = max_decorrelation_weights @ correlation @ max_decorrelation_weights
print("MDec weights (%):", np.round(100 * max_decorrelation_weights, 4))
print(f"Correlation objective: {decorrelation_objective:.6f}")
print(f"ENC: {effective_number(max_decorrelation_weights):.4f}")
print(f"Actual-model annual SD: {portfolio_sd(max_decorrelation_weights, covariance):.4%}")

ได้สัดส่วนประมาณ [9.5102, 28.8155, 38.4396, 23.2346]% มี ENC 3.4034 และ SD ตาม covariance เดิม 11.2442% ต่อปี C ยังมีน้ำหนักสูงสุด แต่ต่ำกว่า 70.6822% ของ GMV เนื่องจากการลด volatility รายตัวไม่ใช่สิ่งที่ MDec ใช้แยกสินทรัพย์

หากทุกสินทรัพย์มี volatility เท่ากันที่ s>0s>0 จะได้ Σ=s2ρ\Sigma=s^2\rho การคูณ objective ด้วยค่าบวกคงที่ไม่เปลี่ยนน้ำหนักที่ทำให้ค่าต่ำที่สุด GMV และ MDec จึงตรงกันในกรณีนั้น เมื่อ volatility ต่างกันอย่างในตัวอย่าง ทั้งสองวิธีตอบคนละโจทย์

Maximum Diversification เปรียบเทียบความเสี่ยงก่อนและหลังรวมพอร์ต

Diversification ratio หรือ DR นิยามเป็น

DR⁡(w)=∑iwiσiwTΣw=wTσσp.\operatorname{DR}(w)=\frac{\sum_iw_i\sigma_i}{\sqrt{w^{\mathsf T}\Sigma w}} =\frac{w^{\mathsf T}\sigma}{\sigma_p}.

ตัวเศษเป็นค่าเฉลี่ยถ่วงน้ำหนักของ volatility รายตัว ตัวส่วนเป็น volatility ของพอร์ตรวม ทั้งสองใช้หน่วยและช่วงเวลาเดียวกัน จึงหารกันแล้วไม่มีหน่วย สูตรนี้และการเลือกพอร์ตที่ทำให้ DR สูงที่สุดอธิบายใน Choueifaty และ Coignard, Toward Maximum Diversification หน้า 40–43

สำหรับ EW ของเรา ตัวเศษคือ 17.5% ต่อปี และตัวส่วนคือ 13.1933% ต่อปี จึงได้ DR ประมาณ 17.5/13.1933=1.326417.5/13.1933=1.3264 เราเปรียบเทียบความเสี่ยงของสินทรัพย์ที่เลือกถ่วงน้ำหนักกับความเสี่ยงหลังรวมเป็นพอร์ต ไม่ได้ใช้ expected return ในเศษ

สำหรับ covariance ที่ถูกต้องและน้ำหนัก long-only ความผันผวนรวมไม่เกินค่าเฉลี่ยถ่วงน้ำหนักของความผันผวนรายตัว จึงได้ DR อย่างน้อยหนึ่งเมื่อส่วนเป็นบวก การถือสินทรัพย์ตัวเดียวที่มี volatility บวกให้ DR เท่ากับหนึ่ง และกรณีทุกสินทรัพย์เคลื่อนไหวสัมพันธ์กันสมบูรณ์ในทิศเดียวกันก็ให้ค่าเดียวกัน หากมีพอร์ต variance ศูนย์แต่ตัวเศษบวก อัตราส่วนนี้จะไม่เป็นจำนวนจำกัด จึงต้องพิจารณากรณีขอบแยกต่างหาก Covariance ชุดหลักของเราเป็น positive definite ทำให้พอร์ตที่ลงทุนเต็มงบไม่มี variance ศูนย์

เราจะหา MaxDR สองทางเพื่อตรวจผลกัน ทางแรกให้ SLSQP ลดค่าลบของ DR เพราะการทำให้ −DR⁡-\operatorname{DR} ต่ำที่สุดเท่ากับทำให้ DR สูงที่สุด ทางที่สองแปลงคำตอบจาก correlation ซึ่งจะอธิบายหลังโค้ด

np.diag(matrix) เมื่อรับเมทริกซ์จะดึง variance บนแนวทแยงออกมา แล้ว np.sqrt ให้ volatility รายตัว คำสั่งเดียวกันนี้จึงมีหน้าที่ต่างจาก np.diag ที่รับ array หนึ่งมิติในตัวอย่างก่อนหน้า

def diversification_ratio(weights, matrix):
    individual_vols = np.sqrt(np.diag(matrix))
    return (weights @ individual_vols) / portfolio_sd(weights, matrix)

max_dr_result = minimize(
    lambda w: -diversification_ratio(w, covariance), equal_weights,
    method="SLSQP", bounds=[(0, 1)] * len(asset_names),
    constraints=[{"type": "eq", "fun": lambda w: w.sum() - 1}],
    options={"ftol": 1e-12, "maxiter": 1000},
)
if not max_dr_result.success:
    raise RuntimeError(max_dr_result.message)
max_dr_weights = max_dr_result.x
if np.any(max_dr_weights < 0) or abs(max_dr_weights.sum() - 1) > 1e-8:
    raise RuntimeError("The MaxDR weights violate the portfolio constraints.")
dr_from_correlation = max_decorrelation_weights / annual_vols
dr_from_correlation = dr_from_correlation / dr_from_correlation.sum()
print("MaxDR weights (%):", np.round(100 * max_dr_weights, 4))
print(f"Maximum DR: {diversification_ratio(max_dr_weights, covariance):.6f}")
print("Matches correlation route:", np.allclose(max_dr_weights, dr_from_correlation, atol=2e-6))

ได้ MaxDR ประมาณ [6.6321, 26.7930, 53.6126, 12.9623]% และ DR 1.463196 ผลทั้งสองวิธีตรงกันภายในความละเอียดที่ตรวจ น้ำหนักนี้ต่างจาก MDec [9.5102, 28.8155, 38.4396, 23.2346]% จึงต้องระบุให้ชัดว่ากำลังใช้ correlation หาคำตอบโดยตรง หรือใช้ correlation เป็นขั้นกลางเพื่อหา MaxDR

เหตุผลที่หารน้ำหนัก MDec ด้วย volatility

กำหนดน้ำหนักใหม่

qi=wiσi∑jwjσj.q_i=\frac{w_i\sigma_i}{\sum_jw_j\sigma_j}.

ถ้า wi≥0w_i\geq0 และทุก σi>0\sigma_i>0 จะได้ qi≥0q_i\geq0 และ ∑iqi=1\sum_iq_i=1 เช่นเดียวกับน้ำหนักพอร์ต โดย qiq_i บอกสัดส่วนของพจน์ wiσiw_i\sigma_i ในผลรวม ตัวนี้ยังไม่ใช่ risk contribution เพราะการคำนวณสัดส่วนความเสี่ยงที่แต่ละสินทรัพย์มีต่อพอร์ตจริงต้องใช้ covariance กับสินทรัพย์อื่นด้วย

เมื่อเขียน Σ=diag⁡(σ)ρdiag⁡(σ)\Sigma=\operatorname{diag}(\sigma)\rho\operatorname{diag}(\sigma) จะจัดรูปได้ว่า

DR⁡(w)=1qTρq.\operatorname{DR}(w)=\frac{1}{\sqrt{q^{\mathsf T}\rho q}}.

การทำให้ DR สูงที่สุดจึงเท่ากับการหา qq ที่ลด qTρqq^{\mathsf T}\rho q ภายใต้ผลรวมหนึ่งและไม่ติดลบ ปัญหาหลังนี้ตรงกับโจทย์ที่ใช้หา MDec เราจึงใช้ max_decorrelation_weights เป็น qq ที่เหมาะสม แล้วกลับไปหาน้ำหนักเงินจริงด้วย

wi=qi/σi∑jqj/σj.w_i=\frac{q_i/\sigma_i}{\sum_jq_j/\sigma_j}.

การแปลงนี้อธิบายว่าทำไมโค้ดต้องหารด้วย annual_vols ก่อนปรับผลรวมเป็นหนึ่ง และช่วยตรวจคำตอบจากตัวแก้ ratio โดยตรงผ่านปัญหา quadratic ที่เป็น convex ในข้อมูลชุดนี้ เงื่อนไขที่ใช้คือ long-only และลงทุนเต็มงบ หากเพิ่มเพดานน้ำหนักหรือ ENC ขั้นต่ำของ ww ข้อจำกัดนั้นต้องถูกแปลงไปยัง qq ด้วย จะนำ MDec ภายใต้ข้อจำกัดเดิมมาหาร volatility แล้วสรุปว่าเป็น MaxDR ที่ทำตามข้อจำกัดเพิ่มเติมไม่ได้

หากทุกสินทรัพย์มี expected excess return เท่ากับ sσis\sigma_i ด้วยค่า Sharpe ร่วม s>0s>0 จะได้ Sharpe พอร์ตเท่ากับ sDR⁡(w)s\operatorname{DR}(w) จึงทำให้ MaxDR เป็น MSR ภายใต้สมมติฐานนั้น รายละเอียดกรณี s=0s=0 และ s<0s<0 อยู่ใน บท Expected Return ส่วนการเลือก DR เป็นเกณฑ์กระจายความเสี่ยงโดยตรงไม่ได้ยืนยันว่าตลาดมี Sharpe รายตัวเท่ากัน

เปรียบเทียบหกวิธีบนข้อมูลชุดเดียวกัน

ตารางต่อไปใช้ A–D ชุดเดิม ทุกพอร์ตเป็น long-only และลงทุนเต็มงบ มีเพียง GMV+ENC3 ที่เพิ่มข้อจำกัด ENC อย่างน้อย 3 เราคำนวณ SD และ DR ของทุกพอร์ตด้วย covariance ชุดเดียวกัน แม้เกณฑ์ที่ใช้หาน้ำหนักจะแตกต่างกัน

strategy_weights เป็น dictionary ซึ่งจับคู่ชื่อวิธีกับ array น้ำหนัก .items() ใช้วนอ่านทั้งชื่อและน้ำหนัก ส่วน strategy_metrics เก็บสามค่าตามลำดับ ENC, annual SD และ DR การแสดง SD ด้วย :.4% คูณด้วย 100 และเติมเครื่องหมายเปอร์เซ็นต์ แต่ค่าที่เก็บยังเป็นทศนิยม

strategy_weights = {
    "CW": cap_weights, "EW": equal_weights, "GMV": gmv_weights,
    "GMV+ENC3": gmv_enc_weights, "MDec": max_decorrelation_weights,
    "MaxDR": max_dr_weights,
}
strategy_metrics = {}
print("Method     ENC     Annual SD     DR      Weights A/B/C/D (%)")
for name, w in strategy_weights.items():
    enc = effective_number(w)
    sd = portfolio_sd(w, covariance)
    dr = diversification_ratio(w, covariance)
    strategy_metrics[name] = np.array([enc, sd, dr])
    print(f"{name:9s} {enc:.4f}  {sd:.4%}  {dr:.4f}  {np.round(100 * w, 2)}")
วิธี ENC SD ต่อปี DR น้ำหนัก A / B / C / D (%)
CW 1.9231 16.7824% 1.1321 70.00 / 10.00 / 10.00 / 10.00
EW 4.0000 13.1933% 1.3264 25.00 / 25.00 / 25.00 / 25.00
GMV 1.7329 8.6855% 1.3332 0.73 / 27.81 / 70.68 / 0.77
GMV+ENC3 3.0000 9.9124% 1.4470 14.73 / 29.49 / 46.48 / 9.30
MDec 3.4034 11.2442% 1.4120 9.51 / 28.82 / 38.44 / 23.23
MaxDR 2.6287 9.5320% 1.4632 6.63 / 26.79 / 53.61 / 12.96

GMV มี SD ต่ำที่สุดตาม covariance ที่ใช้ เพราะนั่นคือ objective ของมัน EW มี ENC สูงที่สุดเพราะแบ่งเงินเท่ากัน MaxDR มี DR สูงที่สุด แต่ SD สูงกว่า GMV และ ENC ต่ำกว่า EW ตัวเลขทั้งสามวัดคนละด้าน จึงไม่ควรนำค่าสูงสุดหรือต่ำสุดข้ามคอลัมน์มาตีความว่าเป็นวิธีที่ชนะทุกเกณฑ์

ข้อมูลที่ต้องใช้เพื่อกำหนดน้ำหนักต่างกันด้วย:

วิธี ข้อมูลที่กำหนดน้ำหนัก สิ่งที่เลือกให้เหมาะที่สุดหรือทำตามกฎ
CW ราคาและจำนวนหุ้นที่ใช้ถ่วงน้ำหนัก ลงเงินตามสัดส่วนมูลค่าตลาดในจักรวาล
EW รายชื่อสมาชิก แบ่งเงิน 1/N1/N ณ เวลาปรับสมดุล
GMV Covariance ลด variance ภายใต้ข้อจำกัด
GMV+ENC3 Covariance และ ENC ขั้นต่ำ ลด variance โดยต้องกระจายเงินถึงเกณฑ์
MDec Correlation ลด wTρww^{\mathsf T}\rho w
MaxDR Volatility และ correlation เพิ่มอัตราส่วน wTσ/σpw^{\mathsf T}\sigma/\sigma_p

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

เรายังไม่ได้กำหนด expected return ของ A–D จึงคำนวณ expected portfolio return หรือจัดอันดับ Sharpe ของหกวิธีจากตารางนี้ไม่ได้ DR ใช้ volatility รายตัวในเศษ ไม่ได้ใช้ excess return

ตรวจจำนวนข้อมูลและความไวของน้ำหนัก

ข้อมูลสั้นอาจทำให้ covariance ไม่มี inverse

ต่อไปเป็นตัวอย่างแยกของผลตอบแทนรายเดือนสมมติสี่เดือน สี่สินทรัพย์ ใช้เพื่อตรวจ rank เท่านั้น เราไม่นำ sample covariance ชุดนี้ไปแทน annual covariance ที่ใช้เปรียบเทียบหกวิธีข้างต้น

เมื่อมี TT observations และลบค่าเฉลี่ยแต่ละคอลัมน์แล้ว เมทริกซ์ข้อมูลที่ได้มี rank ไม่เกิน T−1T-1 ดังนั้น sample covariance ของสินทรัพย์ NN ตัวมี rank ไม่เกิน min⁡(N,T−1)\min(N,T-1) ในกรณี T=N=4T=N=4 จึงมี rank ได้ไม่เกิน 3 และไม่มี inverse แบบปกติ

mean(axis=0) หาค่าเฉลี่ยลงตามแต่ละคอลัมน์ การลบ array ค่าเฉลี่ยออกจากทุกแถวเรียกว่า broadcasting ส่วน .T สลับแถวกับคอลัมน์ np.linalg.matrix_rank ใช้ความละเอียดเชิงตัวเลขเพื่อตรวจจำนวนทิศทางที่เป็นอิสระต่อกัน

small_returns = np.array([
    [-0.02,  0.01,  0.005, -0.04],
    [ 0.01, -0.01,  0.002,  0.03],
    [ 0.02,  0.02,  0.001,  0.05],
    [ 0.05,  0.03, -0.003,  0.06],
])
small_centered = small_returns - small_returns.mean(axis=0)
small_sample_cov = small_centered.T @ small_centered / (len(small_returns) - 1)
print("Return matrix shape:", small_returns.shape)
print("Sample covariance rank:", np.linalg.matrix_rank(small_sample_cov))
print("Sample covariance eigenvalues:", np.linalg.eigvalsh(small_sample_cov))

ผลคือ shape (4, 4) และ rank 3 โดย eigenvalue ที่เล็กที่สุดอยู่ใกล้ศูนย์มาก บางเครื่องอาจแสดงเป็นค่าติดลบขนาดประมาณ 10−2010^{-20} จากความละเอียดทศนิยม ค่านี้ต่างจาก covariance ที่มี eigenvalue ติดลบอย่างมีนัยสำคัญจนให้ variance ติดลบได้

Covariance ที่ singular ยังนิยามโจทย์ long-only minimum variance ได้ แต่คำตอบอาจไม่เป็นเอกลักษณ์ และสูตรที่ใช้ Σ−1\Sigma^{-1} ใช้ไม่ได้ การเพิ่มจำนวนเดือนจนเกินจำนวนสินทรัพย์ช่วยแก้ข้อจำกัดด้าน rank นี้ได้ในบางข้อมูล แต่ยังไม่รับประกัน full rank หากมีสินทรัพย์ซ้ำหรือมีความสัมพันธ์เชิงเส้น และไม่ได้รับประกันความแม่นยำของค่าประมาณ วิธีประมาณด้วย factor model และ shrinkage อยู่ใน บท Covariance Estimation และ บท Covariance Shrinkage

เมทริกซ์คำนวณได้ ก็ยังประมาณผิดได้

กลับสู่ covariance รายปีชุดเดิม คราวนี้ลองสมมติว่าผู้จัดพอร์ตประเมิน volatility ของ C เป็น 8% แทน 10% โดยยังประเมิน correlation ทุกคู่ตามเดิม เราจะหา GMV ใหม่จากค่าประมาณนี้ แล้วประเมินน้ำหนักที่ได้ด้วย covariance เดิมเพื่อดูความไวของการตัดสินใจ

การทดลองนี้กำหนด covariance เดิมเป็นเกณฑ์อ้างอิงที่เรารู้ไว้ล่วงหน้าในโลกสมมติ ทั้งสองตารางเป็นพารามิเตอร์ที่ผู้เขียนกำหนด ไม่ใช่การประมาณจากข้อมูลย้อนหลังหรือการพิสูจน์ว่า covariance เดิมคือความจริงของตลาด

.copy() สร้าง array แยกต่างหาก ทำให้แก้ค่า C ใน stressed_vols ได้โดยไม่เปลี่ยน annual_vols ส่วน [2] เลือกสมาชิกตัวที่สาม เพราะ Python เริ่มนับตำแหน่งจากศูนย์

stressed_vols = annual_vols.copy()
stressed_vols[2] = 0.08
stressed_covariance = np.outer(stressed_vols, stressed_vols) * correlation
stressed_gmv_weights = solve_min_variance(stressed_covariance)
stressed_gmv_enc_weights = solve_min_variance(stressed_covariance, min_enc=3)
for name, w in [
    ("Original GMV", gmv_weights),
    ("Changed-estimate GMV", stressed_gmv_weights),
    ("Changed-estimate GMV+ENC3", stressed_gmv_enc_weights),
]:
    print(name, "weights (%):", np.round(100 * w, 4))
    print(f"  Changed-estimate SD: {portfolio_sd(w, stressed_covariance):.4%}; "
          f"reference SD: {portfolio_sd(w, covariance):.4%}; "
          f"ENC: {effective_number(w):.4f}")

GMV จากค่าประมาณใหม่เพิ่มน้ำหนัก C จาก 70.6822% เป็นประมาณ 80.3605% และลด ENC จาก 1.7329 เป็น 1.4616 พอร์ตใหม่นี้มี SD เพียง 7.3346% ตามค่าประมาณใหม่ แต่เมื่อประเมินด้วย covariance อ้างอิงเดิมจะได้ 8.8315% สูงกว่า 8.6855% ของ GMV เดิม

เมื่อเพิ่ม ENC ขั้นต่ำเป็น 3 พอร์ตจากค่าประมาณใหม่ถือ C ประมาณ 47.2364% และมี SD ตาม covariance อ้างอิง 9.9199% เทียบกับ 9.9124% ของ GMV+ENC3 ที่ใช้ค่าประมาณเดิม น้ำหนักเปลี่ยนน้อยลงในตัวอย่างนี้ แต่ความเสี่ยงอ้างอิงยังสูงกว่า GMV ที่ไม่ใส่ข้อจำกัด ENC เราจึงมองเห็นทั้งผลจำกัดการกระจุกตัวและต้นทุนใน objective ที่กำหนด ไม่ได้พบข้อรับรองว่าข้อจำกัดชนิดนี้ช่วยทุกสถานการณ์

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

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

1. ซื้อจำนวนหุ้นเท่ากันได้ EW หรือไม่

ใช้ราคา A–D เท่ากับ 70, 20, 10 และ 50 บาท ถ้าซื้ออย่างละหนึ่งหุ้น จะได้น้ำหนักเท่าไร และเป็น CW ตามจำนวนหุ้นบริษัทที่กำหนดในบทหรือไม่

เฉลย: มูลค่ารวม 150 บาท น้ำหนักจึงเป็น [70,20,10,50]/150[70,20,10,50]/150 หรือประมาณ [46.6667, 13.3333, 6.6667, 33.3333]% ยังไม่ใช่ EW ซึ่งต้องแบ่งมูลค่าตัวละ 25% และไม่ใช่ CW ของตัวอย่าง เพราะการซื้อหนึ่งหุ้นต่อบริษัทไม่ได้ถ่วงด้วยจำนวนหุ้นบริษัท 10, 5, 10 และ 2 ล้านหุ้น

2. ถือสามตัวแปลว่า ENC เท่ากับสามหรือไม่

พอร์ตมีน้ำหนัก [0.50, 0.25, 0.25, 0] จงหา ENC แล้วเทียบกับจำนวนสินทรัพย์ที่มีน้ำหนักเป็นบวก

เฉลย: ผลรวมกำลังสองคือ 0.25+0.0625+0.0625=0.3750.25+0.0625+0.0625=0.375 จึงได้ ENC =1/0.375≈2.6667=1/0.375\approx2.6667 แม้ถือสามตัวจริง หากต้องการ ENC เท่ากับสามโดยถือเพียงสามตัว ต้องแบ่งเงินสามตัวนั้นเท่ากันตัวละหนึ่งในสาม

3. แยกกองทุนเป็นสองชื่อช่วยลด SD เท่าไร

กองทุนสองกองมีผลตอบแทนเหมือนกันทุกช่วง ทั้งคู่มี annual SD 20% ถ้าถือกองละ 50% จะได้ ENC และ SD เท่าไร

เฉลย: ENC เท่ากับ 2 เพราะน้ำหนักเท่ากัน แต่ correlation เท่ากับหนึ่ง จึงได้ SD =0.5(20%)+0.5(20%)=20%=0.5(20\%)+0.5(20\%)=20\% เหมือนถือกองเดียว การแยกชื่อไม่เพิ่มแหล่งความเสี่ยงที่แตกต่างกันในกรณีนี้

4. CW ต้องขายตัวที่ขึ้นราคาหรือไม่

เริ่มจาก CW [70, 10, 10, 10]% ให้เฉพาะ A ขึ้นราคา 10% ส่วนตัวอื่นราคาเดิม ไม่มีปันผลและจำนวนหุ้นบริษัทคงที่ น้ำหนักใหม่ของ A เท่าไร ต้องขาย A เพื่อรักษา CW หรือไม่

เฉลย: มูลค่าตามสัดส่วนเดิมเปลี่ยนจาก [70, 10, 10, 10] เป็น [77, 10, 10, 10] รวม 107 น้ำหนัก A จึงเป็น 77/107≈71.962677/107\approx71.9626% พอร์ตที่ถือจำนวนหุ้นเดิมจะเลื่อนไปตรงกับ CW ใหม่อยู่แล้ว ถ้าขาย A ให้กลับมา 70% จะเป็นการคงน้ำหนักเป้าหมายเดิม ไม่ใช่ตาม CW ใหม่ในตัวอย่างนี้

5. ENC ขั้นต่ำเป็นสี่เหลือพอร์ตให้เลือกกี่ชุด

มีสี่สินทรัพย์ น้ำหนักไม่ติดลบและรวมหนึ่ง ถ้ากำหนด ENC อย่างน้อยสี่ จะยังหา GMV ที่ให้น้ำหนักต่างจาก EW ได้หรือไม่

เฉลย: ไม่ได้ เพราะ ∑i(wi−1/4)2≥0\sum_i(w_i-1/4)^2\geq0 เมื่อกระจายพจน์และใช้ ∑iwi=1\sum_iw_i=1 จะได้ ∑iwi2≥1/4\sum_iw_i^2\geq1/4 ดังนั้น ENC ไม่เกินสี่ และเกิดค่าเท่ากันได้เมื่อทุกพจน์ wi−1/4w_i-1/4 เป็นศูนย์เท่านั้น จึงเหลือน้ำหนัก [0.25, 0.25, 0.25, 0.25] ชุดเดียว

6. แยก MDec, MaxDR และ GMV ด้วยสองสินทรัพย์

ให้สินทรัพย์สองตัวมี annual SD 10% และ 20% และ correlation เท่ากับศูนย์ ภายใต้ long-only และลงทุนเต็มงบ จงหาน้ำหนักของสามวิธีโดยใช้สูตรในบท

เฉลย: Correlation matrix เป็น identity จึงได้ MDec [0.5, 0.5] สำหรับ MaxDR ให้นำ [0.5, 0.5] หารด้วย [0.10, 0.20] ได้ [5, 2.5] แล้วหารผลรวม 7.5 จึงเป็น [2/3, 1/3] ส่วน GMV เมื่อ covariance เป็นแนวทแยงจะถ่วงน้ำหนักตาม inverse variance คือ [1/0.01, 1/0.04]=[100,25] หาร 125 ได้ [0.8,0.2] ค่า SD ของ MDec, MaxDR และ GMV ประมาณ 11.1803%, 9.4281% และ 8.9443% ต่อปีตามลำดับ MaxDR มี DR 2≈1.4142\sqrt2\approx1.4142 แม้ SD ยังสูงกว่า GMV

7. ข้อมูลสิบเดือนพอหา inverse ของ covariance ยี่สิบสินทรัพย์หรือไม่

ข้อมูลครบทุกเดือน ไม่มี missing values และคำนวณ sample covariance โดยลบค่าเฉลี่ยตามปกติ เมทริกซ์มีขนาด 20×2020\times20 จะ invertible ได้หรือไม่

เฉลย: Rank ไม่เกิน min⁡(20,10−1)=9\min(20,10-1)=9 จึงไม่เป็น full rank และไม่มี inverse แบบปกติ การมีตัวเลขครบ 400 ช่องไม่ได้แปลว่ามีข้อมูลอิสระพอประมาณทุกทิศทาง ควรทบทวนช่วงข้อมูลและวิธีประมาณ covariance พร้อมข้อสมมติ ไม่ใช้การเติมเลขเล็ก ๆ บนแนวทแยงโดยไม่อธิบายผลที่เปลี่ยนแบบจำลอง

8. จากตารางหกวิธี เลือกผู้ชนะด้าน Sharpe ได้หรือยัง

MaxDR มี DR สูงสุด ขณะที่ GMV มี SD ต่ำสุด ข้อมูลใดขาดอยู่หากต้องการเปรียบเทียบ expected Sharpe และต้องเพิ่มอะไรหากต้องการทดสอบผลหลังต้นทุน

เฉลย: ต้องมี expected excess return ที่สอดคล้องกับช่วงเวลาของ covariance จึงคำนวณ expected Sharpe ได้ ค่า DR ใช้ volatility รายตัวแทน expected excess return ในเศษ จะเทียบสองค่านี้โดยตรงได้ภายใต้สมมติฐานเพิ่มเติม เช่น ทุกสินทรัพย์มี Sharpe ร่วมที่เป็นบวก หากต้องการผลหลังต้นทุนต้องกำหนดช่วงทดสอบนอกตัวอย่าง จังหวะประมาณพารามิเตอร์และปรับสมดุล รายชื่อสินทรัพย์ที่รู้ ณ เวลานั้น ตลอดจนต้นทุนและข้อจำกัดการซื้อขาย ข้อมูลที่กำหนดในบทพอเปรียบเทียบเกณฑ์ทางคณิตศาสตร์ แต่ยังไม่มีเส้นทางตลาดสำหรับทดสอบผลลงทุนจริง

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

แหล่งที่มาและขอบเขตของตัวอย่าง

บทเรียนเรียบเรียงใหม่จากหัวข้อในคอร์ส Advanced Portfolio Construction and Analysis with Python โดยอ่าน Transcript ของ Naive Diversification และ Scientific Diversification ครบทั้งสองบทเมื่อ 3 ตุลาคม 2026 ใช้เป็นลำดับแนวคิดเรื่องความกระจุกตัว ENC, GMV และการเลือกน้ำหนักด้วย correlation

คำอธิบาย CW และการดูแลดัชนีตรวจเทียบกับ S&P DJI: Methodology Matters ส่วน DR และความเชื่อมโยงกับ correlation อ้างอิงส่วนคำนิยามและทฤษฎีหน้า 40–43 ของ Choueifaty และ Coignard (2008), Toward Maximum Diversification บทนี้ไม่ได้ทำซ้ำผลทดลองตลาดในงานนั้นและไม่ได้ใช้ผลในอดีตเป็นข้อรับรองผลตอบแทนของวิธีใด

เอกสารประกอบโค้ดที่ตรวจเมื่อ 3 ตุลาคม 2026 ได้แก่ scipy.optimize.minimize แบบ SLSQP, numpy.linalg.solve และ numpy.linalg.matrix_rank ทุก array ในบทเป็นตัวอย่างสมมติที่สร้างขึ้นใหม่ โค้ดใช้ NumPy และ SciPy โดยไม่มี CSV, toolkit หรือ Transcript ของคอร์สรวมอยู่ในไฟล์ดาวน์โหลด

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