戦略をやめる基準の決め方:ドローダウンは「想定内」か「崩壊」か
戦略が負けているとき、私たちは2つの間違いのどちらかを犯します。
- 早すぎる撤退:正常な調整局面で怖くなってやめる → 回復を取り逃す
- 遅すぎる撤退:明らかに壊れているのに「いつか戻る」と持ち続ける → 傷が広がる
この2つを分けるのは根性でも直感でもなく、事前に決めた数値基準です。この記事では、撤退ラインの決め方をPythonで解説します。
⚠️ 本記事は教育目的の情報共有です。投資は自己責任で行ってください。
目次
- 「想定内の負け」を先に知っておく
- モンテカルロで最悪シナリオを計算する
- 撤退トリガーの3類型
- 実装:運用中の戦略を毎月チェックする
- 撤退基準を守るための仕組み
- まとめ
1. 「想定内の負け」を先に知っておく
そもそも、あなたの戦略はどれくらい負けることが正常なのでしょうか。
これを知らずに運用を始めると、正常な範囲の下落でも「壊れた!」と感じます。逆に、異常な下落を「まだ想定内のはず」と正当化してしまうこともあります。
だから運用開始前に、こう決めておきます。
この戦略は、正常に機能していても最大で X% のドローダウンと、Yヶ月 の低迷が起こりうる。それを超えたら「壊れた可能性」を疑う。
XとYを勘で決めないために、モンテカルロを使います。
2. モンテカルロで最悪シナリオを計算する
バックテストの結果は「過去に実際に起きた1本の道筋」にすぎません。同じ戦略でも、リターンの並び順が変わればドローダウンは大きく変わります。
そこで、過去のリターンをシャッフルして「あり得た別の未来」を大量に作ります。
import numpy as np
import pandas as pd
def monte_carlo_drawdown(returns: pd.Series, n_sims: int = 5000,
horizon: int = 252) -> pd.Series:
"""リターンをブートストラップして最大DDの分布を得る
returns: 戦略の日次リターン系列
horizon: シミュレーションする営業日数(252 = 1年)
"""
r = returns.dropna().values
rng = np.random.default_rng(42)
results = []
for _ in range(n_sims):
sample = rng.choice(r, size=horizon, replace=True)
cum = np.cumprod(1 + sample)
peak = np.maximum.accumulate(cum)
dd = ((cum - peak) / peak).min()
results.append(dd * 100)
return pd.Series(results)
# 使い方
dd_dist = monte_carlo_drawdown(strategy_returns)
print(f"中央値 : {dd_dist.median():.1f}%")
print(f"下位5%点 : {dd_dist.quantile(0.05):.1f}%") # 20回に1回はここまで
print(f"下位1%点 : {dd_dist.quantile(0.01):.1f}%") # 100回に1回
出力例:
中央値 : -18.3%
下位5%点 : -34.7%
下位1%点 : -46.2%
この場合、-35%程度のドローダウンは「20年に1回」ではなく「20回のうち1回」起こりうる、正常範囲の出来事です。ここで慌てて撤退するのは早すぎます。
一方、-50%を超えたら「分布の外」=戦略が想定した性質を失った可能性が高い、と判断できます。
💡 ブートストラップは順序をシャッフルするため、トレンドの継続性(自己相関)を壊します。モメンタム系では最悪DDを楽観的に見積もる傾向があるので、出た数字は少し厳しめに解釈するのが安全です。
3. 撤退トリガーの3類型
数値基準は、性質の違う3つを併用すると漏れが減ります。
① 深さ(ドローダウン)
最大DDがモンテカルロの下位1%点を超えたら停止
一撃の大きさを見る基準。分かりやすく、実装も簡単。
② 長さ(低迷期間)
過去高値を N ヶ月以上更新できなければ再検証
「じわじわ死んでいく」タイプの劣化を捉えます。深さより長さの方が、戦略の陳腐化を早く検知できることが多いです。
def months_underwater(equity: pd.Series) -> int:
"""直近の高値からの経過月数"""
peak_date = equity.idxmax()
return (equity.index[-1] - peak_date).days // 30
③ 前提の崩壊(定性)
戦略が依存している前提そのものが変わったか
例:
- 手数料無料化で、コスト前提が変わった
- 対象市場の流動性が構造的に落ちた
- 同じ戦略が広く知られ、優位性が消えた(アルファの減衰)
数値化できませんが、①②が発火したときに必ず点検する項目として持っておきます。
4. 実装:運用中の戦略を毎月チェックする
def health_check(equity: pd.Series, dd_limit: float,
max_underwater_months: int = 18) -> dict:
"""月次で回す健康診断"""
cum = equity / equity.iloc[0]
peak = cum.cummax()
current_dd = ((cum.iloc[-1] - peak.iloc[-1]) / peak.iloc[-1]) * 100
uw = months_underwater(equity)
alerts = []
if current_dd < dd_limit:
alerts.append(f"DD {current_dd:.1f}% が撤退ライン {dd_limit:.1f}% を突破")
if uw >= max_underwater_months:
alerts.append(f"高値未更新 {uw}ヶ月(基準 {max_underwater_months}ヶ月)")
return {
"現在DD(%)": round(current_dd, 1),
"高値未更新(月)": uw,
"判定": "要再検証" if alerts else "継続",
"警告": alerts,
}
重要なのは、この閾値を運用開始前に決めて記録しておくことです。下落の最中に決めた基準は、必ず自分に甘くなります。
5. 撤退基準を守るための仕組み
基準を決めても、守れなければ意味がありません。人間は含み損を抱えると判断が歪みます。
- 紙に書いて日付を入れる:「2026年X月、DD-45%または高値未更新18ヶ月で再検証」と運用開始前に記録
- 判定は月次の決まった日に:毎日眺めると、下落中の判断を毎日迫られることになります
- 「撤退」=「全否定」ではない:ロットを半分にする、レジームフィルタを追加する、ペーパーに戻す、など段階的な選択肢を用意しておく
- 痛みの直後にルールを変えない:損失の直後の変更は、たいてい過剰最適化の入り口です。「改善候補」としてメモし、次の検証区切りで判断する
私自身、運用中の戦略が想定外に沈んだときも、その場でパラメータを触らないことを最優先しています。検証中に手を入れたら、それまでの検証データが無価値になるからです。
6. まとめ
- 「耐えるか撤退か」は根性ではなく事前に決めた数値で判断する
- モンテカルロで「正常な負け方」の分布を先に知る(下位1%点=分布の外)
- トリガーは深さ(DD)・長さ(高値未更新)・前提の崩壊の3類型
- 閾値は運用開始前に決めて記録する(下落中に決めると必ず甘くなる)
- 撤退は全否定ではない。ロット縮小・フィルタ追加・ペーパー復帰という段階がある
戦略を作るときに一番書き忘れるのが「やめる条件」です。最初に決めておけば、下落局面で一番貴重なもの——冷静さ——を守れます。
関連記事 → ウォークフォワード検証とは? / 投資botは「作った後」が本番
質問や感想は X (@QuantNobu) までどうぞ。
⚠️ 免責:本記事は教育目的の情報共有であり、投資助言ではありません。実際の投資判断はご自身の責任で行ってください。