第 07 課:バックテストシステムの罠

バックテストで年間100%リターン、ライブ取引で-50%。これは事故ではなく - 必然です。


完璧な戦略の転落

2019年、あるクオンツ研究者が「完璧な」A株戦略を開発しました。

バックテスト結果:

  • 年間リターン:120%
  • シャープレシオ:3.5
  • 最大ドローダウン:8%
  • 勝率:68%

彼は自信を持って500万元を投資しました。

1ヶ月目:+2%リターン、期待通り 2ヶ月目:-5%リターン、不安になる 3ヶ月目:-8%リターン、疑い始める 6ヶ月目:-35%累積損失

彼は慎重にコードを調べ、3つの致命的なエラーを発見しました:

  1. Look-Ahead Bias:戦略は「翌日の始値」を使用してエントリーを決定していたが、実際には市場オープン前に始値を知ることはできない

  2. 過剰適合:彼は200以上のパラメータ組み合わせをテストし、最良のものを選んだ。しかしそれは単に「履歴データでたまたまパフォーマンスが良い」だけで、本当に効果的ではなかった

  3. 取引コストを無視:バックテストは0.1%スリッページを仮定、ライブは0.5%;バックテストはインパクトコストを考慮せず、大きな注文は頻繁に「食われた」

バックテストは未来を予測するのではなく - 過去を説明します。落とし穴を知らなければ、過去の説明は未来への誤った導きになります。


7.1 Look-Ahead Bias

Look-Ahead Biasとは?

未来の情報を使用してバックテストで意思決定を行うこと。

最も一般的なエラー:

エラー説明
終値を使用してエントリーを決定終値は市場クローズ後にのみわかる
同日データで指標を計算日が終わる前、MACDなどの指標は不確定
未来データをラベルに使用「3日後に上昇」をラベルとして、しかし取引時にはこれを知らない

直感的理解

タイムライン:
9:30 ─────── 10:00 ─────── 11:00 ─────── クローズ
  |            |            |            |
  あなたはここ |            |         <- この情報を知らない
              |            ^
              |         リアルタイムで利用可能
              ^
           リアルタイムで利用可能

一般的なバックテストの間違い:クローズ後の完全なデータを使用し、任意の時点で意思決定を行うふりをする。

典型的なエラーケース

# 間違ったコード(Look-Ahead Bias)
for i in range(len(data)):
    if data['close'][i] > data['open'][i]:  # 今日は陽線でクローズ
        buy_price = data['open'][i]  # <- 間違い!クローズ後にのみ陽線かどうかわかる
        # オープン時には今日が陽線か陰線かわからない

# 正しいコード
for i in range(1, len(data)):  # 2日目から開始
    if data['close'][i-1] > data['open'][i-1]:  # 昨日は陽線でクローズ
        buy_price = data['open'][i]  # 今日のオープンでエントリー

どう避ける?

原則実装
シグナルと実行を分離T日にシグナル、T+1日に実行
過去データのみ使用指標計算時にshift(1)を使用
最悪の実行価格を仮定買いには高値、売りには安値を使用

7.2 データリーケージ

データリーケージとは?

モデルをトレーニングする際、**テストセット情報がトレーニングセットに「リーク」**すること。

時系列でのリーケージ

金融データには時間順序があり、通常のMLのようにランダムに分割できない:

間違った分割:
トレーニングセット:[1月、3月、5月、7月、9月、11月]  <- ランダム選択
テストセット:[2月、4月、6月、8月、10月、12月]  <- ランダム選択

問題:トレーニングには3月データが含まれるが、2月はテスト
      「未来」データを使用して「過去」を予測

正しい分割:

正しい分割:
トレーニングセット:[1月 - 8月]
検証セット:[9月 - 10月]
テストセット:[11月 - 12月]

時間順序を維持!

特徴量エンジニアリングでのリーケージ

リーケージタイプ
未来リターンを特徴量に「5日後のリターン」を入力特徴量として使用
グローバル正規化データセット全体の平均/標準偏差で正規化
特徴量にラベルリターンベースの指標を使用してリターンを予測
# 間違い:グローバル正規化(未来分布情報がリーク)
mean = df['close'].mean()  # 未来データを含む
std = df['close'].std()    # 未来データを含む
df['normalized'] = (df['close'] - mean) / std

# 正しい:ローリングウィンドウ正規化
df['normalized'] = (df['close'] - df['close'].rolling(20).mean()) / df['close'].rolling(20).std()

特徴量重要度でのリーケージ

特徴量が極めて高い重要度を持つ場合(例:50%以上)、リーケージの可能性:

リーケージの兆候:
- 1つの特徴量重要度が50%以上
- モデル精度が90%以上
- トレーニングとテストパフォーマンスが同じように良い

7.3 過剰適合

過剰適合とは?

モデルが実際のパターンを学習するのではなく、履歴データのノイズを「記憶」すること。

過剰適合の兆候

トレーニングセットパフォーマンス:年間80%、Sharpe 3.0
テストセットパフォーマンス:年間5%、Sharpe 0.5

巨大なギャップ = 過剰適合

なぜクオンツは特に過剰適合しやすいのか?

理由説明
パラメータが多すぎる100パラメータ、1000サンプルのみ
複数テスト1000戦略をテスト、いくつかは「効果的」になる
低S/N比市場ノイズは大きく、実際のシグナルは弱い
不安定な分布過去のパターンは未来で保持されない可能性

複数テスト問題

100のランダム戦略(すべて実際には効果なし)をテストするとします:

p &lt; 0.05の確率:5%
100戦略中、5つが「有意」であることが期待される

問題:これら5つの戦略は効果的に見えるが、単に運が良いだけ

Bonferroni補正:n戦略をテストする場合、有意性しきい値は0.05/nであるべき

100戦略をテスト -> しきい値 0.05 / 100 = 0.0005
1000戦略をテスト -> しきい値 0.05 / 1000 = 0.00005

過剰適合の検出

指標過剰適合シグナル
トレーニング/テストギャップトレーニング >> テスト
パラメータ感度小さなパラメータ変更、大きな結果変更
クロス期間安定性年ごとのパフォーマンスが大きく異なる
戦略複雑性より複雑なルール = 過剰適合しやすい

過剰適合を減らす方法?

方法説明
モデルを簡素化よりシンプル = 過剰適合しにくい
より多くのデータより多くのサンプル = ノイズを記憶しにくい
正則化L1/L2は複雑なモデルにペナルティ
早期停止検証パフォーマンスが低下したら停止
アンサンブル方法複数のモデルで投票、個別バイアスを減らす

7.4 取引コストの無視

一般的に無視されるコスト

コストタイプ典型的値(2024-2025)一般的なバックテスト仮定
手数料米国リテール:ゼロ;機関:~$0.003/株;中国A株:~0.02%0%または過小評価
スリッページ0.01-0.5%(流動性により異なる)0%または固定値
市場インパクト0.1-1%+完全に無視
株式借入年間0.5-50%+(利用可能性により異なる)無視
資金調達コスト年間4-5%(現在の金利環境)無視

米国「ゼロ手数料」に関する注意:主要ブローカー(Fidelity、Schwab、Robinhood)はゼロ手数料を提供していますが、SEC手数料(レートは時間とともに変化;売却$1Mあたり数十ドルのオーダー)とPFOF(Payment for Order Flow)からの隠れたコストがまだあります。

スリッページの実際の影響

戦略が年間200回取引、片道0.2%スリッページと仮定:

年間取引コスト = 200 x 0.2% x 2(買い+売り) = 80%

戦略の年間リターンが50%の場合、スリッページ後:
実際のリターン = 50% - 80% = -30%

高頻度戦略がスリッページで殺されるのは標準.

市場インパクトモデリング

大きな注文は注文簿を「食い尽くし」、実行価格を動かす:

平方根ルール推定:

インパクトコスト ~ sigma x sqrt(Q/V)

sigma = 日次ボラティリティ(:2%)
Q = 注文サイズ
V = 平均日次出来高

:
注文サイズ = 日次出来高の1%
インパクトコスト ~ 2% x sqrt(0.01) = 0.2%

正しいコストモデリング

def estimate_trading_cost(
    price: float,
    quantity: float,
    daily_volume: float,
    daily_volatility: float,
    commission_rate: float = 0.0003  # 3 bps
) -> dict:
    """取引コストを推定"""

    # 手数料
    commission = price * quantity * commission_rate

    # スリッページ(成行注文が2-3レベル食うと仮定)
    slippage_rate = 0.001 * (quantity / daily_volume) ** 0.5
    slippage = price * quantity * slippage_rate

    # 市場インパクト
    participation = quantity / daily_volume
    impact_rate = daily_volatility * (participation ** 0.5)
    market_impact = price * quantity * impact_rate

    total = commission + slippage + market_impact

    return {
        'commission': commission,
        'slippage': slippage,
        'market_impact': market_impact,
        'total': total,
        'total_rate': total / (price * quantity)
    }

7.5 正しいバックテスト方法

Walk-Forward検証

核心アイデア:実際の戦略開発プロセスをシミュレート - 過去でトレーニング、「未来」でテスト、ロールフォワード。

ラウンド1:
  トレーニング:[1月-6月]  ->  テスト:[7月]
ラウンド2:
  トレーニング:[2月-7月]  ->  テスト:[8月]
ラウンド3:
  トレーニング:[3月-8月]  ->  テスト:[9月]
...

各ラウンドは「アウトオブサンプル」テスト

利点:

  • 実際の条件をシミュレート
  • 複数のOOSテスト、より信頼できる結果
  • パラメータ安定性を検出できる

Out-of-Sample (OOS)テスト

厳密に開発に参加しないデータを予約:

開発フェーズデータ:[2015-2022]
  |-- トレーニングセット:[2015-2019]
  |-- 検証セット:[2020-2021]
  --> パラメータ調整、モデル選択はすべてここで実行

最終テストデータ:[2022-2023]
  --> 1回だけ使用!
     一度見たら、もはやOOSではない

重要原則:OOSデータは1回だけ使用できる。OOS結果に基づいて戦略を調整すると、それはIS(in-sample)になる。

Monte Carloシミュレーション

ランダム摂動を通じて戦略の堅牢性をテスト:

元のバックテスト:年間30%

Monte Carloシミュレーション(1000回実行):
- 取引順序をランダムにシャッフル
- エントリー時間を+/-1日ランダムに調整
- コストを+/-20%ランダムに調整

結果分布:
  5パーセンタイル:年間8%
  50パーセンタイル:年間25%
  95パーセンタイル:年間45%

-> 実際のパフォーマンスは8%-45%の間の可能性

ほとんどのMonte Carlo結果が損失の場合、戦略は信頼できない.

バックテスト品質ゲート

**これは本書で最も重要なチェックリストの1つです。**すべての項目がライブ取引前に合格する必要があります。印刷して壁に貼ってください。

レイヤー1:データ整合性

#チェック項目合格基準失敗の結果
1.1データカバレッジトレーニング+テスト >= 5年、少なくとも1つの強気/弱気サイクルを含む極端な市場でテストされていない、安定性不明
1.2調整/ロール処理調整価格を使用、先物の連続主契約偽シグナル、リターン膨張
1.3サバイバーシップバイアス上場廃止株データを含む履歴リターンが50%以上膨張
1.4タイムゾーン調整すべてのソースを統一(UTCまたは現地)クロスマーケットシグナルが混乱

レイヤー2:時間整合性

#チェック項目合格基準失敗の結果
2.1Look-Ahead BiasT日にシグナル、T+1日に実行バックテストリターンが2-10倍膨張
2.2データリーケージ厳格な時間的トレーニング/テスト分離過剰適合が検出不可能
2.3特徴量計算すべての特徴量がshift(1)以前を使用未来情報を使用
2.4ラベル定義ラベルは現在時刻以前のデータのみ使用ラベルリーケージ

レイヤー3:過剰適合検出

#チェック項目合格基準失敗の結果
3.1OOSパフォーマンスOOSリターン > トレーニングリターンの50%深刻な過剰適合
3.2パラメータ安定性+/-20%パラメータ変更、リターン変更 < 30%パラメータ敏感、再現性なし
3.3複数テストn戦略をテスト、p値しきい値 = 0.05 / n偽陽性戦略
3.4クロス期間安定性各年Sharpe > 0.5、大きな変動なし特定期間でのみ機能

レイヤー4:コストモデリング

#チェック項目合格基準失敗の結果
4.1手数料実際のレートを含む(米国リテール:ゼロ、機関:~$0.003/株;中国:~0.02%)コスト過小評価
4.2スリッページ保守的仮定(0.1-0.3%推奨)HFT戦略が損失
4.3市場インパクト大口注文は平方根モデルを考慮容量膨張
4.4資金調達コストマージンショートは借入手数料を考慮ショート戦略リターン膨張

レイヤー5:検証方法

#チェック項目合格基準失敗の結果
5.1Walk-Forward少なくとも10ローリング検証単一検証は信頼できない
5.2Monte Carloシミュレーションの90% > 0運の要素が多すぎる
5.3ストレステスト2008、2020、2022危機でテスト済み危機時に破綻
5.4リターン減衰バックテストリターン x 0.5でも許容可能ライブ期待値が高すぎる

使用方法:

  1. すべての項目が合格する必要がある;いずれかの失敗はライブ取引なしを意味
  2. 合格項目をチェック;失敗の具体的問題を記録
  3. 修正後、チェックプロセス全体を再実行

クイック自己チェック(各バックテスト後に自問):

  • 「未来情報を使用したか?」 -> レイヤー2をチェック
  • 「異なる期間で機能するか?」 -> レイヤー3をチェック
  • 「コスト後も利益があるか?」 -> レイヤー4をチェック
  • 「運の要素はあるか?」 -> レイヤー5をチェック

7.6 一般的な誤解

誤解1:高いバックテストリターンは良い戦略を意味する

最も危険な仮定。高いバックテストリターンは過剰適合、Look-Ahead Bias、コスト過小評価から来る可能性があります。本当に重要なのはOOSパフォーマンスとパラメータ安定性です。

誤解2:100戦略をテストし、最良のものを選ぶ

古典的な複数テスト問題。100のランダム戦略をテストすると、5つが「有意」(p < 0.05)になることが期待されるが、それらは単に運が良いだけ。正しいアプローチ:p値しきい値 = 0.05 / 100 = 0.0005。

誤解3:終値実行でバックテストするのは合理的

合理的ではありません。終値はクローズ後にのみわかります。実際の取引では、クローズ前に注文を出し、実行は終値 +/- スリッページになる可能性があります。正しい:T日にシグナル、T+1日に実行。

誤解4:良いペーパー取引はライブの準備ができている

十分ではありません。ペーパー取引は通常理想的なスリッページ、市場インパクトなし、100%約定を持ちます。ライブには段階的展開が必要:ペーパー取引 -> 1-5%資本ライブ -> 徐々に増加。


7.7 バックテストからライブ取引へ

業界コンセンサス:バックテストからライブ取引への平均パフォーマンス劣化は**30-50%**です。これは悲観論ではなく - 無数のライブ展開によって検証された業界の貴重な経験です。

なぜライブは常にバックテストより悪いのか?

要因バックテストライブリターン影響
実行価格クローズまたは仮定実際(通常悪い)-5~20%
スリッページ固定仮定(0.1%)市場により変動(0.2-0.5%)-10~30%
市場インパクト完全に無視大口注文は大幅にコスト増加-5~50%
約定率100%仮定部分約定の可能性-5~15%
レイテンシ無視50-500ms-2~10%
失敗存在しないネットワークダウン、APIエラー予測不可能
心理存在しない恐怖と欲望予測不可能
モデル過剰適合見えない露呈-20~80%

パフォーマンス劣化の業界データ

複数のクオンツ機関からのライブトラッキング統計に基づく:

Backtest to Live Performance Degradation Statistics

重要な洞察:戦略が驚異的なバックテストリターン(年間>100%)を示す場合、ライブ取引でほぼ確実に大幅に割引されます。

期待リターン減衰式

保守的推定方法(推奨):

期待ライブリターン = バックテストリターン x 0.5 - 隠れたコスト

隠れたコストには以下が含まれます:
- データレイテンシ:-2~5%
- 実行差:-3~10%
- モデル減衰:-5~15%

シナリオベース推定:

戦略タイプバックテスト年間楽観的期待保守的期待減衰係数
低頻度バリュー30%20%12%0.4-0.65
中頻度モメンタム50%30%18%0.35-0.6
HFTマーケットメイキング100%40%20%0.2-0.4
MLファクター80%35%15%0.2-0.45

核心原則:バックテストリターンを半分にしても許容できる場合、ライブ取引を検討.

劣化原因の内訳

Performance Degradation Breakdown from Backtest to Live

段階的展開

段階資本規模目標
ペーパー取引$0システム安定性を検証
マイクロライブ総資本の1-5%実行品質を検証
小規模ライブ10-20%ライブデータを蓄積
通常運用計画規模継続監視

7.8 マルチAgent視点

バックテストシステムでのAgent分割

Backtest Agent Flow

なぜ独立した検証Agentが必要?

  • 戦略開発者はバイアスがある:常に戦略が機能することを証明したい
  • 自動検出がより信頼できる:チェック項目を見逃さない
  • 標準化プロセス:すべての戦略が同じレビューを受けることを保証

コード実装(オプション)

Walk-Forward検証フレームワーク

import pandas as pd
import numpy as np
from typing import Callable, List, Dict

def walk_forward_validation(
    data: pd.DataFrame,
    strategy_fn: Callable,
    train_window: int = 252,  # ~1年
    test_window: int = 63,    # ~3ヶ月
    step: int = 21            # 毎月ロール
) -> List[Dict]:
    """
    Walk-Forward検証

    パラメータ:
    - data:価格データを含むDataFrame
    - strategy_fn:戦略関数、トレーニングデータを受け取り、モデル/パラメータを返す
    - train_window:トレーニングウィンドウサイズ(日)
    - test_window:テストウィンドウサイズ(日)
    - step:ロールステップサイズ(日)

    戻り値:
    - 各ラウンドの結果のリスト
    """
    results = []
    total_len = len(data)

    for start in range(0, total_len - train_window - test_window, step):
        train_end = start + train_window
        test_end = train_end + test_window

        train_data = data.iloc[start:train_end]
        test_data = data.iloc[train_end:test_end]

        # トレーニングセットでトレーニング/最適化
        model = strategy_fn(train_data)

        # テストセットで評価
        test_returns = evaluate_strategy(model, test_data)

        results.append({
            'train_start': train_data.index[0],
            'train_end': train_data.index[-1],
            'test_start': test_data.index[0],
            'test_end': test_data.index[-1],
            'test_return': test_returns.sum(),
            'test_sharpe': calculate_sharpe(test_returns)
        })

    return results


def detect_look_ahead_bias(backtest_fn: Callable, data: pd.DataFrame) -> bool:
    """
    Look-Ahead Biasを検出

    原理:未来データを使用している場合、取引時間がシグナル時間と一致しない
    """
    trades = backtest_fn(data)

    for trade in trades:
        signal_time = trade['signal_time']
        execution_time = trade['execution_time']

        # シグナル時間 < 実行時間であるべき
        if signal_time >= execution_time:
            print("Possible Look-Ahead Bias: signal", signal_time, ">= execution", execution_time)
            return True

    return False


def monte_carlo_backtest(
    base_results: pd.Series,
    n_simulations: int = 1000,
    return_perturbation: float = 0.1
) -> Dict:
    """
    Monte Carloシミュレーション

    パラメータ:
    - base_results:元のバックテスト日次リターン
    - n_simulations:シミュレーション回数
    - return_perturbation:リターン摂動の大きさ

    戻り値:
    - シミュレーション結果統計
    """
    simulated_returns = []

    for _ in range(n_simulations):
        # 順序をシャッフル + ノイズを追加
        shuffled = base_results.sample(frac=1, replace=False)
        noisy = shuffled * (1 + np.random.uniform(-return_perturbation, return_perturbation, len(shuffled)))
        total_return = (1 + noisy).prod() - 1
        simulated_returns.append(total_return)

    simulated_returns = np.array(simulated_returns)

    return {
        'mean': simulated_returns.mean(),
        'std': simulated_returns.std(),
        'percentile_5': np.percentile(simulated_returns, 5),
        'percentile_50': np.percentile(simulated_returns, 50),
        'percentile_95': np.percentile(simulated_returns, 95),
        'prob_positive': (simulated_returns > 0).mean()
    }

レッスン成果物

このレッスンを完了すると、以下が得られます:

  1. バックテスト落とし穴の深い理解 - なぜバックテストは利益を上げるがライブ取引で損失するかを知る
  2. バイアス検出能力 - Look-Ahead Biasとデータリーケージを識別できる
  3. 正しいバックテスト方法論 - Walk-Forward、OOS、Monte Carlo
  4. ライブ期待管理 - バックテストからライブへのリターン減衰を理解
  5. バックテスト品質ゲートチェックリスト - 再利用可能な20項目標準

検証チェックリスト

チェックポイント基準自己テスト方法
Look-Ahead検出コードでlook-ahead biasを識別できるコードスニペットを与えられ、未来データが使用されている場所を指摘
データ分割トレーニング/検証/テストを正しく分割できる5年データを与えられ、時間分割図を描く
過剰適合識別3つの過剰適合シグナルを述べることができるノートなしで検出指標をリスト
品質ゲート20チェックの核心基準を暗唱できるゼロからチェックリストを埋める

診断演習:

ある戦略がこれらのバックテスト結果を持つ - 可能な問題を診断:

  • トレーニング年間リターン:85%
  • テスト年間リターン:12%
  • +/-10%パラメータ変更で50-200%リターン変更
  • 150戦略バリアントをテスト
  • 2018-2022データのみ
クリックして診断を表示

問題診断:

  1. 深刻な過剰適合 - トレーニング85% vs テスト12%、7倍ギャップ、50%しきい値をはるかに超える
  2. パラメータ敏感 - +/-10%変更で50-200%リターン変更、安定性要件に失敗
  3. 複数テスト問題 - 150バリアント、p値しきい値は0.05 / 150 = 0.00033であるべき
  4. データ不足 - 4年は2008、2020危機をカバーせず、安定性不明

結論:この戦略はライブできません。必要:

  • モデルを簡素化してパラメータを減らす
  • より長いデータを取得(少なくとも10年)
  • Bonferroni補正を適用して戦略をフィルタ
  • Walk-ForwardとMonte Carlo検証を実施

重要ポイント

  • Look-Ahead Biasの本質と検出方法を理解
  • データリーケージを避けるための正しい時系列データ分割をマスター
  • 過剰適合の危険と複数テスト問題を認識
  • 取引コストを正しくモデル化する方法を学ぶ
  • Walk-Forward、OOS、Monte Carlo検証方法をマスター

拡張読書


Part 2まとめ(レッスン07まで)

この時点で、クオンツの2つの最大の「サイレントキラー」を解剖しました:データとバックテスト。

レッスン07まで、あなたは学びました:

レッスン核心要点
レッスン 02市場構造、取引コスト、戦略ライフサイクル
レッスン 03時系列、リターン、リスク測定、ファットテール
レッスン 04テクニカル指標は特徴量エンジニアリング、売買シグナルではない
レッスン 05トレンドフォロー vs 平均回帰、戦略選択は市場状態に依存
レッスン 06データエンジニアリングの課題:API、タイムゾーン、品質、バイアス
レッスン 07バックテスト落とし穴と正しい検証方法

次のレッスンプレビュー

レッスン 08: Beta、ヘッジと市場中立性

戦略が利益を上げたが、どこから来たのか?それはあなたのAlpha(真の超過リターン)か、それとも単に市場に乗っただけ(Beta)か?次のレッスンでリスクの源を深く掘り下げ、リターンを分解する方法、ヘッジする方法、そしてなぜリテール投資家は真の市場中立ができないかを学びます。

この章を引用する
Zhang, Wayland (2026). 第 07 課:バックテストシステムの落とし穴. In AIクオンツ取引:ゼロからイチへ. https://waylandz.com/quant-book-ja/Lesson-07-Backtest-System-Pitfalls
@incollection{zhang2026quant_Lesson_07_Backtest_System_Pitfalls,
  author = {Zhang, Wayland},
  title = {第 07 課:バックテストシステムの落とし穴},
  booktitle = {AIクオンツ取引:ゼロからイチへ},
  year = {2026},
  url = {https://waylandz.com/quant-book-ja/Lesson-07-Backtest-System-Pitfalls}
}