コンテンツにスキップ

RiskGuardian(コツコツドカン防止)

概要

RiskGuardian は、「コツコツ稼いでドカンと負ける」パターンを防ぐための多層防御機構です。

問題の背景

オプション売り戦略は勝率が高い一方、一度の大負けで複数回の勝ちを帳消しにするリスクがある。 Geminiとの議論で「口座の50%を失わない」ための具体的な防御策を設計。


3層防御アーキテクチャ

graph TD
    A[Level 1: ポジション管理] -->|エントリー前| B{can_enter_position?}
    B -->|No| C[エントリー拒否]
    B -->|Yes| D[ポジションオープン]

    D --> E[Level 2: リスク監視]
    E -->|保有中| F{should_exit_position?}
    F -->|Yes| G[通常イグジット]
    F -->|No| H[継続保有]

    H --> I[Level 3: 緊急対応]
    I -->|危機時| J{is_emergency_exit_required?}
    J -->|Yes| K[全ポジション強制決済]
    J -->|No| E

Level 1: ポジション管理(エントリー前)

can_enter_position() チェック項目

def can_enter_position(
    self,
    symbol: str,
    strategy: str,
    risk_amount: float
) -> tuple[bool, str]:
    """新規ポジションを建てられるかチェック"""

    # 1. VIXチェック
    if self.current_vix > self.config.vix_limit:
        return False, f"VIX過高 ({self.current_vix:.1f} > {self.config.vix_limit})"

    # 2. アロケーションチェック
    if self.current_allocation_pct > self.config.max_allocation_pct:
        return False, f"アロケーション上限 ({self.current_allocation_pct:.1f}%)"

    # 3. セクター集中チェック
    sector = self._get_sector(symbol)
    if self.sector_count[sector] >= self.config.max_per_sector:
        return False, f"セクター集中 ({sector}: {self.sector_count[sector]})"

    # 4. 決算フィルター
    if self._has_earnings_before_expiry(symbol):
        return False, "決算リスク"

    # 5. 相関チェック
    if self._is_highly_correlated(symbol):
        return False, f"高相関 (SPY相関 > {self.config.max_correlation})"

    return True, "OK"

設定パラメータ($50,000口座向け)

パラメータ 根拠
Max Allocation 60% ($30,000) 40%は常に現金で保持
VIX Limit 30 VIX 30超えで新規停止
Max per Sector 4 同一セクター最大4ポジション
Max Correlation 0.7 SPYとの相関制限

Level 2: リスク監視(保有中)

should_exit_position() チェック項目

def should_exit_position(
    self,
    position: OptionSpread,
    current_pnl_pct: float
) -> tuple[bool, str]:
    """ポジションを決済すべきかチェック"""

    # 1. VIX急騰チェック
    if self.current_vix > self.config.vix_limit:
        return True, "VIX急騰"

    # 2. 日次ドローダウンチェック
    if self.daily_drawdown_pct > self.config.daily_dd_limit:
        return True, "日次DD超過"

    # 3. ガンマリスクチェック(DTE < 3)
    if position.dte < self.config.gamma_filter_dte:
        return True, "ガンマリスク"

    # 4. 損切りチェック(プレミアム倍率ベース)
    if current_pnl_pct < -self.config.stop_loss_pct:
        return True, "損切り"

    # 5. 利確チェック
    if current_pnl_pct > self.config.profit_take_pct:
        return True, "利確"

    return False, ""

設定パラメータ

パラメータ 根拠
Daily DD Limit 3% ($390) 日次ドローダウン制限
Gamma Filter DTE < 3 残存3日で強制決済
Stop Loss 200% of Credit プレミアム倍率ベース
Profit Taking 50% of Max Profit 勝ち逃げ

Level 3: 緊急対応(危機時)

is_emergency_exit_required() トリガー

def is_emergency_exit_required(self) -> tuple[bool, str]:
    """全ポジション強制決済が必要かチェック"""

    # 1. VIX 30超え
    if self.current_vix > 30:
        return True, f"VIX緊急 ({self.current_vix:.1f})"

    # 2. 日次DD 3%超え
    if self.daily_drawdown_pct > 3.0:
        return True, f"日次DD緊急 ({self.daily_drawdown_pct:.1f}%)"

    # 3. 週次DD 6%超え
    if self.weekly_drawdown_pct > 6.0:
        return True, f"週次DD緊急 ({self.weekly_drawdown_pct:.1f}%)"

    return False, ""

80:20 バーベル戦略

Gemini推奨の資金配分戦略:

口座 $50,000
├── Beat Shield (80% = $40,000)
│   ├── 役割: 防御・生活費
│   ├── 戦略: Put Credit Spread / Iron Condor
│   ├── リスク: 2% per trade ($1,000)
│   └── 最大ポジション: 8
└── Sunacchan Spear (20% = $10,000)
    ├── 役割: 攻撃・ボーナス
    ├── 戦略: Option Buying (Directional)
    ├── リスク: 1% per trade ($500)
    └── 最大ポジション: 4

戦略別リスク設定

戦略 配分 1トレードリスク 最大ポジション
Beat Shield 80% 2% ($1,000) 8
Sunacchan Spear 20% 1% ($500) 4

「50%を失わない」シミュレーション

最悪シナリオ

初期口座: $50,000
最大アロケーション: 60% = $30,000

最悪ケース: 全ポジション同時最大損失
損失額: $30,000
残高: $50,000 - $30,000 = $20,000
残存率: 40%

リスク許容度

アグレッシブな設定では最大60%の損失が発生しうる。 ただし、12ポジション(Shield 8 + Spear 4)が同時に最大損失となる確率は極めて低い。

リカバリー計算

損失率 残高 復帰に必要なリターン
10% $45,000 11.1%
20% $40,000 25.0%
30% $35,000 42.9%
40% $30,000 66.7%
50% $25,000 100.0%

実装詳細

core/risk_guardian.py

@dataclass
class RiskGuardianConfig:
    """RiskGuardian設定"""
    # 口座設定
    account_capital: float = 50000.0

    # アロケーション
    max_allocation_pct: float = 60.0
    beat_allocation_pct: float = 80.0
    spear_allocation_pct: float = 20.0

    # リスク設定
    beat_risk_per_trade_pct: float = 2.0
    spear_risk_per_trade_pct: float = 1.0

    # ポジション上限
    beat_max_positions: int = 8
    spear_max_positions: int = 4
    max_per_sector: int = 4

    # 監視閾値
    vix_limit: float = 30.0
    daily_dd_limit_pct: float = 3.0
    weekly_dd_limit_pct: float = 6.0
    gamma_filter_dte: int = 3
    max_correlation: float = 0.7

    # 損益設定
    stop_loss_pct: float = 200.0  # プレミアムの200%
    profit_take_pct: float = 50.0  # 最大利益の50%

class RiskGuardian:
    """多層防御リスク管理エンジン"""

    def __init__(self, config: RiskGuardianConfig = None):
        self.config = config or RiskGuardianConfig()
        self._positions: List[OptionSpread] = []
        self._daily_pnl: float = 0.0
        self._weekly_pnl: float = 0.0

    # Level 1
    def can_enter_position(self, ...) -> tuple[bool, str]: ...

    # Level 2
    def should_exit_position(self, ...) -> tuple[bool, str]: ...

    # Level 3
    def is_emergency_exit_required(self) -> tuple[bool, str]: ...

    # ヘルパー
    def get_available_risk_budget(self, strategy: str) -> float: ...
    def update_daily_pnl(self, pnl: float): ...
    def reset_daily_stats(self): ...

ペーパートレードでの使用

# run_paper_trading.py

class PaperTradingEngine:
    def __init__(self):
        self.risk_guardian = RiskGuardian(RiskGuardianConfig(
            account_capital=13000.0  # 仮想資本
        ))

    async def _try_entry(self, symbol: str, strategy: str):
        # RiskGuardianでエントリー可否をチェック
        can_enter, reason = self.risk_guardian.can_enter_position(
            symbol=symbol,
            strategy=strategy,
            risk_amount=self._calculate_risk(...)
        )

        if not can_enter:
            logger.info(f"[{symbol}] エントリー拒否: {reason}")
            return

        # エントリー実行
        await self._execute_entry(...)

    async def _check_exits(self):
        # 緊急決済チェック
        is_emergency, reason = self.risk_guardian.is_emergency_exit_required()
        if is_emergency:
            logger.warning(f"⚠️ 緊急決済発動: {reason}")
            await self._close_all_positions()
            return

        # 通常イグジットチェック
        for position in self.positions:
            should_exit, reason = self.risk_guardian.should_exit_position(
                position=position,
                current_pnl_pct=position.unrealized_pnl_pct
            )
            if should_exit:
                await self._execute_exit(position, reason)

バックテストでの使用

# バックテストサマリーに追加される統計
{
    "risk_guardian_stats": {
        "entries_blocked": 45,
        "block_reasons": {
            "VIX過高": 12,
            "アロケーション上限": 18,
            "セクター集中": 8,
            "決算リスク": 5,
            "高相関": 2
        },
        "emergency_exits": 2,
        "max_daily_dd": 2.8,
        "max_weekly_dd": 4.5
    }
}

ログ出力例

[RiskGuardian] Level 1 チェック:
  VIX: 22.5 ✅
  アロケーション: 32% ✅
  セクター(Tech): 1/2 ✅
  決算: なし ✅
  相関: 0.45 ✅
  結果: エントリー許可

[RiskGuardian] Level 2 チェック:
  VIX: 28.5 ✅
  日次DD: 1.2% ✅
  DTE: 8日 ✅
  PnL: +35% → 利確推奨

[RiskGuardian] ⚠️ Level 3 緊急発動:
  理由: VIX 32.5 > 30
  アクション: 全ポジション強制決済

関連ファイル

ファイル 役割
core/risk_guardian.py RiskGuardian本体
core/risk.py 基本リスク計算
scripts/run_paper_trading.py ペーパートレード統合
scripts/run_beat_sunacchan_backtest.py バックテスト統合

更新履歴

日付 変更内容
2025-11-30 $50,000口座向けに設定更新(ポジション数増加: Shield 4→8, Spear 2→4)
2025-11-30 Max Allocation を 40% → 60% に変更
2025-11-30 セクター上限を 2 → 4 に変更
2025-11-29 Gemini推奨パラメータに基づき実装
2025-11-29 80:20バーベル戦略を設定に反映
2025-11-29 ペーパートレード・バックテストに統合