【初心者向け】仮説検定でバックテスト結果の信頼性を検証する方法

基礎知識・戦略

※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。

「バックテストで勝てた」は本当に意味があるのか?

バックテストで「平均リターン+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行で計算できる
  • 統計的有意性と実用的な有効性は別物。過学習にも注意する
タイトルとURLをコピーしました