コンテンツにスキップ

イグジットロジック

監視

PositionMonitor

class PositionMonitor:
    """ポジション監視"""

    async def monitor_loop(self):
        """監視ループ"""
        while True:
            for position in self.open_positions:
                # 1. 価格更新
                await self._update_price(position)

                # 2. イグジット判定
                exit_signal = self._check_exit_conditions(position)

                # 3. イグジット実行
                if exit_signal:
                    await self._execute_exit(position, exit_signal)

            await asyncio.sleep(60)  # 1分間隔

イグジット条件

4条件

graph TD
    A[ポジション監視] --> B{1. 利確?}
    B -->|Yes| E[決済: PROFIT_TARGET]
    B -->|No| C{2. 損切り?}
    C -->|Yes| E[決済: STOP_LOSS]
    C -->|No| D{3. 時間経過?}
    D -->|Yes| E[決済: TIME_EXIT]
    D -->|No| F{4. GEX変化?}
    F -->|Yes| E[決済: GEX_CHANGE]
    F -->|No| G[継続保有]

条件詳細

条件 Spear Shield
利確 +50% +60%
損切り -35% -50%
損切り判定方法 エントリー価格ベース 最大損失ベース
時間 10日 30日
GEX変化 レジーム反転 レジーム反転

損切り判定方法の違い

  • sunacchan_spear: エントリー価格の35%増加で損切り(例: $0.96 → $1.296)
  • beat_shield: 最大損失の50%で損切り(例: 最大損失$500 → $250)

利確判定

def check_profit_target(position: Position) -> bool:
    """利確条件をチェック"""
    # クレジットスプレッドの場合
    # 価値が下がる = 利益
    current_value = position.current_price
    entry_value = position.entry_price

    # 利益率計算
    profit_pct = (entry_value - current_value) / entry_value * 100

    return profit_pct >= position.strategy.profit_target

損切り判定

AEGISでは、戦略によって2つの損切り判定方法を使用しています。

方法1: エントリー価格ベース(sunacchan_spear戦略)

Credit spreadの場合、エントリー時の受取クレジット価格に対する増加率で損切りを判定します。

def check_stop_loss_entry_based(position: Position) -> bool:
    """エントリー価格ベースの損切り判定(sunacchan_spear戦略)"""
    entry_credit = position.entry_price  # エントリー時の受取クレジット
    current_spread_price = position.current_price  # 現在のスプレッド価格

    # 損切り価格 = エントリー価格 × (1 + 損切り率)
    # 例: エントリー$0.96、損切り率35%の場合、損切り価格 = $0.96 × 1.35 = $1.296
    stop_loss_price = entry_credit * (1 + position.strategy.stop_loss_pct)

    # Credit spreadの場合、価格が上がる = 損失
    # 現在価格 >= 損切り価格 → 損切り条件に達している
    return current_spread_price >= stop_loss_price

例(sunacchan_spear戦略、損切り率35%): - エントリー価格: $0.96 - 損切り価格: $0.96 × 1.35 = $1.296 - 現在価格: $1.40 - 判定: $1.40 >= $1.296 → 損切り発動

方法2: 最大損失ベース(beat_shield戦略)

理論上の最大損失(スプレッド幅 - エントリークレジット)に対する割合で損切りを判定します。

def check_stop_loss_max_loss_based(position: Position) -> bool:
    """最大損失ベースの損切り判定(beat_shield戦略)"""
    # 実際のPnLを計算
    actual_pnl = position.unrealized_pnl  # 負の値

    # 損切りライン = 最大損失 × 損切り率 × 数量 × 100
    # 例: 最大損失$500、損切り率50%、数量2枚の場合
    #     損切りライン = -$500 × 0.50 × 2 × 100 = -$50,000(実際は1組あたりで計算)
    stop_loss_line = -position.max_loss * position.strategy.stop_loss_pct * position.quantity * 100

    # 実際のPnLが損切りライン以下の場合、損切り
    return actual_pnl <= stop_loss_line

例(beat_shield戦略、損切り率50%): - 最大損失(1組あたり): \(500 - 数量: 2枚 - 損切りライン(全数量): -\)500 × 0.50 × 2 × 100 = -\(50,000 - 実際のPnL: -\)45,000 - 判定: -\(45,000 <= -\)50,000 → 損切り未達

戦略別の損切り判定方法

戦略 判定方法 説明
sunacchan_spear エントリー価格ベース エントリー価格の35%増加で損切り
beat_shield 最大損失ベース 最大損失の50%で損切り

実装上の注意

OptionSpread.should_close_for_loss()メソッドは、use_entry_credit_basedパラメータで判定方法を切り替えます。 - use_entry_credit_based=True: エントリー価格ベース - use_entry_credit_based=False: 最大損失ベース(デフォルト)


時間イグジット

def check_time_exit(position: Position) -> bool:
    """時間イグジット条件をチェック"""
    days_held = (date.today() - position.entry_date).days
    return days_held >= position.strategy.max_hold_days

GEX変化イグジット

def check_gex_change(position: Position) -> bool:
    """GEX変化条件をチェック"""
    current_regime = self.gex_engine.get_regime(position.symbol)
    entry_regime = position.entry_regime

    # レジーム反転をチェック
    if entry_regime == "POS_GAMMA_SAFE" and current_regime == "NEG_GAMMA_RISKY":
        return True
    if entry_regime == "NEG_GAMMA_RISKY" and current_regime == "POS_GAMMA_SAFE":
        return True

    return False

決済実行

async def execute_exit(position: Position, reason: str):
    """決済を実行"""

    # 1. 決済注文を作成
    close_order = ComboOrder(
        legs=[
            OrderLeg(position.sell_option, "BUY", position.quantity),
            OrderLeg(position.buy_option, "SELL", position.quantity),
        ],
        order_type="MKT",  # 市場注文
        tif="DAY"
    )

    # 2. 注文発注
    result = await ibkr.place_order(close_order)

    # 3. トレード記録
    trade_store.record_exit(
        position_id=position.id,
        exit_price=result.fill_price,
        exit_reason=reason,
        pnl=calculate_pnl(position, result.fill_price)
    )

    # 4. 通知
    await notify_exit(position, reason)

PnL計算

def calculate_pnl(position: Position, exit_price: float) -> float:
    """PnLを計算"""
    # クレジットスプレッドの場合
    entry_credit = position.entry_price * 100 * position.quantity
    exit_debit = exit_price * 100 * position.quantity

    pnl = entry_credit - exit_debit

    return pnl

イグジット追跡(v2.5)

FilterTracker統合

v2.5から、イグジット理由とPnLを自動追跡します。

from core.filter_tracker import FilterTracker, ExitReasonType

tracker = FilterTracker.instance()

# イグジット結果を記録
tracker.record_exit(
    trade_id="abc123",
    symbol="NVDA",
    target_date=date.today(),
    reason=ExitReasonType.PROFIT_TAKE.value,
    pnl=450.0,
    pnl_pct=30.0,
)

追跡されるイグジット理由

理由 説明 期待PnL
PROFIT_TAKE 利確(目標達成) +$$
STOP_LOSS 損切り(閾値到達) -$$
TIME_DECAY 最大保有日数経過 ±$
EXPIRY 満期接近(DTE閾値) ±$
GEX_CHANGE GEXレジーム変化 ±$$
MOMENTUM_REVERSAL モメンタム反転(Spear専用) ±$$

イグジット統計

# サマリーからイグジット分布を取得
stats = tracker.get_summary()

# 出力例
{
    "exit_distribution": [
        {"name": "PROFIT_TAKE", "count": 10, "total_pnl": 4500.0, "avg_pnl": 450.0},
        {"name": "STOP_LOSS", "count": 5, "total_pnl": -1500.0, "avg_pnl": -300.0},
        {"name": "TIME_DECAY", "count": 3, "total_pnl": 200.0, "avg_pnl": 66.7},
        ...
    ]
}

取引分析エクスポート

特定の取引について詳細分析用データをエクスポートできます。

# 特定の取引をエクスポート
python scripts/export_trade_analysis.py --trade-ids abc123,def456

# 大勝ち/大負け取引を自動抽出
python scripts/export_trade_analysis.py --auto-select --threshold 500

実装ファイル

  • scripts/run_paper_trading.py - 監視ループ
  • core/execution.py - 決済実行
  • core/trade_store.py - トレード記録
  • core/filter_tracker.py - イグジット追跡
  • scripts/export_trade_analysis.py - 分析エクスポート