イグジットロジック¶
監視¶
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- 分析エクスポート