1:概要
・トレードは必ず正規分布からのズレから生じる
・実データにおける正規分布からのズレを特定する方法を(GPTに)検討してもらった
・ズレの大きさの定量化から始まり、最終的にはズレているポイントまで特定できた
2:背景
正規分布からのズレがトレードにおけるαとなる。
しかし、そのαが実際にはどの程度存在しているか、まず知らなければ戦略も立てられない
そこで正規分布からのズレ具合を(GPTに)確認してもらった
3:正規分布からのズレを出す(数値情報)
まずは数値として、どれぐらい正規分布からズレいているか把握する
①:統計的検定(正規性検定)
「この分布は正規分布とどれだけ違うか」を調べるには、まず 正規性の検定(正規分布化どうかチェックする) を行うのが一般的です。
✅ 代表的な検定
Shapiro-Wilk検定
Kolmogorov-Smirnov検定
Anderson-Darling検定
Jarque-Bera検定
これらは「正規分布からのズレ」を数値(統計量)として出してくれます。
②:KLダイバージェンス(情報量的なズレ)
ある分布 𝑃 と正規分布 Q の間の距離を測る指標として Kullback-Leibler (KL) ダイバージェンス を使うことができます。これは「PがQからどれだけズレているか」を%ではなく 「情報量の差」 で測る方法です。
③:ヒストグラムの差異から%ズレを評価
分布 P をヒストグラムにして、正規分布のヒストグラムと比べることができます。 各ビンの差を合計 し、以下のように%を出す方法です
※ これは正規化された確率ヒストグラムで行うとよいです。
4:日経平均で試した場合
①:正規性検定
→ このデータは統計的にはほぼ正規分布と言ってよい結果です。
②:KLダイバージェンス
値:25.83
これは「真の分布と正規分布の情報的なズレ」を意味します。値が大きいほどズレが大きいですが、絶対的な解釈は難しいため、他の分布と比較して相対的に使います。
③:ヒストグラム差異による%ズレ
正規分布との絶対差(正規化ヒストグラムで): 約25.83%
つまり、ヒストグラムの各ビンの合計的な差異が全体の約26%存在する、という意味になります。
※:25.83の解釈
あるビンでは、正規分布よりも実データの方が多く
別のビンでは、逆に正規分布の方が多く
…という 偏りや歪みの総合的なズレを全部足し合わせて、それが 理想的な正規分布の確率全体(100%)のうち25.83%に相当する ということです。
意外にズレが大きい
5:正規分布からのズレを可視化する
数値だけだと分からないので、可視化する
①:ヒストグラムの可視化
コード
# ズレを塗るために実データと正規分布の密度差をプロット
plt.figure(figsize=(10, 6))
# 実データのヒストグラム(密度)
hist_p, bins = np.histogram(returns, bins=50, density=True)
bin_centers = (bins[1:] + bins[:-1]) / 2
# 正規分布の密度(同じビン中心で計算)
hist_q = stats.norm.pdf(bin_centers, mu, std)
# プロット
plt.plot(bin_centers, hist_p, drawstyle='steps-mid', label='Actual Returns', color='blue')
plt.plot(bin_centers, hist_q, linestyle='--', label='Fitted Normal', color='red')
# 差の部分を塗る
plt.fill_between(bin_centers, hist_p, hist_q, where=(hist_p > hist_q), color='orange', alpha=0.4, label='Actual > Normal')
plt.fill_between(bin_centers, hist_p, hist_q, where=(hist_p < hist_q), color='green', alpha=0.4, label='Normal > Actual')
plt.title('Distribution Comparison with Deviation Highlighted')
plt.xlabel('Daily Return')
plt.ylabel('Probability Density')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
中央が多くて、周辺で少なくなっているのが何となく分かる
②:累積分布(CDF)での比較、尖度・歪度の可視化やQ-Qプロット
コード
# CDF(累積分布)の可視化
plt.figure(figsize=(15, 4))
# サブプロット1: CDF
plt.subplot(1, 3, 1)
sorted_returns = np.sort(returns)
cdf_empirical = np.arange(1, len(returns)+1) / len(returns)
cdf_normal = stats.norm.cdf(sorted_returns, mu, std)
plt.plot(sorted_returns, cdf_empirical, label='Empirical CDF', color='blue')
plt.plot(sorted_returns, cdf_normal, linestyle='--', label='Normal CDF', color='red')
plt.title('CDF Comparison')
plt.xlabel('Daily Return')
plt.ylabel('Cumulative Probability')
plt.legend()
plt.grid(True)
# サブプロット2: Q-Qプロット
plt.subplot(1, 3, 2)
stats.probplot(returns, dist="norm", plot=plt)
plt.title('Q-Q Plot')
# サブプロット3: 歪度・尖度の可視化(棒グラフ)
plt.subplot(1, 3, 3)
skewness = stats.skew(returns)
kurtosis = stats.kurtosis(returns, fisher=True) # Fisher=True で 0 が正規分布の基準
bars = plt.bar(['Skewness', 'Kurtosis'], [skewness, kurtosis], color=['skyblue', 'lightcoral'])
plt.axhline(0, color='gray', linestyle='--')
plt.title('Skewness and Kurtosis')
plt.grid(True)
plt.tight_layout()
plt.show()
今までの結果とは違って、ほとんど正規分布的に見える(逆に言えば、これでも25%はズレている)
※:累積分布(CDF)での比較、尖度・歪度やQ-Qプロットの解釈
📊【1. CDF(累積分布関数)比較グラフ】
● 何を見ているか?
実際のデータが「ある値以下になる確率」を順に並べた線(青)
正規分布に従った場合の累積確率(赤い破線)
解釈
2本の線が重なっていれば → 正規分布に近い
ズレていれば → 分布の位置や形状が違う
今回の観察
真ん中付近はよく重なっている → 平均付近は正規的
両端(特に左端)でややズレ → 外れ値や裾野に**非正規性(fat tailsや偏り)**がある
📊【2. Q-Qプロット(Quantile-Quantile Plot)】
● 何を見ているか?
実際のデータの分位点(何%目の値)と、正規分布の同じ分位点を比較したプロット
解釈
点が一直線上(45度線)に並べば → 正規分布と同じ形
端が曲がっていれば → 歪みや厚い尾(heavy tails)
今回の観察
中央付近は直線的 → 中央は正規的
両端がやや外れる → 外れ値の頻度が正規分布より多い(厚い尾)
GPT歪度の解釈間違ってない?
どこが一番正規分布からズレているか特定する
①:正規分布からのズレ領域の特定
コード
# 各ビンごとのズレ率を計算(正規分布との絶対差 / 正規分布値)
bin_differences = hist_p_sp - hist_q_sp
bin_relative_diff = (bin_differences / hist_q_sp) * 100 # パーセント化
bin_relative_diff = np.round(bin_relative_diff, 2)
# ズレの大きい順にデータフレーム化
deviation_df = pd.DataFrame({
"Bin Center": bin_centers_sp,
"Actual Density": hist_p_sp,
"Normal Density": hist_q_sp,
"Difference (%)": bin_relative_diff
}).sort_values(by="Difference (%)", key=abs, ascending=False).reset_index(drop=True)
# 上位のズレ(絶対値が大きい)10件を表示
tools.display_dataframe_to_user(name="ズレの大きいビン上位10件", dataframe=deviation_df.head(10))
とりあえず大きそう(小並)
②ズレの大きさを可視化:ズレが大きいビンの棒グラフ(上位10件)
コード
top_deviation = deviation_df.head(10)
plt.figure(figsize=(12, 6))
bars = plt.bar(top_deviation["Bin Center"], top_deviation["Difference (%)"], width=0.0015, color='orange')
plt.axhline(0, color='gray', linestyle='--')
plt.xlabel("Return Bin Center")
plt.ylabel("Difference (%) vs Normal Distribution")
plt.title("Top 10 Deviations Between Actual and Normal Distribution (S&P500)")
plt.grid(True)
# 差が正か負かで色を変える
for bar, diff in zip(bars, top_deviation["Difference (%)"]):
bar.set_color('orange' if diff > 0 else 'green')
plt.tight_layout()
plt.show()
解釈
特に-2〜-3%付近の下落が正規分布より「はるかに頻繁」に発生(最大で4500%以上の差!)
一方で、+2%付近の急騰も正規分布より多め
→ fat tails(厚い尾)を持つ分布であり、正規性を仮定するモデルはリスクを過小評価する可能性がある
まとめ
以上のような流れで正規分布からのズレを特定してきた。
実際にはもっと色々調査する必要はあるだろうが、差し当たっての正規分布からのズレ調査としては十分だろう。