Lab 1 · 報酬率分配 & 常態假說檢驗
報酬率分配與常態假說檢驗
在馬可維茲與 Sharpe 模型中,我們都「假設」報酬率服從常態分配 —— 這個假設對不對? 本 Lab 帶你用 10 年真實資料(2016-2025)親自驗證這個假設: 計算偏態 $G_1$、超額峰態 $G_2$,用 $k\sigma$ 覆蓋率比對常態理論 68/95/99.7%, 畫 Q-Q Plot,跑 D'Agostino's $K^2$ 檢定。每個計算都附完整 Python 程式,可直接複製到 Colab。
對應 CH2 問題集 P1–P3
對應 CH3 問題集 P1–P8
資料期間 2016-01-01 ~ 2025-12-31
開始之前
🕹️ 你要做什麼
- 選 2~6 檔資產(預選 QQQ/SPY/AAPL/KO,也可加入 2330 / 0050 / 00645 做台美比較)。
- 切換日/月頻率,觀察 $G_1$、$G_2$ 在兩種頻率下的差異。
- 挑一檔資產做「常態假說檢驗」:看 $k\sigma$ 表、直方圖、Q-Q、K² 檢定。
- 寫三題反思 → 匯出 Markdown 報告(含 Python 實作碼可貼到 Colab 驗算)。
🧠 要建立的觀念
- 偏態 $G_1$:衡量分布不對稱程度。$G_1 > 0$ 右偏、$G_1 < 0$ 左偏。股票報酬常呈左偏(崩盤比暴漲多)。
- 超額峰態 $G_2$:常態分配下 $G_2 = 0$;$G_2 > 0$ 為高峽峰(leptokurtic),即尾部比常態厚 —— 這是所謂的「厚尾」。
- 日頻報酬幾乎都非常態(厚尾明顯),月頻因中央極限定理較接近常態但仍偏離。
- 這個「常態不成立」的事實是 Lab 2 要學 VaR 三種算法的根本動機。
🛠️ 會學到的方法
- Fisher–Pearson 修正樣本偏態 $G_1$ 與超額峰態 $G_2$ 的公式與
pandas.Series.skew() / kurt()。 - $k\sigma$ 覆蓋率的計算與判讀。
- Q-Q Plot 的繪製(
scipy.stats.probplot)。 - D'Agostino's $K^2$ 常態檢定(
scipy.stats.normaltest),從統計量到 p-value 到結論。
📐 本 Lab 使用的關鍵公式(Fisher–Pearson 修正樣本偏態 / 峰態)
$$G_1 = \frac{n}{(n-1)(n-2)} \sum_{i=1}^{n} \left( \frac{x_i - \bar{x}}{s} \right)^3$$
$$G_2 = \frac{n(n+1)}{(n-1)(n-2)(n-3)} \sum_{i=1}^{n} \left( \frac{x_i - \bar{x}}{s} \right)^4 - \frac{3(n-1)^2}{(n-2)(n-3)}$$
這兩個就是 pandas.Series.skew() 與 pandas.Series.kurt() 的預設公式(Fisher-Pearson 修正版)。
$G_2$ 已經扣掉常態分配的 3,所以常態下 $G_2 = 0$。
1選擇資產與頻率
資料期間固定為 2016-01-01 ~ 2025-12-31(配合 CH2/CH3 問題集作業規格)2016-01-01 → 2025-12-31(日報酬)
報酬頻率:
2報酬率分配統計
對應 CH2 問題集 P1-3、CH3 問題集 P1-4(自動填表)🐍 Python 程式碼(計算分配統計,點擊展開)
import yfinance as yf, pandas as pd
tickers = ["QQQ", "SPY", "AAPL", "KO"] # 可加 "2330.TW" / "0050.TW"
data = yf.download(tickers, start="2016-01-01", end="2025-12-31",
auto_adjust=False)["Adj Close"]
rets = data.pct_change().dropna() # 日報酬
# rets = data.resample("M").last().pct_change().dropna() # 月報酬(CH3 P2/P4 版本)
n = len(rets)
summary = pd.DataFrame({
"算術平均": rets.mean(),
"幾何年化": (1 + rets).prod() ** (252 / n) - 1, # 月報酬改成 12/n
"波動度": rets.std(ddof=1),
"G1 偏態": rets.skew(), # Fisher–Pearson G1
"G2 超額峰態": rets.kurt(), # Fisher–Pearson G2(pandas 預設就是修正後)
})
print(summary.round(4))
3常態假說檢驗
對應 CH3 問題集 P5-8:k-sigma 覆蓋率、Q-Q Plot、D'Agostino $K^2$ 檢定
焦點資產:
3.1 · $k\sigma$ 覆蓋率表
比對實際資料落在 $[\mu - k\sigma, \mu + k\sigma]$ 的比例,對照常態分配理論值 68.27% / 95.45% / 99.73%。差距愈大,代表偏離常態愈嚴重。
🐍 Python 對照碼
r = rets["SPY"] # 換成你的焦點資產
mu, sd = r.mean(), r.std(ddof=1)
for k in [1, 2, 3]:
pct = ((r >= mu - k*sd) & (r <= mu + k*sd)).mean()
theory = [0.6827, 0.9545, 0.9973][k-1]
print(f"{k}σ: 實際 {pct:.4f} vs 常態 {theory}")
3.2 · D'Agostino's $K^2$ 常態檢定
用偏態與峰態構造兩個 Z 統計量,平方和後得到 $K^2 \sim \chi^2_2$。p < 0.05 → 拒絕常態。
🐍 Python 對照碼
from scipy import stats
k2, p = stats.normaltest(r) # D'Agostino K²
print(f"K²={k2:.2f}, p-value={p:.3e}")
print("結論:", "拒絕常態" if p < 0.05 else "無法拒絕常態")
3.3 · 直方圖 + 常態分配疊加
紅線是用你算出的 $\mu, \sigma$ 畫出的理論常態。看看實際分布「中間比常態尖、尾部比常態厚」嗎?
3.4 · Q-Q Plot
樣本點若完全落在紅線上 = 常態。尾部往下偏離(左尾更負、右尾更正)= 厚尾訊號。
🐍 Python Q-Q Plot 對照碼(scipy.stats.probplot)
import matplotlib.pyplot as plt
from scipy import stats
stats.probplot(r, dist="norm", plot=plt)
plt.title("Q-Q Plot · SPY")
plt.grid(True, alpha=0.3)
plt.show()