「勝率65%のストラテジーが、ある月だけ30%に急落する」——この現象の原因は、市場レジーム(相場環境の状態)の変化にある。トレンドフォロー戦略はレンジ相場で機能しないし、レンジ逆張りはトレンド相場で大損する。
固定の閾値でエントリー判定を続けると、相場環境に合わない局面でも同じ頻度で取引してしまう。これを解決するために、ADX(Average Directional Index)とVIX(恐怖指数)を組み合わせた「VIXフェーズマトリクス」を実装し、相場環境に応じてエントリー閾値を動的に変化させる仕組みを構築した。143テスト全PASSで本番稼働中だ。
市場レジームとは何か
市場レジーム(Market Regime)とは、相場がどのような「状態」にあるかを分類したものだ。大きく分けると以下の4つに分類できる。
| レジーム | 特徴 | トレンドフォローの有効性 |
|---|---|---|
| 強トレンド | ADX > 30、方向が明確 | 非常に高い |
| 弱トレンド | ADX 20〜30、方向が曖昧 | 中程度 |
| レンジ | ADX < 20、方向感なし | 低い(逆効果の場合も) |
| 高ボラティリティ | VIX > 30、急激な値動き | 予測困難(リスク過大) |
同じ「RSIが30以下で買い」というシグナルでも、強トレンド中のRSI30は押し目買いのチャンスだが、高ボラティリティ環境のRSI30はさらなる暴落の入口かもしれない。
ADXベースのフェーズ分類
ADX(Average Directional Index)とは
ADXはトレンドの「強さ」を0〜100で数値化する指標だ。方向(上昇か下降か)は示さず、純粋にトレンドの強度だけを測る。
- ADX < 20: トレンドなし(レンジ相場)
- ADX 20〜30: 弱いトレンド
- ADX > 30: 強いトレンド
- ADX > 50: 非常に強いトレンド(稀)
3フェーズ分類
ADXの値に基づいて相場を3フェーズに分類した。
def classify_market_phase(adx: float) -> str:
if adx >= 30:
return "TRENDING" # トレンド相場
elif adx >= 20:
return "TRANSITIONING" # 過渡期
else:
return "RANGING" # レンジ相場
各フェーズに対して min_total_score(エントリーに必要な最低スコア)の乗数を設定する。
PHASE_SCORE_MULTIPLIERS = {
"TRENDING": 1.0, # 通常通り(トレンドフォローが有効)
"TRANSITIONING": 1.3, # 30%引き上げ(慎重に)
"RANGING": 1.8, # 80%引き上げ(非常に強いシグナルのみ)
}
VIXとの組み合わせ:VIXフェーズマトリクス
ADXだけでは「ボラティリティの水準」が分からない。ADXが30(トレンド中)でも、VIXが50(極端な恐怖)の局面ではリスクが全く違う。
VIX(Volatility Index、恐怖指数)は、S&P500オプションの予想変動率から算出される指標で、市場参加者の「恐怖度」を数値化したものだ。
- VIX < 20: 低ボラティリティ(平穏な相場)
- VIX 20〜30: やや高め
- VIX 30〜50: 高ボラティリティ(警戒水準)
- VIX > 50: 極端な恐怖(パニック相場)
マトリクス設計
ADX(3段階)× VIX(4段階)= 12通りの相場状態を定義し、それぞれにスコア乗数を割り当てた。
VIX Low VIX Normal VIX High VIX Extreme
(<20) (20-30) (30-50) (>50)
TRENDING 1.0 ★ 1.0 1.5 999.9
TRANSITION 1.3 1.5 2.0 999.9
RANGING 1.8 2.0 999.9 999.9
- ★ 最も積極的: トレンド中 + 低ボラティリティ → 通常閾値でエントリー
- 999.9: 事実上エントリー不可 → 高ボラ環境やレンジ+高ボラの局面はトレードしない
VIX取得の3層フォールバック
VIX値の取得は外部API依存のため、経済指標フィルター(FX-ADD-D2)と同じ設計思想で3層フォールバックを実装した。
Layer 1: Alpha Vantage API(15分キャッシュ)
↓ 失敗
Layer 2: Yahoo Finance API
↓ 失敗
Layer 3: ATR推定値(ローカルParquetファイルから計算)
VIX取得に全層失敗した場合は「VIX Normal」(20〜30)をデフォルト値として使用し、フィルターの動作を継続する。
シグナル評価フローへの統合
既存のエントリー判定フローへの変更は最小限に抑えた。
変更前:
min_total_score = config["min_total_score"] # 固定値(例: 1.9)
→ should_entry(indicators, min_total_score)
変更後:
base_score = config["min_total_score"] # ベース値(例: 1.9)
phase = classify_market_phase(adx_value) # ADXでフェーズ判定
vix_profile = get_vix_profile(current_vix) # VIXプロファイル取得
multiplier = VIX_PHASE_MATRIX[phase][vix_profile] # マトリクスから乗数
dynamic_min_score = base_score * multiplier # 動的閾値
→ should_entry(indicators, dynamic_min_score)
追加したのは「動的乗数の取得と適用」だけ。UnifiedSignalEvaluator(シグナル評価器)本体のコードは変更していない。
バックテストでの検証
マトリクスのパラメータは勘ではなく、バックテストのグリッドサーチで最適化した。
ADXの閾値(20/25/30)、VIXの閾値区分、各セルの乗数を変化させて、以下の指標を最適化対象とした。
- シャープ比: リスクあたりのリターン(高いほど良い)
- 最大ドローダウン: 最大損失幅(低いほど良い)
- プロフィットファクター: 総利益 ÷ 総損失(1.0以上が必須)
結果
マトリクス適用前後で、EUR_JPY + GBP_JPYの2ペアにおいて:
- トレード数: 15〜20%減少(レンジ相場のノイズトレードが除外された)
- 勝率: 2〜5%向上
- 最大ドローダウン: 10〜15%改善
トレード数は減るが、1トレードあたりの質が上がる。これは意図した挙動だ。
学んだこと
1. 相場環境を無視した固定閾値は時限爆弾
「勝率65%」はあくまで全期間の平均だ。レンジ相場だけを切り出すと30%以下ということもある。固定閾値は「良い環境でも悪い環境でも同じ基準でトレードする」ことを意味し、これは自ら不利な状況に飛び込む行為だ。
2. マトリクスは直感的で保守しやすい
「ADX×VIX」の2軸マトリクスは、12セルの表として一覧できる。機械学習モデルのブラックボックスと違い、「なぜこの局面でエントリーしなかったか」が即座に説明できる。運用中のチューニングも、セルの値を変えるだけで済む。
3. VIX取得のフォールバックは必須
VIXは外部APIでしか取得できない。APIが落ちたら市場レジーム判定ができなくなるのは致命的だ。ローカルデータからのATR推定値を最終手段として持っておくことで、完全なオフライン耐性を確保した。
まとめ
市場レジーム自動識別の設計で重要なのは以下の3点だ。
- ADX×VIXの2軸マトリクス: 12通りの相場状態に対して個別にエントリー閾値を設定。直感的で保守しやすい
- 乗数方式での統合: 既存のシグナル評価器を変更せず、閾値に乗数を掛けるだけで動的制御を実現
- 3層フォールバック: VIX取得の信頼性を確保し、API障害時もフィルターが機能し続ける
「相場環境に合わせてトレード頻度を変える」のは、裁量トレーダーが自然にやっていることだ。それをルールベースでシステムに組み込むことが、市場レジームフィルターの本質だ。