ウォークフォワード分析とは?過学習を防ぐ検証手法

基礎知識・戦略

「バックテストでは素晴らしい成績なのに実際の取引では損失続き」——この現象の多くは過学習(オーバーフィッティング)が原因です。ウォークフォワード分析はこの問題を防ぐための強力な検証手法です。

過学習とは?

過学習とは、戦略のパラメーターを特定の過去データに最適化しすぎて、新しいデータには対応できなくなる現象です。

例えば「トヨタ株の2010〜2020年データでRSI=65、移動平均=47日が最良」と最適化しても、2021年以降の相場には当てはまらない可能性が高いです。

ウォークフォワード分析の基本概念

ウォークフォワード分析は、データを時系列順に複数のウィンドウに分割し、最適化(In-Sample)→検証(Out-of-Sample)を繰り返す手法です。

期間:  2015  2016  2017  2018  2019  2020  2021  2022
      [────────────In-Sample────────────][Out-of-Sample]  ← ウィンドウ1
            [────────────In-Sample────────────][OOS]      ← ウィンドウ2
                  [────────────In-Sample────────────][OOS]← ウィンドウ3

OOSの結果を組み合わせて最終評価

Pythonでウォークフォワード分析を実装する

import yfinance as yf
import pandas as pd
import numpy as np
from itertools import product

def ma_cross_strategy(df, short, long_):
    """移動平均クロス戦略のリターンを計算"""
    df = df.copy()
    df['MA_s'] = df['Close'].rolling(short).mean()
    df['MA_l'] = df['Close'].rolling(long_).mean()
    df['Signal'] = np.where(df['MA_s'] > df['MA_l'], 1, 0).shift(1)
    df['Return'] = df['Close'].pct_change() * df['Signal']
    return df['Return'].dropna()

def optimize_params(df, short_range, long_range):
    """In-Sampleでパラメーターを最適化"""
    best_sharpe = -999
    best_params = None
    
    for short, long_ in product(short_range, long_range):
        if short >= long_:
            continue
        returns = ma_cross_strategy(df, short, long_)
        if len(returns) < 30:
            continue
        sharpe = returns.mean() / returns.std() * np.sqrt(252) if returns.std() != 0 else 0
        if sharpe > best_sharpe:
            best_sharpe = sharpe
            best_params = (short, long_)
    
    return best_params, best_sharpe

def walk_forward_analysis(df, is_window=504, oos_window=126, step=63):
    """
    ウォークフォワード分析
    
    Args:
        df: 価格データ
        is_window: In-Sampleウィンドウのサイズ(日数)
        oos_window: Out-of-Sampleウィンドウのサイズ(日数)
        step: ステップサイズ(日数)
    """
    results = []
    short_range = range(5, 50, 5)
    long_range = range(20, 200, 10)
    
    start = 0
    while start + is_window + oos_window <= len(df):
        # データを分割
        is_data = df.iloc[start:start + is_window]
        oos_data = df.iloc[start + is_window:start + is_window + oos_window]
        
        # In-Sampleで最適化
        best_params, is_sharpe = optimize_params(is_data, short_range, long_range)
        
        if best_params is None:
            start += step
            continue
        
        # Out-of-Sampleで検証
        oos_returns = ma_cross_strategy(oos_data, best_params[0], best_params[1])
        oos_sharpe = oos_returns.mean() / oos_returns.std() * np.sqrt(252) if oos_returns.std() != 0 else 0
        oos_return = (1 + oos_returns).prod() - 1
        
        results.append({
            'IS_Start': is_data.index[0].date(),
            'IS_End': is_data.index[-1].date(),
            'OOS_Start': oos_data.index[0].date(),
            'OOS_End': oos_data.index[-1].date(),
            'Best_Params': best_params,
            'IS_Sharpe': round(is_sharpe, 2),
            'OOS_Sharpe': round(oos_sharpe, 2),
            'OOS_Return': f"{oos_return:.1%}"
        })
        
        start += step
    
    return pd.DataFrame(results)

# 実行
df = yf.download("7203.T", period="10y", progress=False)
wf_results = walk_forward_analysis(df)

print("=== ウォークフォワード分析結果 ===")
print(wf_results.to_string())

結果の解釈

# 総合パフォーマンス評価
print(f"\n=== 総合評価 ===")
print(f"検証ウィンドウ数: {len(wf_results)}")
print(f"OOS勝率(シャープ>0): {(wf_results['OOS_Sharpe'] > 0).mean():.1%}")
print(f"IS平均シャープレシオ: {wf_results['IS_Sharpe'].mean():.2f}")
print(f"OOS平均シャープレシオ: {wf_results['OOS_Sharpe'].mean():.2f}")

# 効率係数(OOS/IS比率):1に近いほど過学習が少ない
efficiency = wf_results['OOS_Sharpe'].mean() / wf_results['IS_Sharpe'].mean()
print(f"効率係数(OOS/IS): {efficiency:.2f}")
print(f"判定: {'良好(過学習少)' if efficiency > 0.5 else '要注意(過学習の可能性)'}")

アンカーウィンドウ vs ローリングウィンドウ

方式 概要 特徴
アンカーウィンドウ In-Sampleの開始点は固定し、終点を移動 データが増えるほど安定。市場変化への適応が遅い
ローリングウィンドウ In-Sampleの開始点・終点を同じ幅で移動 最新の市場環境に適応。古いデータを切り捨てる

ウォークフォワード分析のポイント

  • OOS/IS比率(効率係数)が0.5以上あれば良好
  • 全OOSウィンドウの60%以上でプラスリターンが理想
  • パラメーターが毎回大きく変わる場合は戦略が不安定
  • OOSのシャープレシオがISより大幅に低い場合は過学習の可能性

まとめ

ウォークフォワード分析は単純なバックテストより手間がかかりますが、過学習を検出できる非常に重要な検証手法です。本番投入前に必ず実施することをオススメします。

タイトルとURLをコピーしました