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

開始之前

🕹️ 你要做什麼

  1. 選 2~6 檔資產(預選 QQQ/SPY/AAPL/KO,也可加入 2330 / 0050 / 00645 做台美比較)。
  2. 切換日/月頻率,觀察 $G_1$、$G_2$ 在兩種頻率下的差異。
  3. 挑一檔資產做「常態假說檢驗」:看 $k\sigma$ 表、直方圖、Q-Q、K² 檢定。
  4. 寫三題反思 → 匯出 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()

4反思與匯出

答完這三題,你就能回答 CH3 問題集 P5/P6/P7/P8 的「財務意涵」子題。
下一個:Lab 2 · 尾部風險實驗室 →