top of page
Portfolio Management Toolkit Notesสารบัญ
Portfolio Management Toolkit Notesพิมพ์หน้านี้

Advanced · Expected Returns · บทย่อย 3

Black–Litterman: รวมค่าตั้งต้นกับ Views

ถ้าค่าตั้งต้นบอกว่าผลตอบแทนคาดหวังอยู่ที่ 4% แต่งานวิเคราะห์ของเราประเมินได้ 6% เราควรใช้ตัวเลขใด และควรเชื่อตัวเลขนั้นแค่ไหน?

บทก่อนหน้า Implied Returns และ Active Views เตรียมค่าตั้งต้น Π\Pi จากน้ำหนักอ้างอิง และเขียนมุมมองเพิ่มเติมเป็น P,Q,ΩP,Q,\Omega แล้ว ขั้นตอนของ Black–Litterman ในบทนี้คือรวมข้อมูลสองส่วน โดยให้ความไม่แน่นอนของแต่ละส่วนกำหนดขนาดการปรับค่าเฉลี่ย จากนั้นจึงนำผลไปเลือกน้ำหนักพอร์ตภายใต้ข้อจำกัดที่ต้องการ

ตัวอย่างทั้งหมดเป็นข้อมูลสมมติที่เขียนขึ้นใหม่ ใช้ผลตอบแทนคาดหวังส่วนเกินจากอัตราปลอดความเสี่ยงในช่วงหนึ่งปีเดียวกัน หน่วยทศนิยม เช่น 0.04 หมายถึง 4% ต่อปี ไม่มีต้นทุน ภาษี หรือข้อมูลราคาตลาดจริง โค้ดรันตามลำดับใน Notebook ใหม่ได้ด้วย NumPy, pandas และ SciPy โดยไม่ต้องรันบทก่อนหน้า ดาวน์โหลด Notebook

ลองรวมค่าประมาณเพียงตัวเดียวก่อน

สมมติว่าเราต้องการประมาณค่าเฉลี่ยผลตอบแทนส่วนเกินที่ยังไม่รู้ เรียกค่านี้ว่า μ\mu ข้อมูลก่อนเพิ่ม View หรือ prior ของเราระบุว่า μ\mu มีค่าเฉลี่ย 4% และ SD ของความไม่แน่นอนเท่ากับ 2 จุดเปอร์เซ็นต์ เขียนด้วยการแจกแจง Normal ได้ว่า

μ∼N(0.04,  0.022).\mu\sim N(0.04,\;0.02^2).

เลขตัวที่สองใน N(mean,variance)N(\text{mean},\text{variance}) เป็น variance จึงต้องยกกำลังสอง ส่วน 2 จุดเปอร์เซ็นต์นี้วัดความไม่แน่นอนเกี่ยวกับ μ\mu ไม่ใช่ SD ของผลตอบแทนที่สินทรัพย์จะให้ในปีหน้า

งานวิเคราะห์อีกส่วนให้ View ว่า μ\mu อยู่ที่ 6% โดยมีความคลาดเคลื่อนแบบ Normal ที่ SD เท่ากับ 1 จุดเปอร์เซ็นต์ เราสมมติว่าความคลาดเคลื่อนของ View เป็นอิสระจากค่า μ\mu ที่มี prior ข้างต้น การรวมข้อมูลภายใต้สมมติฐานนี้ใช้น้ำหนักแปรผกผันกับ variance เรียก 1/variance1/\text{variance} ว่า precision

vpost=11/0.022+1/0.012,μpost=vpost(0.040.022+0.060.012).v_{\text{post}}=\frac{1}{1/0.02^2+1/0.01^2},\qquad \mu_{\text{post}}=v_{\text{post}} \left(\frac{0.04}{0.02^2}+\frac{0.06}{0.01^2}\right).

Prior มี precision 2,500 ส่วน View มี precision 10,000 จึงให้น้ำหนัก 20% กับ prior และ 80% กับ View ค่าเฉลี่ยหลังรวม หรือ posterior เท่ากับ 0.2(4%)+0.8(6%)=5.6%0.2(4\%)+0.8(6\%)=5.6\%

import numpy as np
import pandas as pd
from scipy.optimize import minimize

scalar_prior_mean, scalar_prior_se = 0.04, 0.02
scalar_view_mean, scalar_view_se = 0.06, 0.01
scalar_prior_precision = 1 / scalar_prior_se**2
scalar_view_precision = 1 / scalar_view_se**2
scalar_posterior_var = 1 / (scalar_prior_precision + scalar_view_precision)
scalar_posterior_mean = scalar_posterior_var * (
    scalar_prior_precision * scalar_prior_mean
    + scalar_view_precision * scalar_view_mean
)
print(f"Posterior expected return: {scalar_posterior_mean:.4%}")
print(f"Posterior uncertainty SD: {np.sqrt(scalar_posterior_var):.4%}")
print("Weights on prior and view:",
      scalar_posterior_var * np.array([scalar_prior_precision, scalar_view_precision]))

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

ในโค้ด **2 คือยกกำลังสอง และ np.sqrt คือรากที่สอง ส่วนรูปแบบ :.4% คูณตัวเลขทศนิยมด้วย 100 เพื่อแสดงเป็นเปอร์เซ็นต์สี่ตำแหน่ง ตัวแปรยังเก็บค่าที่ไม่ปัดเศษไว้สำหรับคำนวณต่อ

กลับมาที่สามสินทรัพย์และสอง Views

ใช้สินทรัพย์สมมติ A/B/C ชุดเดียวกับบทก่อนหน้า น้ำหนักอ้างอิงคือ w0=(0.5,0.3,0.2)Tw_0=(0.5,0.3,0.2)^\mathsf{T} และ covariance ของผลตอบแทนส่วนเกินหนึ่งปีคือ

Σ=[0.040.0120.0080.0120.02250.00450.0080.00450.01].\Sigma=\begin{bmatrix} 0.04&0.012&0.008\\ 0.012&0.0225&0.0045\\ 0.008&0.0045&0.01 \end{bmatrix}.

รากที่สองของแนวทแยงให้ SD ผลตอบแทน 20%, 15% และ 10% ต่อปีตามลำดับ เรากำหนดค่าความเกลียดความเสี่ยงสำหรับสร้าง prior เป็น δ=2.5\delta=2.5 จึงได้ implied excess returns

Π=δΣw0=[0.0630.0341250.018375].\Pi=\delta\Sigma w_0 =\begin{bmatrix}0.063\\0.034125\\0.018375\end{bmatrix}.

นี่เป็นผลตอบแทนที่สอดคล้องกับน้ำหนักอ้างอิงภายใต้ covariance และแบบจำลองที่เลือก ไม่ใช่ผลตอบแทนอนาคตที่อ่านออกมาจากราคาตลาดได้โดยปราศจากสมมติฐาน

สอง Views ของเราคือ C มีผลตอบแทนส่วนเกินคาดหวัง 2.5% และ A มีผลตอบแทนคาดหวังสูงกว่า B อยู่ 2 จุดเปอร์เซ็นต์:

P=[0011−10],Q=[0.0250.020].P=\begin{bmatrix}0&0&1\\1&-1&0\end{bmatrix},\qquad Q=\begin{bmatrix}0.025\\0.020\end{bmatrix}.

แถวแรกเลือกค่าเฉลี่ยของ C ส่วนแถวที่สองนำค่าเฉลี่ย A ลบ B ดังนั้น PμP\mu มีสองค่า ตรงกับจำนวน Views ไม่ใช่จำนวนสินทรัพย์ หาก View แรกเป็นผลตอบแทนรวมแทนผลตอบแทนส่วนเกิน ต้องหักอัตราปลอดความเสี่ยงของช่วงเดียวกันก่อน ส่วน View แบบ A ลบ B หักอัตราปลอดความเสี่ยงเดียวกันออกจากทั้งสองฝั่งแล้วหายไป

กำหนดความไม่แน่นอนของ prior เป็น τΣ\tau\Sigma โดย τ=0.05\tau=0.05 และใช้กฎสาธิตของ Lab สำหรับ ความไม่แน่นอนของ Views: เก็บเฉพาะแนวทแยงของ PτΣPTP\tau\Sigma P^\mathsf{T} เป็น Ω\Omega ค่า 0.05 และกฎนี้เป็นข้อกำหนดของตัวอย่าง ไม่ได้ประมาณมาจากความแม่นยำของผู้วิเคราะห์

assets = ["A", "B", "C"]
covariance = np.array([[0.0400, 0.0120, 0.0080],
                       [0.0120, 0.0225, 0.0045],
                       [0.0080, 0.0045, 0.0100]])
benchmark_weights = np.array([0.5, 0.3, 0.2])
risk_aversion = 2.5
pi = risk_aversion * covariance @ benchmark_weights
P = np.array([[0.0, 0.0, 1.0], [1.0, -1.0, 0.0]])
Q = np.array([0.025, 0.020])
tau = 0.05
prior_mean_cov = tau * covariance
projected_prior_cov = P @ prior_mean_cov @ P.T
omega = np.diag(np.diag(projected_prior_cov))
print("Prior excess returns (%):", np.round(pi * 100, 4))
print("View surprises (percentage points):", np.round((Q - P @ pi) * 100, 4))
print("Projected prior covariance:\n", projected_prior_cov)
print("View-error covariance:\n", omega)

@ คูณเมทริกซ์ ส่วน .T สลับแถวกับคอลัมน์ np.diag ครั้งแรกดึงแนวทแยงออกมาเป็นเวกเตอร์ และครั้งที่สองนำเวกเตอร์นั้นไปสร้างเมทริกซ์แนวทแยง จึงได้

PτΣPT=[0.00050.0001750.0001750.001925],Ω=[0.0005000.001925].P\tau\Sigma P^\mathsf{T} =\begin{bmatrix}0.0005&0.000175\\0.000175&0.001925\end{bmatrix},\qquad \Omega=\begin{bmatrix}0.0005&0\\0&0.001925\end{bmatrix}.

เมทริกซ์แรกวัดความไม่แน่นอนของค่าที่ prior คาดสำหรับสอง Views ส่วนเมทริกซ์หลังวัดความคลาดเคลื่อนของแหล่งข้อมูล Views ที่เราใส่เพิ่ม การตั้งช่องนอกแนวทแยงของ Ω\Omega เป็นศูนย์จึงเป็นสมมติฐานว่าความคลาดเคลื่อนของ Views เป็นอิสระ ไม่ได้หมายความว่าพอร์ตที่ Views กล่าวถึงไม่มีความสัมพันธ์กัน

ความต่าง Q−PΠQ-P\Pi หรือ view surprise เท่ากับ (0.006625,−0.008875)T(0.006625,-0.008875)^\mathsf{T} View ของ C สูงกว่า prior 0.6625 จุดเปอร์เซ็นต์ แต่ View A ชนะ B อยู่ 2 จุดเปอร์เซ็นต์ต่ำกว่า prior spread ที่ 2.8875 จุดเปอร์เซ็นต์ จึงเป็นการปรับความคาดหวังส่วนต่าง A/B ลง แม้ประโยคของ View จะยังใช้คำว่า A ชนะ B

ปรับค่าเฉลี่ยด้วยขนาดความไม่แน่นอน

แบบจำลองกำหนดให้ μ∼N(Π,τΣ)\mu\sim N(\Pi,\tau\Sigma) และ Q=Pμ+εQ=P\mu+\varepsilon โดย ε∼N(0,Ω)\varepsilon\sim N(0,\Omega) และ ε\varepsilon เป็นอิสระจาก μ\mu เราจะใช้สัญลักษณ์ μBL\mu_{BL} สำหรับค่าเฉลี่ย posterior ของ μ\mu

เริ่มจากเมทริกซ์ขนาดสอง Views คูณสอง Views:

Av=PτΣPT+Ω.A_v=P\tau\Sigma P^\mathsf{T}+\Omega.

จากนั้นสร้างเมทริกซ์ตัวคูณการปรับ หรือ gain:

G=τΣPTAv−1,μBL=Π+G(Q−PΠ).G=\tau\Sigma P^\mathsf{T}A_v^{-1},\qquad \mu_{BL}=\Pi+G(Q-P\Pi).

สูตรมีความหมายว่าเริ่มจาก prior แล้วบวกการปรับตามความต่างของ Views ส่วน GG กำหนดว่าความต่างแต่ละ View ส่งต่อไปยังค่าเฉลี่ยแต่ละสินทรัพย์เท่าใด โดยใช้ทั้ง covariance และความไม่แน่นอนที่ระบุไว้

ตัวแปร ขนาดในตัวอย่าง ความหมาย
Π,μBL\Pi,\mu_{BL} 3 ค่า ค่าเฉลี่ยผลตอบแทนส่วนเกินของ A/B/C
PP 2 × 3 แปลงค่าเฉลี่ยสินทรัพย์เป็นค่าของสอง Views
Q−PΠQ-P\Pi 2 ค่า ความต่างของ Views จาก prior
AvA_v 2 × 2 ความไม่แน่นอนรวมในพื้นที่ของ Views
GG 3 × 2 ส่งความต่างสองค่าไปปรับค่าเฉลี่ยสามสินทรัพย์

ในโปรแกรมเราไม่จำเป็นต้องสร้าง inverse ทั้งเมทริกซ์ np.linalg.solve(A, B) หา XX ที่ทำให้ AX=BAX=B โค้ดต่อไปแก้ AvX=PτΣA_v X=P\tau\Sigma แล้ว transpose คำตอบเพื่อให้ได้ GG โดยใช้ความสมมาตรของเมทริกซ์ covariance

view_system = P @ prior_mean_cov @ P.T + omega
gain = np.linalg.solve(view_system, P @ prior_mean_cov).T
posterior_mean = pi + gain @ (Q - P @ pi)
posterior_mean_cov = prior_mean_cov - gain @ P @ prior_mean_cov
predictive_cov = covariance + posterior_mean_cov
print("Gain matrix:\n", np.round(gain, 6))
print(pd.DataFrame({"Prior excess (%)": pi * 100,
                    "Posterior excess (%)": posterior_mean * 100}, index=assets).round(4).to_string())
print("Posterior view means (%):", np.round(P @ posterior_mean * 100, 4))
print("Posterior uncertainty of the mean:\n", np.round(posterior_mean_cov, 8))

ได้ค่าเฉลี่ย posterior ของ A/B/C ประมาณ 6.2156%, 3.7098% และ 2.1458% ต่อปี ช่อง A ของ gain บอกว่าต้องคูณ surprise ของ View C ด้วย 0.339061 และ surprise ของ View A/B ด้วย 0.348225 จึงคำนวณการปรับ A ได้ประมาณ

0.339061(0.006625)+0.348225(−0.008875)≈−0.0008442.0.339061(0.006625)+0.348225(-0.008875) \approx-0.0008442.

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

เมื่ออ่านผลผ่าน PμBLP\mu_{BL} เราได้ C ประมาณ 2.1458% และ A ลบ B ประมาณ 2.5057 จุดเปอร์เซ็นต์ Views ยังไม่ถูกบังคับให้เกิดขึ้นตรงตัว เพราะ Ω\Omega ระบุว่าข้อมูล Views มีความคลาดเคลื่อน

ความไม่แน่นอนของค่าเฉลี่ยยังไม่ใช่ความเสี่ยงของผลตอบแทน

นอกจากค่าเฉลี่ย posterior แล้ว เราคำนวณ covariance ของความไม่แน่นอนที่เหลือเกี่ยวกับ μ\mu ได้ด้วย:

M=τΣ−GPτΣ.M=\tau\Sigma-GP\tau\Sigma.

ภายใต้โมเดลนี้ μ\mu หลังรับ Views มีการแจกแจง N(μBL,M)N(\mu_{BL},M) ถ้าต้องการความเสี่ยงของผลตอบแทนในปีหน้าต้องพิจารณาอีกชั้นหนึ่ง สมมติให้ผลตอบแทนส่วนเกิน RR เมื่อทราบ μ\mu มีค่าเฉลี่ย μ\mu และ covariance Σ\Sigma ที่ถือว่าทราบแล้ว ความผันผวนรอบค่าเฉลี่ยนี้ยังคงมีอยู่ แม้เราจะรู้ค่าเฉลี่ยอย่างแม่นยำ

การรวมความไม่แน่นอนสองชั้นให้

Cov⁡(R∣Views)=Σ⏟ความผันผวนรอบค่าเฉลี่ย+M⏟ความไม่แน่นอนของค่าเฉลี่ย=Σpredictive.\operatorname{Cov}(R\mid\text{Views}) =\underbrace{\Sigma}_{\text{ความผันผวนรอบค่าเฉลี่ย}} +\underbrace{M}_{\text{ความไม่แน่นอนของค่าเฉลี่ย}} =\Sigma_{\text{predictive}}.

เราจึงเก็บทั้งสามผลแยกกัน: mean คือ μBL\mu_{BL}, mean_cov คือ MM และ covariance + mean_cov คือ predictive covariance อย่านำ MM เพียงอย่างเดียวไปใช้เป็น covariance ของผลตอบแทนสินทรัพย์

อ่านเพิ่ม: สูตรที่เขียนด้วย precision matrix

หาก τΣ\tau\Sigma และ Ω\Omega กลับเมทริกซ์ได้ สูตรเดียวกันเขียนเป็น

M=[(τΣ)−1+PTΩ−1P]−1,M=\left[(\tau\Sigma)^{-1}+P^\mathsf{T}\Omega^{-1}P\right]^{-1},
μBL=M[(τΣ)−1Π+PTΩ−1Q].\mu_{BL}=M\left[(\tau\Sigma)^{-1}\Pi +P^\mathsf{T}\Omega^{-1}Q\right].

นี่เป็นรูปหลายมิติของการบวก precision ในตัวอย่างแรก รูป gain ที่เราใช้ไม่ต้องกลับ Ω\Omega โดยตรง จึงใช้กับบางกรณีที่ Ω\Omega เป็น singular ได้ ตราบใดที่ AvA_v ยังแก้ระบบสมการได้ อย่างไรก็ตามการเปลี่ยนรูปสูตรไม่ได้ทำให้ข้อมูลซ้ำหรือระบบ singular ทุกชนิดหายไป

รวมขั้นตอนเป็นฟังก์ชันเพื่อทดลองเปลี่ยนข้อมูลเข้าได้สะดวก ฟังก์ชันนี้ใช้ array หนึ่งมิติสำหรับ Π,Q\Pi,Q และเมทริกซ์สองมิติสำหรับตัวอื่น โดยให้ลำดับสินทรัพย์ตรงกันทุกจุด

def bl_posterior(covariance, pi, P, Q, omega, tau=0.05):
    covariance, pi, P, Q, omega = [np.asarray(x, dtype=float)
                                  for x in (covariance, pi, P, Q, omega)]
    if (covariance.ndim != 2 or covariance.shape[0] == 0
            or covariance.shape[0] != covariance.shape[1] or pi.ndim != 1
            or P.ndim != 2 or Q.ndim != 1):
        raise ValueError("Need a square covariance, vectors pi/Q and a matrix P")
    n, k = len(pi), len(Q)
    if covariance.shape != (n, n) or P.shape != (k, n) or omega.shape != (k, k):
        raise ValueError("Asset and view dimensions do not match")
    if not all(np.isfinite(x).all() for x in (covariance, pi, P, Q, omega)):
        raise ValueError("Inputs must be finite")
    if not np.isfinite(tau) or tau <= 0:
        raise ValueError("Tau must be finite and positive")
    if (not np.allclose(covariance, covariance.T, atol=1e-12)
            or not np.allclose(omega, omega.T, atol=1e-12)):
        raise ValueError("Covariance matrices must be symmetric")
    np.linalg.cholesky(covariance)  # requires positive definite asset covariance
    prior_cov = tau * covariance
    if k == 0:
        return pi.copy(), prior_cov, covariance + prior_cov
    if np.linalg.eigvalsh(omega).min() < -1e-12:
        raise ValueError("View-error covariance must be positive semidefinite")
    system = P @ prior_cov @ P.T + omega
    system_eigenvalues = np.linalg.eigvalsh(system)
    if system_eigenvalues.min() <= 1e-12 * system_eigenvalues.max():
        raise np.linalg.LinAlgError("View system is singular or too close to singular")
    np.linalg.cholesky(system)  # redundant exact views may make this singular
    update = np.linalg.solve(system, P @ prior_cov).T
    mean = pi + update @ (Q - P @ pi)
    mean_cov = prior_cov - update @ P @ prior_cov
    mean_cov = (mean_cov + mean_cov.T) / 2  # remove roundoff asymmetry
    return mean, mean_cov, covariance + mean_cov

bl_mean, bl_M, bl_predictive = bl_posterior(covariance, pi, P, Q, omega, tau)
assert np.allclose(bl_mean, posterior_mean)
assert np.allclose(bl_M, posterior_mean_cov)
print(pd.DataFrame({
    "Prior mean uncertainty SD (%)": np.sqrt(np.diag(prior_mean_cov)) * 100,
    "Posterior mean uncertainty SD (%)": np.sqrt(np.diag(bl_M)) * 100,
    "Conditional return SD (%)": np.sqrt(np.diag(covariance)) * 100,
    "Predictive return SD (%)": np.sqrt(np.diag(bl_predictive)) * 100
}, index=assets).round(4).to_string())

สำหรับ C, SD ของความไม่แน่นอนเกี่ยวกับค่าเฉลี่ยลดจากประมาณ 2.2361 เป็น 1.5748 จุดเปอร์เซ็นต์ แต่ SD ของผลตอบแทนที่คาดการณ์ยังอยู่ประมาณ 10.1232% ต่อปี เพราะรวมความผันผวนพื้นฐาน 10% ด้วย การลดความไม่แน่นอนของค่าเฉลี่ยไม่ได้ทำให้การลงทุนไม่มีความเสี่ยง

หาก Views ตรงกับ prior พอดีจน Q−PΠ=0Q-P\Pi=0, ค่าเฉลี่ย posterior จะไม่เปลี่ยน แต่ข้อมูล Views ที่กำหนดว่ามีความแม่นยำอาจยังลด MM ได้ การมีข้อมูลมายืนยันค่าเดิมจึงต่างจากการไม่มี Views เลย

np.asarray(..., dtype=float) ทำให้ข้อมูลเข้าอยู่ในรูป array ตัวเลข np.isfinite ปฏิเสธทั้งค่าที่หายไปและ infinity ส่วน np.linalg.cholesky ตรวจว่าระบบที่ต้องใช้เป็น positive definite โค้ดยังหยุดถ้าเมทริกซ์ของ Views singular หรือใกล้ singular มาก เราควรตรวจว่ามี Views ซ้ำหรือข้อมูลเข้าผิดก่อนเปลี่ยนตัวแก้สมการ ผลต่างเล็กน้อยระหว่าง mean_cov กับ transpose จาก roundoff ถูกเฉลี่ยกลับให้สมมาตร

การทำงานได้ของสมการไม่ยืนยันว่า Σ\Sigma เป็นค่าความเสี่ยงจริง เราถือว่า covariance นี้ทราบแล้วเพื่อแยกบทเรียนเรื่องค่าเฉลี่ยออกมา แต่ในการใช้งาน covariance เองก็ต้องประมาณ และมีความคลาดเคลื่อนตาม บท Covariance Estimation

ถ้าเชื่อ Views มากขึ้นหรือน้อยลง

ขนาดของ Ω\Omega วัดความคลาดเคลื่อน ไม่ใช่เปอร์เซ็นต์โอกาสที่ View จะถูกต้อง ถ้าเพิ่ม Ω\Omega โดยคงข้อมูลอื่นไว้ เรากำลังบอกว่า Views มีข้อมูลแม่นยำน้อยลง หากลด Ω\Omega เรากำลังเพิ่มความมั่นใจในข้อมูล Views

ทดลองคูณ Ω\Omega ทั้งเมทริกซ์ด้วย 0, 0.1, 1, 10 และหนึ่งล้าน ค่า 0 หมายถึงถือว่าทั้งสองสมการของ Views แน่นอนในแบบจำลอง ตัวอย่างนี้ใช้ได้เพราะสองแถวของ PP ไม่ซ้ำกันและเมทริกซ์ AvA_v ยังเป็น PD

confidence_rows = []
for multiplier in [0.0, 0.1, 1.0, 10.0, 1_000_000.0]:
    mean, mean_cov, _ = bl_posterior(covariance, pi, P, Q, multiplier * omega, tau)
    confidence_rows.append({"Omega multiplier": multiplier,
                            "C expected excess (%)": mean[2] * 100,
                            "A-B expected spread (%)": (P @ mean)[1] * 100,
                            "C mean uncertainty SD (%)": np.sqrt(max(mean_cov[2, 2], 0)) * 100})
confidence_table = pd.DataFrame(confidence_rows).set_index("Omega multiplier")
print(confidence_table.round(6).to_string())
exact_mean, exact_M, _ = bl_posterior(covariance, pi, P, Q, np.zeros((2, 2)), tau)
assert np.allclose(P @ exact_mean, Q)
assert np.allclose(P @ exact_M @ P.T, 0, atol=1e-12)
print("Uncertainty left outside the two exact views:", round(np.trace(exact_M), 8))
ตัวคูณ Ω ค่าเฉลี่ย C (%) ส่วนต่างค่าเฉลี่ย A − B (จุดเปอร์เซ็นต์)
0 2.500000 2.000000
0.1 2.431298 2.102541
1 2.145762 2.505742
10 1.890899 2.826199
1,000,000 1.837501 2.887499

เมื่อเพิ่มความคลาดเคลื่อนมาก ๆ ผลเข้าใกล้ prior ของ C ที่ 1.8375% และ prior spread ที่ 2.8875 จุดเปอร์เซ็นต์ ส่วนกรณีไม่ให้มีความคลาดเคลื่อน เราได้ PμBL=QP\mu_{BL}=Q และความไม่แน่นอนของสองค่าที่ Views ระบุเป็นศูนย์

แต่ MM ทั้งเมทริกซ์ยังไม่เป็นศูนย์: รู้ค่าเฉลี่ย C และส่วนต่าง A/B ไม่ได้ทำให้รู้ระดับค่าเฉลี่ย A กับ B แยกจากกันทั้งหมด ยังเหลือความไม่แน่นอนในทิศทางที่ Views ไม่ได้ระบุไว้ อีกทั้ง Σ\Sigma ยังมีอยู่ แม้สมมติว่า Views ของค่าเฉลี่ยแน่นอน ผลตอบแทนที่เกิดขึ้นจริงก็ไม่ได้ถูกบังคับให้ตรงกับค่าเฉลี่ยนั้น

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

เมื่อใช้เฉพาะ View ของ C ค่าเฉลี่ย posterior เริ่มที่ 2.5% เมื่อ view error เป็นศูนย์ และเข้าใกล้ prior 1.8375% เมื่อความไม่แน่นอนเพิ่ม
เพื่อแยกผลของความเชื่อมั่น กราฟนี้ใช้เฉพาะ absolute View ของ C และตัด View A ลบ B ออก คง prior และ tau เดิม แกนนอนเป็น SD ของข้อผิดพลาดใน View; ที่ SD ≈ 2.2361 จุดเปอร์เซ็นต์ ค่าเฉลี่ย C เท่ากับ 2.16875% จึงต่างจากกรณีสอง Views ในตารางด้านบน

ทำไมเปลี่ยน τ แล้วค่าเฉลี่ยอาจไม่เปลี่ยน

ถ้าคง Ω\Omega ไว้ แต่เพิ่ม τ\tau เรากำลังเพิ่มความไม่แน่นอนของ prior เมื่อเทียบกับ Views อย่างไรก็ตามกฎสาธิตที่ใช้สร้าง Ω\Omega ข้างต้นก็คูณด้วย τ\tau ด้วย หากสร้าง Ω\Omega ใหม่ตามกฎนี้ทุกครั้ง ความไม่แน่นอนทั้งสองส่วนจะเพิ่มด้วยตัวคูณเดียวกัน

เขียน Ω=τΩ0\Omega=\tau\Omega_0 แล้วแทนลงใน gain จะได้

G=ΣPT(PΣPT+Ω0)−1.G=\Sigma P^\mathsf{T} \left(P\Sigma P^\mathsf{T}+\Omega_0\right)^{-1}.

τ\tau ตัดกันในสูตรค่าเฉลี่ย posterior แต่ MM ยังมีขนาดแปรตาม τ\tau จึงไม่ตัดกันจาก predictive covariance ทดลองทั้งกรณีสร้าง Ω\Omega ใหม่และคง Ω\Omega เดิมไว้:

tau_rows = []
for tau_value in [0.01, 0.05, 0.20]:
    scaled_omega = np.diag(np.diag(P @ (tau_value * covariance) @ P.T))
    scaled_mean, scaled_M, scaled_predictive = bl_posterior(
        covariance, pi, P, Q, scaled_omega, tau_value)
    fixed_mean, _, _ = bl_posterior(covariance, pi, P, Q, omega, tau_value)
    assert np.allclose(scaled_mean, bl_mean)
    tau_rows.append({"Tau": tau_value,
                     "C mean, scaled Omega (%)": scaled_mean[2] * 100,
                     "C mean, fixed Omega (%)": fixed_mean[2] * 100,
                     "C mean uncertainty SD (%)": np.sqrt(scaled_M[2, 2]) * 100,
                     "C predictive return SD (%)": np.sqrt(scaled_predictive[2, 2]) * 100})
print(pd.DataFrame(tau_rows).set_index("Tau").round(4).to_string())

เมื่อสร้าง Ω\Omega ใหม่ตาม τ\tau, ค่าเฉลี่ย C อยู่ที่ประมาณ 2.1458% เท่าเดิมทั้งสามกรณี แต่ SD ของความไม่แน่นอนเกี่ยวกับค่าเฉลี่ย C เปลี่ยนจากประมาณ 0.7043 เป็น 1.5748 และ 3.1496 จุดเปอร์เซ็นต์ตามลำดับ ส่วนกรณีคง Ω\Omega เดิม ค่าเฉลี่ย C เปลี่ยนจากประมาณ 1.9362% ไปเป็น 2.1458% และ 2.3516%

ดังนั้นประโยคว่า “ค่า tau ไม่มีผล” ต้องระบุว่าไม่มีผลต่อค่าเฉลี่ยภายใต้กฎการปรับ Ω\Omega ที่ใช้อยู่เท่านั้น หากนำ predictive covariance ไปหาน้ำหนักพอร์ต ผลของ tau อาจยังปรากฏผ่านความเสี่ยง

Views สองข้ออาจมาจากข้อมูลชุดเดียวกัน

ผู้วิเคราะห์สองคนอาจใช้ประมาณการเศรษฐกิจชุดเดียวกันจนความผิดพลาดสัมพันธ์กัน หรือ Views คนละข้ออาจมาจากโมเดลตัวเดียวกัน การตั้ง Ω\Omega เป็นแนวทแยงในทุกกรณีจะข้ามเรื่องนี้ไป

ตัวอย่างต่อไปคง variance ของความคลาดเคลื่อนแต่ละ View ไว้ แต่สมมติ correlation ของความคลาดเคลื่อนเท่ากับ 0.6 เราสร้างช่องนอกแนวทแยงด้วยสูตรเดียวกับ covariance ทั่วไป คือ correlation คูณ SD ทั้งสองตัว

view_error_correlation = 0.6
view_error_sd = np.sqrt(np.diag(omega))
omega_correlated = np.outer(view_error_sd, view_error_sd) * np.array(
    [[1.0, view_error_correlation], [view_error_correlation, 1.0]])
correlated_mean, correlated_M, correlated_predictive = bl_posterior(
    covariance, pi, P, Q, omega_correlated, tau)
print("Correlated view-error covariance:\n", np.round(omega_correlated, 8))
print(pd.DataFrame({"Independent errors (%)": bl_mean * 100,
                    "Correlated errors (%)": correlated_mean * 100}, index=assets).round(4).to_string())

ได้ covariance ของ view errors นอกแนวทแยงประมาณ 0.00058864 ซึ่งเป็นคนละค่ากับ 0.000175 ใน PτΣPTP\tau\Sigma P^\mathsf{T} ผลค่าเฉลี่ย A/B/C เปลี่ยนเป็นประมาณ 6.0981%, 3.8588% และ 2.2570% ตามลำดับ

ผลนี้ไม่ควรถูกแปลว่า errors สัมพันธ์กันแล้วต้องเชื่อ Views น้อยลงทุกทิศทางเสมอไป ในตัวอย่าง surprise ของ C เป็นบวก แต่ surprise ของ A/B เป็นลบ เมื่อ errors มักเคลื่อนไปทางเดียวกันตามสมมติฐาน ส่วนต่างของสัญญาณจึงมีความไม่แน่นอนต่างจากกรณี errors อิสระ การรวมต้องใช้เมทริกซ์ทั้งหมด

การคัดลอก View เดิมไม่ได้สร้างหลักฐานใหม่

แยกออกมาเฉพาะ View ของ C เพื่อเห็นปัญหานี้ชัดขึ้น หากเพิ่มแถว C=2.5% เดิมอีกหนึ่งแถวแล้วตั้ง errors ให้เป็นอิสระ โปรแกรมจะเข้าใจว่าเราได้ข้อมูลใหม่อีกชิ้น ถ้าเป็นการคัดลอกข้อมูลเดิมจริง ต้องรวมเป็น View เดียว หรือระบุการพึ่งพากันให้ตรงความจริง

one_P, one_Q, one_omega = P[:1], Q[:1], omega[:1, :1]
one_mean, one_M, _ = bl_posterior(covariance, pi, one_P, one_Q, one_omega, tau)
duplicate_P = np.repeat(one_P, 2, axis=0)
duplicate_Q = np.repeat(one_Q, 2)
duplicate_rows = [{"Treatment": "One observation", "C mean (%)": one_mean[2] * 100,
                   "C mean uncertainty SD (%)": np.sqrt(one_M[2, 2]) * 100}]
for label, error_corr in [("Two independent observations", 0.0),
                          ("Nearly identical errors", 0.999)]:
    duplicate_omega = one_omega[0, 0] * np.array([[1.0, error_corr], [error_corr, 1.0]])
    mean, mean_cov, _ = bl_posterior(covariance, pi, duplicate_P, duplicate_Q, duplicate_omega, tau)
    duplicate_rows.append({"Treatment": label, "C mean (%)": mean[2] * 100,
                           "C mean uncertainty SD (%)": np.sqrt(mean_cov[2, 2]) * 100})
print(pd.DataFrame(duplicate_rows).set_index("Treatment").round(6).to_string())
try:
    bl_posterior(covariance, pi, duplicate_P, duplicate_Q,
                 np.full((2, 2), one_omega[0, 0]), tau)
except np.linalg.LinAlgError:
    print("Exactly duplicated information: keep one row instead of solving a singular system")

View ของ C เพียงข้อเดียวให้ค่าเฉลี่ย posterior C เท่ากับ 2.168750% และ SD ความไม่แน่นอนประมาณ 1.581139 จุดเปอร์เซ็นต์ หากป้อนซ้ำแต่บอกว่าเป็นข้อมูลอิสระ ค่าเฉลี่ยจะขยับไปหา 2.5% มากขึ้นเป็น 2.279167% และ SD ลดเหลือ 1.290994 จุดเปอร์เซ็นต์ การเปลี่ยนนี้สมเหตุผลเมื่อเป็นหลักฐานอิสระจริง แต่เป็นการนับข้อมูลซ้ำถ้าใช้รายงานเดิมสองครั้ง

เมื่อ errors มี correlation 0.999 ผลเกือบกลับมาตรงกับ View เดียว ส่วนกรณีสำเนาเหมือนกันทุกประการที่ correlation เป็น 1 ระบบในรูปนี้เป็น singular โค้ดจึงหยุดและแจ้งให้เก็บแถวเดียว try กับ except ในตัวอย่างใช้รับข้อผิดพลาดที่ตั้งใจสาธิต ไม่ได้ทำให้ระบบที่ไม่มีคำตอบเฉพาะตัวกลายเป็นระบบที่ใช้ได้

ค่าเฉลี่ยชุดเดียวกันยังเลือกพอร์ตได้หลายโจทย์

Black–Litterman ให้ค่าประมาณและความไม่แน่นอน ขั้นเลือกพอร์ตต้องระบุอีกว่าถือเงินสดได้หรือไม่ กู้ยืมได้หรือไม่ ขายชอร์ตได้หรือไม่ และใช้ covariance ใดวัดความเสี่ยง เราจะเทียบสองโจทย์โดยคงข้อมูล Views ชุดเดียวกัน

โจทย์แรก: ให้น้ำหนักสินทรัพย์เสี่ยงเปลี่ยนได้ และถือเงินสดส่วนที่เหลือ

ให้ γ>0\gamma>0 เป็นความเกลียดความเสี่ยงของผู้เลือกพอร์ต และ CRC_R เป็น covariance ที่เลือกใช้ ประโยชน์เชิงค่าเฉลี่ย–variance ของพอร์ตเขียนเป็น

U(w)=wTμBL−γ2wTCRw.U(w)=w^\mathsf{T}\mu_{BL}-\frac{\gamma}{2}w^\mathsf{T}C_Rw.

เมื่อไม่มีข้อจำกัดน้ำหนักและ CRC_R เป็น PD คำตอบได้จาก γCRw=μBL\gamma C_Rw=\mu_{BL} จึงใช้ solve อีกครั้ง น้ำหนักเงินสดคือ 1−∑iwi1-\sum_iw_i ค่าติดลบหมายถึงกู้ยืมที่อัตราปลอดความเสี่ยงเพื่อลงทุนเพิ่ม ส่วนสินทรัพย์ที่มีน้ำหนักติดลบหมายถึงขายชอร์ต สมมติฐานนี้ยังไม่รวมส่วนต่างอัตรากู้ยืม ต้นทุน หรือข้อจำกัดหลักประกัน

ในช่วงแรกกำหนด γ=δ=2.5\gamma=\delta=2.5 เพื่อเทียบกับ prior เดิม เราจะแยกสัญลักษณ์ γ\gamma และ δ\delta เพราะการเปลี่ยนความเสี่ยงที่ผู้ลงทุนยอมรับกับการเปลี่ยนค่าที่ใช้สร้าง prior เป็นคนละการตัดสินใจ

ตรวจกรณีไม่มี Views ก่อน

เมื่อไม่มี Views, μBL=Π\mu_{BL}=\Pi และ M=τΣM=\tau\Sigma ถ้าใช้ CR=ΣC_R=\Sigma จะได้น้ำหนัก w0w_0 กลับมา แต่ถ้าใช้ predictive covariance CR=(1+τ)ΣC_R=(1+\tau)\Sigma จะได้

wno views,predictive=w01+τ.w_{\text{no views,predictive}}=\frac{w_0}{1+\tau}.
no_view_mean, no_view_M, no_view_predictive = bl_posterior(
    covariance, pi, np.empty((0, 3)), np.empty(0), np.empty((0, 0)), tau)
no_view_fixed_weights = np.linalg.solve(risk_aversion * covariance, no_view_mean)
no_view_predictive_weights = np.linalg.solve(risk_aversion * no_view_predictive, no_view_mean)
assert np.allclose(no_view_mean, pi)
assert np.allclose(no_view_M, tau * covariance)
assert np.allclose(no_view_fixed_weights, benchmark_weights)
assert np.allclose(no_view_predictive_weights, benchmark_weights / (1 + tau))
print("No views, fixed Sigma risky weights:", np.round(no_view_fixed_weights, 6))
print("No views, predictive risky weights:", np.round(no_view_predictive_weights, 6))
print(f"Predictive cash weight: {1 - no_view_predictive_weights.sum():.4%}")

np.empty((0, 3)) สร้างเมทริกซ์ที่ไม่มีแถว Views แต่ยังมีสามคอลัมน์สินทรัพย์ ไม่ใช่การใส่ View ว่าผลตอบแทนเป็นศูนย์ ฟังก์ชันจัดการกรณีนี้โดยคืน prior และความไม่แน่นอนเดิม

กรณี fixed Σ\Sigma ได้ A/B/C เป็น 50%/30%/20% ส่วน predictive covariance ได้ประมาณ 47.6190%/28.5714%/19.0476% รวมสินทรัพย์เสี่ยง 95.2381% และเงินสด 4.7619% อัตราส่วนระหว่างสินทรัพย์เสี่ยงยังเท่าเดิม แต่จำนวนเงินที่ลงทุนในสินทรัพย์เสี่ยงลดลง เพราะโจทย์นี้คำนึงถึงความไม่แน่นอนของค่าเฉลี่ยด้วย

ใส่ Views แล้วแก้โจทย์เดิม

fixed_sigma_weights = np.linalg.solve(risk_aversion * covariance, bl_mean)
predictive_weights = np.linalg.solve(risk_aversion * bl_predictive, bl_mean)
allocation_table = pd.DataFrame({
    "Fixed Sigma (%)": np.r_[fixed_sigma_weights, 1 - fixed_sigma_weights.sum()] * 100,
    "Predictive Sigma+M (%)": np.r_[predictive_weights, 1 - predictive_weights.sum()] * 100
}, index=assets + ["Cash"])
print(allocation_table.round(4).to_string())
รายการ ใช้ Σ คงเดิม (%) ใช้ predictive Σ + M (%)
A 44.7455 43.1351
B 35.2545 33.0554
C 34.1695 34.4473
เงินสด −14.1695 −10.6378

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

ในโค้ด np.r_[risky_weights, cash_weight] ต่อรายการน้ำหนักเงินสดไว้ท้ายเวกเตอร์เพื่อแสดงตารางเท่านั้น ไม่ได้ทำการปรับน้ำหนักที่แก้ได้

โจทย์ที่สอง: Long-only และลงทุนในสามสินทรัพย์เต็มจำนวน

เพิ่มข้อจำกัด wi≥0w_i\geq0 และ ∑iwi=1\sum_iw_i=1 โดยไม่มีเงินสดเป็นสินทรัพย์ในชุดนี้ เราต้องแก้โจทย์ใหม่:

max⁡w  wTμBL−γ2wTΣpredictivew,wi≥0,∑iwi=1.\max_w\;w^\mathsf{T}\mu_{BL} -\frac{\gamma}{2}w^\mathsf{T}\Sigma_{\text{predictive}}w, \qquad w_i\geq0,\quad\sum_iw_i=1.
def long_only_utility(mean, risk_cov, investor_aversion=2.5):
    mean, risk_cov = np.asarray(mean, dtype=float), np.asarray(risk_cov, dtype=float)
    if (mean.ndim != 1 or len(mean) == 0 or risk_cov.shape != (len(mean), len(mean))
            or not np.isfinite(mean).all() or not np.isfinite(risk_cov).all()
            or not np.isfinite(investor_aversion) or investor_aversion <= 0
            or not np.allclose(risk_cov, risk_cov.T, atol=1e-12)):
        raise ValueError("Need finite means, a symmetric covariance and positive risk aversion")
    np.linalg.cholesky(risk_cov)
    n = len(mean)
    result = minimize(
        lambda w: investor_aversion / 2 * (w @ risk_cov @ w) - w @ mean,
        np.full(n, 1 / n), jac=lambda w: investor_aversion * risk_cov @ w - mean,
        method="SLSQP", bounds=[(0, 1)] * n,
        constraints={"type": "eq", "fun": lambda w: w.sum() - 1,
                     "jac": lambda w: np.ones(n)},
        options={"ftol": 1e-12, "maxiter": 500})
    if (not result.success or abs(result.x.sum() - 1) > 1e-8
            or result.x.min() < -1e-8 or result.x.max() > 1 + 1e-8):
        raise RuntimeError("Optimizer did not return a feasible solution")
    return result.x

long_only_weights = long_only_utility(bl_mean, bl_predictive, risk_aversion)
print("Fully invested, long-only weights (%):", np.round(long_only_weights * 100, 4))
print(f"Cash weight: {1 - long_only_weights.sum():.4%}")
print(f"Model expected excess return: {long_only_weights @ bl_mean:.4%}")
print(f"Predictive SD: {np.sqrt(long_only_weights @ bl_predictive @ long_only_weights):.4%}")

SciPy minimize แบบ SLSQP หาค่าต่ำสุด เราจึงเปลี่ยนเครื่องหมายของ utility แล้วใส่เป็น objective ส่วน jac ให้ความชัน, bounds กำหนดช่วงน้ำหนัก และ constraints บังคับผลรวมหนึ่ง หลังจบยังตรวจสถานะโปรแกรมและความเป็นไปได้ของน้ำหนัก

ได้น้ำหนัก A/B/C ประมาณ 43.2221%/30.5768%/26.2011% ไม่มีเงินสดและไม่มีการกู้ยืม ผลตอบแทนส่วนเกินคาดหวังตามโมเดลประมาณ 4.3831% และ predictive SD ประมาณ 12.8762% ต่อปี ทั้งสองตัวเลขเป็นผลคำนวณจากสมมติฐาน ไม่ใช่ผลตอบแทนหรือความผันผวนที่ยืนยันจากข้อมูลทดสอบ

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

เปลี่ยนความเกลียดความเสี่ยงของใคร

ถ้าเราเชื่อค่าเฉลี่ย posterior และ predictive covariance ชุดเดิม แต่เพิ่ม γ\gamma ของผู้ลงทุนในโจทย์ที่มีเงินสด เขาจะลงทุนในสินทรัพย์เสี่ยงน้อยลง สูตร w=(γCR)−1μBLw=(\gamma C_R)^{-1}\mu_{BL} ทำให้น้ำหนักสินทรัพย์เสี่ยงลดครึ่งหนึ่งเมื่อเพิ่ม γ\gamma เป็นสองเท่า โดยยังไม่เปลี่ยน Π\Pi หรือ Views

aversion_rows = []
for investor_aversion in [1.25, 2.5, 5.0]:
    risky_weights = np.linalg.solve(investor_aversion * bl_predictive, bl_mean)
    aversion_rows.append({"Investor risk aversion": investor_aversion,
                         "Risky allocation (%)": risky_weights.sum() * 100,
                         "Cash (%)": (1 - risky_weights.sum()) * 100,
                         "Model expected excess (%)": risky_weights @ bl_mean * 100})
print(pd.DataFrame(aversion_rows).set_index("Investor risk aversion").round(4).to_string())
new_prior_pi = 5.0 * covariance @ benchmark_weights
new_prior_mean, _, _ = bl_posterior(covariance, new_prior_pi, P, Q, omega, tau)
print("Different prior calibration, posterior excess (%):", np.round(new_prior_mean * 100, 4))

เมื่อ γ\gamma เท่ากับ 1.25, 2.5 และ 5 สัดส่วนสินทรัพย์เสี่ยงรวมประมาณ 221.2756%, 110.6378% และ 55.3189% ตามลำดับ ค่าแรกจึงต้องกู้ยืมมาก และอาจใช้ไม่ได้ภายใต้ข้อจำกัดจริง ความสัมพันธ์แบบหารตาม γ\gamma นี้เป็นของโจทย์ unconstrained ที่มีเงินสด ไม่ใช่คำตอบของโจทย์ fully invested ในส่วนก่อนหน้า

ท้ายโค้ดลองอีกการเปลี่ยนหนึ่ง: สร้าง prior ใหม่ด้วย δ=5\delta=5 แล้วคง Views ไว้ ค่าเฉลี่ย posterior กลายเป็นประมาณ 10.8871%, 7.0881% และ 3.0057% นี่คือการเปลี่ยนข้อมูลความเชื่อตั้งต้นเอง จึงไม่เหมือนการให้ผู้ลงทุนคนใหม่ถือพอร์ตจากค่าประมาณเดิม การทดลองความไวของแบบจำลองควรบอกให้ชัดว่าคงตัวแปรใดและเปลี่ยนตัวแปรใด

ลองตรวจความเข้าใจ

  1. ในตัวอย่างหนึ่งมิติ ถ้าเปลี่ยน SD ของความคลาดเคลื่อน View จาก 1 เป็น 2 จุดเปอร์เซ็นต์ โดย prior ยังเหมือนเดิม ค่าเฉลี่ย posterior และ SD ของความไม่แน่นอนเป็นเท่าไร?
  2. View A ชนะ B อยู่ 2 จุดเปอร์เซ็นต์เป็นการเพิ่มหรือลดส่วนต่างเมื่อเทียบกับ prior ของตัวอย่าง และทราบได้จากตัวเลขใด?
  3. หากความไม่แน่นอนของค่าเฉลี่ยสินทรัพย์หนึ่งมี variance เท่ากับ 0.0004 และ conditional return variance เท่ากับ 0.01, predictive SD เท่ากับ 2% หรือไม่?
  4. เพราะเหตุใดการเพิ่ม tau จึงอาจไม่เปลี่ยนค่าเฉลี่ย posterior แต่เปลี่ยนน้ำหนักที่ใช้ predictive covariance ได้?
  5. ถ้าคัดลอกรายงานประมาณการฉบับเดียวกันมาเป็นสอง Views จะใช้ Ω\Omega แบบแนวทแยงและนับทั้งสองเป็นข้อมูลอิสระได้หรือไม่?
  6. กรณีไม่มี Views, τ=0.10\tau=0.10, γ=δ\gamma=\delta และใช้ predictive covariance โดยถือเงินสดได้ สัดส่วนสินทรัพย์เสี่ยงรวมกับเงินสดเป็นเท่าไร? ถ้าข้อกำหนดเปลี่ยนเป็นลงทุนเต็มจำนวนในสินทรัพย์เสี่ยง ต้องแก้โจทย์เดิมหรือโจทย์ใหม่?
แนวคำตอบ
  1. Prior และ View มี variance เท่ากัน จึงให้น้ำหนักครึ่งต่อครึ่ง ค่าเฉลี่ยเป็น 5% และ variance posterior เท่ากับ 0.022/2=0.00020.02^2/2=0.0002 จึงมี SD ประมาณ 1.4142 จุดเปอร์เซ็นต์
  2. ลดส่วนต่าง เพราะ prior ให้ ΠA−ΠB=6.3%−3.4125%=2.8875\Pi_A-\Pi_B=6.3\%-3.4125\%=2.8875 จุดเปอร์เซ็นต์ จึงมี surprise เท่ากับ −0.8875 จุดเปอร์เซ็นต์ คำว่า “ชนะ” เพียงอย่างเดียวไม่ได้บอกทิศทางการปรับจาก prior
  3. ไม่ใช่ 2% ตัวเลขนั้นเป็น SD ของความไม่แน่นอนเกี่ยวกับค่าเฉลี่ย predictive SD เท่ากับ 0.01+0.0004≈10.1980%\sqrt{0.01+0.0004}\approx10.1980\%
  4. หาก Ω\Omega เพิ่มตาม tau ด้วย ตัวคูณจะตัดกันใน gain และค่าเฉลี่ย แต่ MM ยังเปลี่ยน จึงเปลี่ยน covariance Σ+M\Sigma+M ในโจทย์เลือกพอร์ตได้ ถ้าคง Ω\Omega ไว้ ค่าเฉลี่ยก็อาจเปลี่ยนด้วย
  5. ไม่ได้ถ้าเป็นข้อมูลชิ้นเดียวกันจริง การนับเป็นอิสระทำให้ความมั่นใจเพิ่มโดยไม่มีหลักฐานใหม่ ควรเก็บ View เดียว หรือสร้างโครงสร้างความคลาดเคลื่อนที่สะท้อนข้อมูลร่วมกัน โดยตรวจระบบ singular ด้วย
  6. สินทรัพย์เสี่ยงรวม 1/1.10≈90.9091%1/1.10\approx90.9091\% และเงินสดประมาณ 9.0909% การบังคับให้ลงทุนในสินทรัพย์เสี่ยงเต็มจำนวนเพิ่มข้อจำกัดใหม่ จึงต้องแก้โจทย์ตามข้อจำกัดนั้น ไม่ควรเรียกน้ำหนักที่ normalize แล้วว่าคำตอบ utility unconstrained เดิม

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

อ่าน Transcript ภาษาอังกฤษเต็มของ Black-Litterman Analysis และ Lab Session — Black Litterman เมื่อ 3 ตุลาคม 2026 และตรวจ lab_23.ipynb ผ่าน Coursera Labs and code แบบอ่านอย่างเดียว โดยเฉพาะเซลล์ In[4]–In[6] สำหรับ implied returns, default Omega และ posterior; In[8] กับ In[22]–In[23] สำหรับการปรับสเกลน้ำหนักและฐาน w0/(1+τ)w_0/(1+\tau) ไม่ได้รันหรือแจกจ่าย Notebook ของคอร์ส

สูตร prior และ posterior ตรวจประกอบกับ He และ Litterman, The Intuition Behind Black-Litterman Model Portfolios, December 1999, Appendix B และ Idzorek, A Step-by-Step Guide to the Black-Litterman Model, ฉบับ July 2004, Section 2 ซึ่งเป็นสำเนางานผู้เขียนที่โฮสต์ในเว็บไซต์มหาวิทยาลัย เอกสาร PyPortfolioOpt แสดงสูตร predictive covariance Σ+M\Sigma+M เช่นกัน บทนี้เขียนสูตรและฟังก์ชันด้วย NumPy เอง ไม่ต้องติดตั้ง PyPortfolioOpt

โค้ด Lab เรียกเมทริกซ์ที่คืนว่า sigma_bl และคำนวณเป็น Σ+M\Sigma+M บทนี้จึงแยกชื่อความไม่แน่นอนของค่าเฉลี่ยออกจากความเสี่ยง predictive อย่างชัดเจน ส่วนค่า tau, covariance, Views และความคลาดเคลื่อนทั้งหมดเป็นข้อกำหนดสำหรับสอน ไม่ใช่ค่าที่สอบเทียบกับตลาดหรือความสามารถพยากรณ์จริง การใช้ข้อมูลจริงต้องตรวจหน่วย ช่วงเวลา ลำดับสินทรัพย์ แหล่งข้อมูลร่วมกันของ Views และผลนอกช่วงที่ใช้เลือกพารามิเตอร์

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

ปิด
คำค้นหา

© 2023 by QuantCorner, Thailand.

bottom of page