※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
「バックテストで勝てた」は本当に意味があるのか?
バックテストで「平均リターン+1.5%/日」という結果が出たとします。これは本当に「戦略が機能している証拠」なのでしょうか?実は、ランダムな売買でもたまたま良い結果になることがあります。
このような疑問に答えるのが仮説検定(Hypothesis Testing)です。「この結果は偶然ではない」ことを統計的に確認する手法です。
仮説検定の基本概念
| 用語 | 意味 |
|---|---|
| 帰無仮説(H₀) | 「効果がない」「偶然の結果」という仮説。例:平均リターン = 0 |
| 対立仮説(H₁) | 「効果がある」「偶然ではない」という仮説。例:平均リターン ≠ 0 |
| 有意水準(α) | 「この確率以下なら偶然ではないと判断する」閾値。通常5%(0.05) |
| p値 | 帰無仮説が正しいとして、実際の結果が出る確率。p値 < α なら帰無仮説を棄却 |
| t値 | サンプルの平均が0からどれだけ離れているかを示す統計量 |
Pythonでバックテストのt検定を実装する
import yfinance as yf
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family'] = 'Meiryo'
# ==============================
# 設定エリア
# ==============================
SYMBOL = "7203.T" # 対象銘柄
PERIOD = "2y" # バックテスト期間
SHORT_MA = 5 # 短期移動平均
LONG_MA = 25 # 長期移動平均
ALPHA = 0.05 # 有意水準(5%)
# ==============================
# データ取得と簡単なMA戦略のバックテスト
# ==============================
df = yf.Ticker(SYMBOL).history(period=PERIOD)
close = df["Close"]
# 移動平均クロス戦略のシグナル
sma_short = close.rolling(SHORT_MA).mean()
sma_long = close.rolling(LONG_MA).mean()
# ポジション: 短期>長期なら買い(+1), それ以外は現金(0)
df["position"] = 0
df.loc[sma_short > sma_long, "position"] = 1
# 日次リターン(対数リターン)
df["log_return"] = np.log(close / close.shift(1))
# 戦略リターン: 前日のポジション × 当日のリターン
df["strat_return"] = df["position"].shift(1) * df["log_return"]
# 有効なデータだけ取得
strat_returns = df["strat_return"].dropna()
buy_hold_ret = df["log_return"].dropna()
print(f"=== バックテスト結果 [{SYMBOL}] ===")
print(f"戦略リターン 平均: {strat_returns.mean()*100:.3f}%/日")
print(f"B&H リターン 平均: {buy_hold_ret.mean()*100:.3f}%/日")
print(f"サンプル数: {len(strat_returns)}日")
print()
# ==============================
# 1標本t検定: 平均リターンが0か検定
# ==============================
t_stat, p_value = stats.ttest_1samp(strat_returns, popmean=0)
print(f"=== 仮説検定結果 ===")
print(f"帰無仮説: 平均リターン = 0(戦略に意味がない)")
print(f"t値: {t_stat:.3f}")
print(f"p値: {p_value:.4f}")
print()
if p_value < ALPHA:
print(f"→ p値({p_value:.4f}) < 有意水準({ALPHA})")
print("→ 帰無仮説を棄却: この戦略のリターンは統計的に有意(偶然ではない可能性が高い)")
else:
print(f"→ p値({p_value:.4f}) >= 有意水準({ALPHA})")
print("→ 帰無仮説を棄却できない: リターンが偶然の可能性を否定できない")
# ==============================
# 信頼区間の計算
# ==============================
ci = stats.t.interval(
confidence=1 - ALPHA,
df=len(strat_returns) - 1,
loc=strat_returns.mean(),
scale=stats.sem(strat_returns)
)
print()
print(f"95%信頼区間: [{ci[0]*100:.4f}%, {ci[1]*100:.4f}%]")
p値の読み方
p値が0.05(5%)未満であれば、「5%の有意水準で統計的に有意」と言えます。ただし、p値が低くても実用的な意味があるかは別問題です。取引コストや過学習(過去データへの過剰な最適化)も考慮が必要です。
まとめ
- 仮説検定は「結果が偶然かどうか」を統計的に判断する手法
- 帰無仮説を棄却できれば「偶然ではない可能性が高い」と言える
- p値 < 0.05(有意水準5%)が統計的有意の一般的な基準
- t検定はPythonのscipy.statsで1行で計算できる
- 統計的有意性と実用的な有効性は別物。過学習にも注意する

