コンテンツにスキップ

エントリーロジック

スキャン

DynamicScanner

class DynamicScanner:
    """動的銘柄スキャナー"""

    def scan(self) -> list[HotSymbol]:
        """ホットシンボルをスキャン"""
        hot_symbols = []

        for symbol in self.universe:
            # 1. UWフロー確認
            flows = self.uw_client.get_flows(symbol)

            # 2. GEXプロファイル取得
            gex = self.gex_engine.calculate(symbol)

            # 3. IV Rank取得
            iv_rank = self.iv_provider.get_iv_rank(symbol)

            # 4. ホット判定
            if self._is_hot(flows, gex, iv_rank):
                hot_symbols.append(HotSymbol(symbol, flows, gex, iv_rank))

        return hot_symbols

フィルタ条件

5条件チェック

graph TD
    A[候補銘柄] --> B{1. UW Sweep?}
    B -->|No| X[スキップ]
    B -->|Yes| C{2. GEXマッチ?}
    C -->|No| X
    C -->|Yes| D{3. IV Rank範囲内?}
    D -->|No| X
    D -->|Yes| E{4. モメンタム?}
    E -->|No| X
    E -->|Yes| F{5. リスク枠?}
    F -->|No| X
    F -->|Yes| G[エントリー候補]

条件詳細

条件 Spear Shield
UW Sweep 必須 不要
GEXマッチ 必須 必須
IV Rank 30-70 50+
モメンタム 必須 反転シグナル
リスク枠 15%以下 8%以下

エントリー

ストライク選定

def select_strikes(
    chain: OptionChain,
    direction: str,
    spread_width: float
) -> tuple[float, float]:
    """ストライクを選定"""
    atm = chain.get_atm_strike()

    if direction == "CALL":
        # Call Credit Spread
        sell_strike = atm + 1  # ATM+1
        buy_strike = sell_strike + spread_width
    else:
        # Put Credit Spread
        sell_strike = atm - 1  # ATM-1
        buy_strike = sell_strike - spread_width

    return sell_strike, buy_strike

DTE選定

def select_expiration(
    chain: OptionChain,
    target_dte_min: int,
    target_dte_max: int
) -> date:
    """満期日を選定"""
    expirations = chain.get_expirations()

    for exp in expirations:
        dte = (exp - date.today()).days
        if target_dte_min <= dte <= target_dte_max:
            return exp

    return None

数量計算

def calculate_quantity(
    capital: float,
    risk_percent: float,
    spread_width: float
) -> int:
    """数量を計算"""
    risk_amount = capital * risk_percent
    max_loss_per_contract = spread_width * 100
    return int(risk_amount / max_loss_per_contract)

注文発注

Limit Order

def place_entry_order(
    symbol: str,
    sell_strike: float,
    buy_strike: float,
    expiration: date,
    option_type: str,
    quantity: int
) -> Order:
    """エントリー注文を発注"""

    # クレジット計算
    sell_option = get_option(symbol, sell_strike, expiration, option_type)
    buy_option = get_option(symbol, buy_strike, expiration, option_type)

    credit = sell_option.bid - buy_option.ask
    limit_price = credit * 0.95  # 5%マージン

    # スプレッド注文
    order = ComboOrder(
        legs=[
            OrderLeg(sell_option, "SELL", quantity),
            OrderLeg(buy_option, "BUY", quantity),
        ],
        order_type="LMT",
        limit_price=limit_price,
        tif="DAY"
    )

    return ibkr.place_order(order)

フィルタ追跡(v2.5)

FilterTracker統合

v2.5から、各フィルタ段階での通過/振り落とし状況を自動追跡します。

from core.filter_tracker import FilterTracker, FilterStageType

tracker = FilterTracker.instance()

# 各段階での判定を記録
tracker.record_filter_result(
    symbol="NVDA",
    target_date=date.today(),
    stage=FilterStageType.MOMENTUM.value,
    passed=False,
    reason="score=0.35 < 0.50",
    trade_id="abc123",
    snapshot=market_snapshot,
)

追跡される段階

段階 説明 振り落とし例
OPTION_CHAIN チェーン利用可能 no_option_chain
UW_SWEEP Sweep検知 no_sweep_detected
GEX_MATCH レジームマッチ not_gex_valley
IV_RANK IV範囲内 iv_rank=0.25<0.30
MOMENTUM モメンタム条件 score=0.35<0.50
RISK_BUDGET リスク枠 quantity=0
SPREAD_BUILD 構築成功 spread_build_failed

サマリー取得

# ダッシュボード用サマリー
stats = tracker.get_summary()

# 出力例
{
    "entry_funnel": [
        {"name": "OPTION_CHAIN", "passed": 1000, "rejected": 50, "pass_rate": 95.2},
        {"name": "MOMENTUM", "passed": 200, "rejected": 800, "pass_rate": 20.0},
        ...
    ],
    "exit_distribution": [...],
    "total_scanned": 1050,
    "total_entered": 15,
}

実装ファイル

  • core/scanner.py - DynamicScanner
  • core/execution.py - 注文発注
  • core/filter_tracker.py - フィルタ追跡
  • strategies/sunacchan_spear.py - Spearエントリー
  • strategies/beat_shield.py - Shieldエントリー