「バックテストで年利30%出たのに実運用で2週間で損失」——これ、僕の実話です。原因はパラメータの過学習でした。今回はそれを防ぐウォークフォワード分析をPythonで実装します。
過学習とウォークフォワード分析とは
過学習(オーバーフィッティング)とは、過去データに最適化しすぎて未来のデータに対応できない状態。ウォークフォワード分析は、データを時系列に沿って複数の窓に分け「最適化→検証→次の窓へ」を繰り返す手法。
実装コード
import pandas as pd
import numpy as np
import yfinance as yf
from itertools import product
def download_data(ticker="7203.T", start="2019-01-01", end="2024-12-31"):
df = yf.download(ticker, start=start, end=end, auto_adjust=True)
df.columns = [c[0] if isinstance(c, tuple) else c for c in df.columns]
return df
def sma_strategy(df, fast, slow):
"""シンプルな移動平均クロス戦略"""
df = df.copy()
df["sma_fast"] = df["Close"].rolling(fast).mean()
df["sma_slow"] = df["Close"].rolling(slow).mean()
df["signal"] = (df["sma_fast"].shift(1) > df["sma_slow"].shift(1)).astype(int)
df["ret"] = df["Close"].pct_change()
df["strat_ret"] = df["ret"] * df["signal"]
total_ret = (1 + df["strat_ret"].dropna()).prod() - 1
return total_ret
def walk_forward(df, train_months=12, test_months=3,
fast_range=range(5,21,5), slow_range=range(20,61,10)):
"""ウォークフォワード分析"""
results = []
dates = pd.date_range(df.index[0], df.index[-1], freq="MS")
for i in range(0, len(dates) - train_months - test_months, test_months):
train_end = dates[i + train_months]
test_end = dates[i + train_months + test_months]
train = df[dates[i]:train_end]
test = df[train_end:test_end]
# インサンプルで最適化
best_ret, best_params = -np.inf, None
for fast, slow in product(fast_range, slow_range):
if fast >= slow:
continue
ret = sma_strategy(train, fast, slow)
if ret > best_ret:
best_ret, best_params = ret, (fast, slow)
# アウトオブサンプルで検証
if best_params:
oos_ret = sma_strategy(test, *best_params)
results.append({
"期間": f"{dates[i].date()}~{test_end.date()}",
"最適パラメータ": best_params,
"IS_リターン": f"{best_ret:.1%}",
"OOS_リターン": f"{oos_ret:.1%}"
})
return pd.DataFrame(results)
# 実行
df = download_data()
result = walk_forward(df)
print(result.to_string(index=False))
インサンプル(IS)でいい数字が出てもアウトオブサンプル(OOS)が悪ければ過学習。ISとOOSのリターンが近いパラメータが「頑健な戦略」の目安になる。次は日本製造メーカー株でこの分析を走らせてみたいと思っている。

