Skip to content
Published on

エンジニアのためのオプション取引:グリークス、戦略、リスク管理をPythonで実装する

Authors
  • Name
    Twitter
Options Trading for Engineers

はじめに

オプションは最も強力な金融商品の一つですが、多くのエンジニアはその複雑さを理由に敬遠しがちです。しかし実際には、オプション価格決定は根本的に数学の問題であり、エンジニアこそが理解し解決するのに最も適した立場にあります。

本記事ではエンジニアファーストのアプローチを取ります。PythonでBlack-Scholesプライシングモデルをゼロから構築し、グリークスを計算・可視化し、一般的な戦略を実装し、体系的なリスク管理フレームワークを開発します。曖昧な説明は一切なく、数学、コード、データのみで解説します。

前提知識: 確率分布の基礎知識、NumPy/SciPyを使ったPython、株式市場の基本的な理解。

オプションの基礎

オプションとは何か

オプションとは、保有者に特定の価格(行使価格)で、特定の日付(満期日)までに原資産を売買する権利を与えるが、義務は課さない契約です。

  • コールオプション: 行使価格で買う権利。原資産の価格が行使価格を上回ると利益が発生します。
  • プットオプション: 行使価格で売る権利。原資産の価格が行使価格を下回ると利益が発生します。

買い手はプレミアムを前払いします。売り手(ライター)はプレミアムを受け取り、義務を負います。

本源的価値と時間的価値

import numpy as np

def option_intrinsic_value(spot: float, strike: float, option_type: str) -> float:
    """オプションの本源的価値を計算する。"""
    if option_type == 'call':
        return max(spot - strike, 0)
    elif option_type == 'put':
        return max(strike - spot, 0)
    else:
        raise ValueError("option_typeは'call'または'put'である必要があります")

def option_time_value(premium: float, spot: float, strike: float, option_type: str) -> float:
    """時間的価値 = プレミアム - 本源的価値"""
    return premium - option_intrinsic_value(spot, strike, option_type)

# 例: AAPLコールオプション
spot_price = 185.0
strike_price = 180.0
call_premium = 8.50

intrinsic = option_intrinsic_value(spot_price, strike_price, 'call')
time_val = option_time_value(call_premium, spot_price, strike_price, 'call')

print(f"本源的価値: ${intrinsic:.2f}")  # $5.00
print(f"時間的価値: ${time_val:.2f}")    # $3.50
print(f"プレミアム合計: ${call_premium:.2f}")  # $8.50

オプションのマネーネス(インザマネー状態)

| 状態                  | コール(S vs K| プット(S vs K| 説明                             |
|----------------------|-------------------|------------------|----------------------------------|
| イン・ザ・マネー(ITM| SKより上       | SKより下        | 本源的価値がある                    |
| アット・ザ・マネー(ATM| SKと等しい       | SKと等しい      | 行使価格が現在価格と同じ             |
| アウト・オブ・ザ・マネー  | SKより下        | SKより上        | 本源的価値なし、時間的価値のみ        |
| ディープITM            | SKよりはるかに上  | SKよりはるかに下 | 原資産と同様の動き                  |
| ディープOTM            | SKよりはるかに下  | SKよりはるかに上 | 行使される確率が非常に低い           |

Black-Scholesモデル

Black-Scholes-Merton(BSM)モデルは、1973年にフィッシャー・ブラック、マイロン・ショールズ、ロバート・マートンによって発表され、ヨーロピアンスタイルのオプション価格決定に閉形式解を提供します。現代のオプション価格理論の基礎であり続けています。

前提条件

  1. 原資産は一定のボラティリティを持つ幾何ブラウン運動に従う
  2. オプション期間中に配当なし(拡張可能)
  3. リスクフリーレートは一定
  4. 取引コストや税金なし
  5. ヨーロピアンスタイルの行使(満期時のみ)
  6. 市場は摩擦なく連続的

数式

ヨーロピアンコールオプションのBlack-Scholes価格は以下の通りです:

C = S _ N(d1) - K _ e^(-rT) * N(d2)

ヨーロピアンプットオプションの場合:

P = K _ e^(-rT) _ N(-d2) - S * N(-d1)

ここで:

  • d1 = (ln(S/K) + (r + sigma^2/2) _ T) / (sigma _ sqrt(T))
  • d2 = d1 - sigma * sqrt(T)
  • S = 現在の株価
  • K = 行使価格
  • T = 満期までの時間(年単位)
  • r = リスクフリーレート
  • sigma = 原資産のボラティリティ
  • N(x) = 標準正規累積分布関数

Pythonによる実装

import numpy as np
from scipy.stats import norm

class BlackScholes:
    """グリークス計算を含むBlack-Scholesオプション価格モデル。"""

    def __init__(self, S: float, K: float, T: float, r: float, sigma: float):
        """
        パラメータ:
        -----------
        S : float - 現在の株価
        K : float - 行使価格
        T : float - 満期までの時間(年単位)
        r : float - リスクフリーレート(年率)
        sigma : float - ボラティリティ(年率)
        """
        self.S = S
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma
        self._compute_d1_d2()

    def _compute_d1_d2(self):
        """d1とd2パラメータを計算する。"""
        self.d1 = (
            (np.log(self.S / self.K) + (self.r + 0.5 * self.sigma ** 2) * self.T)
            / (self.sigma * np.sqrt(self.T))
        )
        self.d2 = self.d1 - self.sigma * np.sqrt(self.T)

    def call_price(self) -> float:
        """Black-Scholesコールオプション価格を計算する。"""
        return (
            self.S * norm.cdf(self.d1)
            - self.K * np.exp(-self.r * self.T) * norm.cdf(self.d2)
        )

    def put_price(self) -> float:
        """Black-Scholesプットオプション価格を計算する。"""
        return (
            self.K * np.exp(-self.r * self.T) * norm.cdf(-self.d2)
            - self.S * norm.cdf(-self.d1)
        )

    # --- グリークス ---

    def delta(self, option_type: str = 'call') -> float:
        """デルタ: オプション価格の原資産価格に対する変化率。"""
        if option_type == 'call':
            return norm.cdf(self.d1)
        return norm.cdf(self.d1) - 1

    def gamma(self) -> float:
        """ガンマ: デルタの原資産価格に対する変化率。
        コールとプットで同じ値。"""
        return norm.pdf(self.d1) / (self.S * self.sigma * np.sqrt(self.T))

    def theta(self, option_type: str = 'call') -> float:
        """セータ: オプション価格の時間に対する変化率(1日あたり)。"""
        common = -(self.S * norm.pdf(self.d1) * self.sigma) / (2 * np.sqrt(self.T))
        if option_type == 'call':
            theta_annual = common - self.r * self.K * np.exp(-self.r * self.T) * norm.cdf(self.d2)
        else:
            theta_annual = common + self.r * self.K * np.exp(-self.r * self.T) * norm.cdf(-self.d2)
        return theta_annual / 365  # 1暦日あたり

    def vega(self) -> float:
        """ベガ: オプション価格のボラティリティに対する変化率。
        コールとプットで同じ値。ボラティリティ1%変動あたり。"""
        return self.S * norm.pdf(self.d1) * np.sqrt(self.T) / 100

    def rho(self, option_type: str = 'call') -> float:
        """ロー: オプション価格の金利に対する変化率。
        金利1%変動あたり。"""
        if option_type == 'call':
            return self.K * self.T * np.exp(-self.r * self.T) * norm.cdf(self.d2) / 100
        return -self.K * self.T * np.exp(-self.r * self.T) * norm.cdf(-self.d2) / 100

    def summary(self, option_type: str = 'call') -> dict:
        """価格とグリークスの完全なサマリーを返す。"""
        price = self.call_price() if option_type == 'call' else self.put_price()
        return {
            'type': option_type,
            'price': round(price, 4),
            'delta': round(self.delta(option_type), 4),
            'gamma': round(self.gamma(), 4),
            'theta': round(self.theta(option_type), 4),
            'vega': round(self.vega(), 4),
            'rho': round(self.rho(option_type), 4),
        }


# --- 使用例 ---
bs = BlackScholes(S=100, K=100, T=30/365, r=0.05, sigma=0.20)

print("=== ATMコールオプション (S=100, K=100, 30日満期, ボラティリティ20%) ===")
for key, val in bs.summary('call').items():
    print(f"  {key:>8}: {val}")

print("\n=== ATMプットオプション ===")
for key, val in bs.summary('put').items():
    print(f"  {key:>8}: {val}")

# プット・コール・パリティの検証: C - P = S - K*e^(-rT)
call_p = bs.call_price()
put_p = bs.put_price()
parity_lhs = call_p - put_p
parity_rhs = bs.S - bs.K * np.exp(-bs.r * bs.T)
print(f"\nプット・コール・パリティ検証: {parity_lhs:.6f} == {parity_rhs:.6f} -> {np.isclose(parity_lhs, parity_rhs)}")

期待される出力:

=== ATMコールオプション (S=100, K=100, 30日満期, ボラティリティ20%) ===
      type: call
     price: 2.5265
     delta: 0.5282
     gamma: 0.0695
     theta: -0.0343
      vega: 0.1143
       rho: 0.0399

=== ATMプットオプション ===
      type: put
     price: 2.1172
     delta: -0.4718
     gamma: 0.0695
     theta: -0.0207
      vega: 0.1143
       rho: -0.0420

プット・コール・パリティ検証: 0.409336 == 0.409336 -> True

グリークスの理解

グリークスはオプション価格の様々な要因に対する感応度を測定します。オプション価格関数の偏微分と考えてください。

グリークス一覧表

| グリーク | 記号 | 測定対象                    | コール範囲     | プット範囲      | 重要な知見                            |
|---------|------|---------------------------|---------------|----------------|--------------------------------------|
| デルタ   | D    | 原資産価格に対する価格感応度   | 0 から +1     | -1 から 0      | ITMで終了する確率の近似値              |
| ガンマ   | G    | デルタの変化率               | 常にプラス      | 常にプラス      | 満期近くのATMで最大                   |
| セータ   | Th   | 1日あたりの時間価値の減少     | 通常マイナス    | 通常マイナス     | 満期が近づくと加速する                 |
| ベガ    | V    | ボラティリティに対する感応度   | 常にプラス      | 常にプラス      | ATMの長期オプションで最大              |
| ロー    | Rho  | 金利に対する感応度           | プラス         | マイナス        | 通常最も小さいグリーク                 |

行使価格ごとのグリークスの可視化

import numpy as np
from scipy.stats import norm

def bs_greeks_surface(S, K_range, T, r, sigma):
    """行使価格の範囲にわたってグリークスを計算する。"""
    results = {'K': [], 'call_delta': [], 'put_delta': [],
               'gamma': [], 'call_theta': [], 'vega': []}

    for K in K_range:
        d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)

        results['K'].append(K)
        results['call_delta'].append(norm.cdf(d1))
        results['put_delta'].append(norm.cdf(d1) - 1)
        results['gamma'].append(norm.pdf(d1) / (S * sigma * np.sqrt(T)))

        theta_common = -(S * norm.pdf(d1) * sigma) / (2 * np.sqrt(T))
        call_theta = theta_common - r * K * np.exp(-r * T) * norm.cdf(d2)
        results['call_theta'].append(call_theta / 365)

        results['vega'].append(S * norm.pdf(d1) * np.sqrt(T) / 100)

    return results

# パラメータ
S = 100
K_range = np.arange(70, 131, 1)
T = 30 / 365
r = 0.05
sigma = 0.25

greeks = bs_greeks_surface(S, K_range, T, r, sigma)

# 結果のサンプル表示
print(f"{'行使価格':>8} {'コールΔ':>10} {'プットΔ':>10} {'ガンマ':>10} {'セータ':>10} {'ベガ':>10}")
print("-" * 68)
for i in range(0, len(K_range), 10):
    print(f"{greeks['K'][i]:>8.0f} {greeks['call_delta'][i]:>10.4f} "
          f"{greeks['put_delta'][i]:>10.4f} {greeks['gamma'][i]:>10.4f} "
          f"{greeks['call_theta'][i]:>10.4f} {greeks['vega'][i]:>10.4f}")

各グリークスの重要な直感的理解

デルタ -- デルタは「等価株数」ポジションと考えてください。デルタ0.50のコールは、50株を保有しているのと同じ動きをします(1契約100株の場合)。デルタはオプションがイン・ザ・マネーで終了する確率の近似値でもあります。

ガンマ -- ガンマはデルタの加速度です。ガンマが高いということは、デルタが急速に変化することを意味します。満期が近いATMオプションは巨大なガンマを持ち、売り手にとっては危険で、買い手にとってはエキサイティングです。

セータ -- 時間はオプション買い手の敵であり、売り手の味方です。セータによる時間的価値の減少は線形ではなく、満期が近づくにつれて加速します。オプションは最後の1週間で残りの時間的価値の約3分の1を失います。

ベガ -- ボラティリティは原資産価格に次いで、オプション価格に最も重要な要因です。オプションの買いは実現ボラティリティがインプライドボラティリティを上回ることへの賭けであり、オプションの売りはその逆の賭けです。

ロー -- 通常、短期オプションでは最も重要度の低いグリークスです。しかし、LEAPS(1年以上の長期オプション)の場合、金利変動の影響が時間とともに複利的に蓄積されるため、ローは重要になります。

一般的なオプション戦略

戦略比較表

| 戦略              | 最大利益           | 最大損失          | 損益分岐点              | 最適な状況                    | 複雑さ |
|-------------------|-------------------|------------------|------------------------|------------------------------|--------|
| ロングコール       | 無制限             | 支払プレミアム     | 行使価格 + プレミアム     | 強気、大きな上昇を期待         ||
| ロングプット       | 行使価格 - プレミアム| 支払プレミアム     | 行使価格 - プレミアム     | 弱気、大きな下落を期待         ||
| カバードコール     | プレミアム + (K-S) | S - プレミアム     | 購入価格 - プレミアム     | 中立からやや強気              ||
| プロテクティブプット| 上昇は無制限       | プレミアム + (S-K) | S + プレミアム           | 株式保有時の下落ヘッジ         ||
| ブルコールスプレッド |- デビット     | 純デビット        | 低い方のK + デビット      | 穏やかな上昇                  ||
| ベアプットスプレッド |- デビット     | 純デビット        | 高い方のK - デビット      | 穏やかな下落                  ||
| ロングストラドル    | 無制限            | 合計プレミアム     | K +/- 合計プレミアム     | 大きな動きを期待(方向不問)    ||
| アイアンコンドル    | 純クレジット       |- クレジット   | 内側の行使価格 +/- クレジット| 低ボラティリティ、レンジ相場   ||

カバードコールの実装

カバードコールは、株式100株を保有しながら、その株に対して1枚のコールオプションを売る戦略です。上値を制限する代わりにプレミアム収入を得ます。

import numpy as np

def covered_call_pnl(stock_prices: np.ndarray, purchase_price: float,
                     strike: float, premium: float) -> dict:
    """
    カバードコールポジションの損益を計算する。

    パラメータ:
    -----------
    stock_prices : 満期時の想定株価の配列
    purchase_price : 株式の購入価格
    strike : コールオプションの行使価格
    premium : コールオプション売却で受け取ったプレミアム
    """
    # 株式の損益
    stock_pnl = stock_prices - purchase_price

    # ショートコールの損益(株価が行使価格を超えるとマイナス)
    short_call_pnl = np.where(
        stock_prices > strike,
        premium - (stock_prices - strike),
        premium
    )

    # 合計損益
    total_pnl = stock_pnl + short_call_pnl

    # 主要指標
    max_profit = (strike - purchase_price) + premium
    breakeven = purchase_price - premium
    max_loss = breakeven  # 株価がゼロになった場合

    return {
        'stock_prices': stock_prices,
        'stock_pnl': stock_pnl,
        'option_pnl': short_call_pnl,
        'total_pnl': total_pnl,
        'max_profit': max_profit,
        'breakeven': breakeven,
        'max_loss': max_loss,
    }

# 例: AAPLを$185で購入、$190コールを$3.50で売却
prices = np.arange(160, 211, 1).astype(float)
result = covered_call_pnl(prices, purchase_price=185, strike=190, premium=3.50)

print("=== カバードコール損益: AAPL $185ロング + $190コールショート @ $3.50 ===")
print(f"最大利益: ${result['max_profit']:.2f} (${190}以上で)")
print(f"損益分岐点: ${result['breakeven']:.2f}")
print(f"最大損失: ${result['max_loss']:.2f} (株価がゼロの場合)")
print(f"\n各価格での損益:")
print(f"  株価 $170: ${result['total_pnl'][10]:.2f}")
print(f"  株価 $185: ${result['total_pnl'][25]:.2f}")
print(f"  株価 $190: ${result['total_pnl'][30]:.2f}")
print(f"  株価 $200: ${result['total_pnl'][40]:.2f}")
print(f"  株価 $210: ${result['total_pnl'][50]:.2f}")

アイアンコンドルの実装

アイアンコンドルは、原資産がレンジ内に留まることで利益を得る戦略です。ブルプットスプレッド(市場価格の下)とベアコールスプレッド(市場価格の上)で構成されます。

import numpy as np

def iron_condor_pnl(stock_prices: np.ndarray,
                    put_long_K: float, put_short_K: float,
                    call_short_K: float, call_long_K: float,
                    net_credit: float) -> dict:
    """
    アイアンコンドルの損益を計算する。

    レッグ構成:
    1. プット買い(put_long_K)  - 下方の保護
    2. プット売り(put_short_K) - プレミアム収受
    3. コール売り(call_short_K)- プレミアム収受
    4. コール買い(call_long_K) - 上方の保護
    """
    # ブルプットスプレッドの損益
    short_put_pnl = np.where(stock_prices < put_short_K, -(put_short_K - stock_prices), 0.0)
    long_put_pnl = np.where(stock_prices < put_long_K, put_long_K - stock_prices, 0.0)
    put_spread_pnl = short_put_pnl + long_put_pnl

    # ベアコールスプレッドの損益
    short_call_pnl = np.where(stock_prices > call_short_K, -(stock_prices - call_short_K), 0.0)
    long_call_pnl = np.where(stock_prices > call_long_K, stock_prices - call_long_K, 0.0)
    call_spread_pnl = short_call_pnl + long_call_pnl

    # 合計損益
    total_pnl = put_spread_pnl + call_spread_pnl + net_credit

    # 指標
    put_width = put_short_K - put_long_K
    call_width = call_long_K - call_short_K
    max_width = max(put_width, call_width)

    return {
        'stock_prices': stock_prices,
        'total_pnl': total_pnl,
        'max_profit': net_credit,
        'max_loss': max_width - net_credit,
        'lower_breakeven': put_short_K - net_credit,
        'upper_breakeven': call_short_K + net_credit,
    }

# 例: SPYアイアンコンドル
# SPY $500、30日満期
# 480プット買い、485プット売り、515コール売り、520コール買い
# 純クレジット: $1.80
prices = np.arange(470, 531, 0.5).astype(float)
ic = iron_condor_pnl(prices,
                     put_long_K=480, put_short_K=485,
                     call_short_K=515, call_long_K=520,
                     net_credit=1.80)

print("=== アイアンコンドル: SPY 480/485/515/520 @ $1.80クレジット ===")
print(f"最大利益:      ${ic['max_profit']:.2f} (SPYが$485-$515の範囲)")
print(f"最大損失:      ${ic['max_loss']:.2f}")
print(f"下方損益分岐点: ${ic['lower_breakeven']:.2f}")
print(f"上方損益分岐点: ${ic['upper_breakeven']:.2f}")
print(f"リスク・リワード比: 1:{ic['max_profit']/ic['max_loss']:.2f}")
print(f"利益ゾーン:    ${ic['upper_breakeven'] - ic['lower_breakeven']:.2f}幅 "
      f"(スポットの{(ic['upper_breakeven'] - ic['lower_breakeven'])/500*100:.1f}%)")

損益ダイアグラムの解説

損益(P&L)ダイアグラムの理解は、取引を開始する前にリスクを可視化するために不可欠です。X軸は満期時の原資産価格を、Y軸は利益または損失を表します。

ロングコールの損益: ダイアグラムは、行使価格以下のすべての価格で支払ったプレミアム(最大損失)の水平線を示し、損益分岐点(行使価格 + プレミアム)を超えると直線的に上昇します。傾きは原資産と1:1です。

アイアンコンドルの損益: ダイアグラムは、2つのショート行使価格の間で最大利益の水平線を示し、両側で下降傾斜し、ロング行使価格以遠で最大損失に達します。台地のような形状になります。

損益ダイアグラムの読み方のポイント:

  • 線がゼロを横切る点が損益分岐点
  • 最も平坦な部分が、株価がそこで満期を迎えてほしい場所
  • 傾きが急なほど、価格変動に対する感応度が高い
  • 最大利益と最大損失の差がリスク・リワード比

リスク管理フレームワーク

ケリー基準によるポジションサイジング

import numpy as np

def kelly_criterion(win_prob: float, win_loss_ratio: float) -> float:
    """
    ケリー基準で最適なベットサイズを計算する。

    f* = p - (1-p)/b

    ここで:
    p = 勝率
    b = 利益額と損失額の比率
    """
    return win_prob - (1 - win_prob) / win_loss_ratio

def options_position_sizing(account_size: float, max_risk_pct: float,
                           max_loss_per_contract: float,
                           kelly_fraction: float = 0.5) -> dict:
    """
    オプション取引のポジションサイズを計算する。

    パラメータ:
    -----------
    account_size : 口座の総資産額
    max_risk_pct : 1取引あたりのリスク上限(口座の割合)
    max_loss_per_contract : 1契約あたりの最大損失(手数料含む)
    kelly_fraction : ケリーの割合(ハーフケリーが一般的)
    """
    max_risk_dollars = account_size * max_risk_pct
    max_contracts_risk = int(max_risk_dollars / max_loss_per_contract)

    kelly_bet = kelly_fraction * account_size
    max_contracts_kelly = int(kelly_bet / max_loss_per_contract)

    # より保守的な方を採用
    recommended = min(max_contracts_risk, max_contracts_kelly)

    return {
        'account_size': account_size,
        'max_risk_dollars': max_risk_dollars,
        'max_contracts_by_risk': max_contracts_risk,
        'max_contracts_by_kelly': max_contracts_kelly,
        'recommended_contracts': max(1, recommended),
        'total_risk': max(1, recommended) * max_loss_per_contract,
        'risk_pct_of_account': max(1, recommended) * max_loss_per_contract / account_size * 100,
    }

# 例: アイアンコンドルのポジションサイジング
account = 100_000
win_prob = 0.70  # ICの勝率は約70%
avg_win = 1.80 * 100  # 1契約あたり$180
avg_loss = 3.20 * 100  # 1契約あたり$320

kelly = kelly_criterion(win_prob, avg_win / avg_loss)
print(f"フルケリー: {kelly:.2%}")
print(f"ハーフケリー: {kelly/2:.2%}")

sizing = options_position_sizing(
    account_size=account,
    max_risk_pct=0.02,  # 1取引あたり最大リスク2%
    max_loss_per_contract=320,
    kelly_fraction=kelly / 2  # ハーフケリー
)

print(f"\n=== アイアンコンドルのポジションサイジング ===")
for key, val in sizing.items():
    if isinstance(val, float):
        print(f"  {key}: ${val:,.2f}" if 'pct' not in key else f"  {key}: {val:.2f}%")
    else:
        print(f"  {key}: {val}")

リスク管理ルール

オプションリスクを管理するための体系的なチェックリストを以下に示します。

  1. 1取引あたり口座の2%以上をリスクにさらさない -- これにより連敗期間を乗り切れます。
  2. ポートフォリオ全体のセータは1日あたり口座の0.5%を超えない -- 過剰なセータ収受は過剰なリスクを意味します。
  3. 満期日を分散する -- すべてのポジションが同じ週に満期を迎えないようにします。
  4. 機械的なストップロスを設定する -- クレジットスプレッドでは受取プレミアムの2倍で損切りします。
  5. ポートフォリオのグリークスを追跡する -- 純デルタ、ガンマ、セータ、ベガのエクスポージャーを監視します。

よくある間違いと損失シナリオ

間違い1: テールリスクを理解せずにネイキッドオプションを売る

ネイキッド(無防備な)コールの売りは理論上無制限の損失をもたらします。ネイキッドプットの売りでさえ壊滅的な損失を招く可能性があります。2018年2月、XIV(逆VIX)が1日で96%暴落し、ボラティリティの売り手を壊滅させました。

ルール: 十分な経験とマージンがない限り、常にリスク限定戦略(スプレッド、コンドル)を使用してください。

間違い2: オプション購入時にインプライドボラティリティを無視する

# デモンストレーション: 決算前(高IV)にオプションを買った場合
# vs 決算後(IVクラッシュ)

pre_earnings_iv = 0.60   # 決算前のIV 60%
post_earnings_iv = 0.25  # 決算後のIV 25%(IVクラッシュ)

bs_pre = BlackScholes(S=100, K=100, T=14/365, r=0.05, sigma=pre_earnings_iv)
bs_post = BlackScholes(S=102, K=100, T=13/365, r=0.05, sigma=post_earnings_iv)

print("=== IVクラッシュの例 ===")
print(f"決算前のコール価格 (IV=60%):  ${bs_pre.call_price():.2f}")
print(f"決算後のコール価格 (IV=25%, 株価$2上昇): ${bs_post.call_price():.2f}")
print(f"株価が上昇したにもかかわらず損益: ${bs_post.call_price() - bs_pre.call_price():.2f}")
print(f"\n株価は$2上昇しましたが、IVクラッシュにより損失が発生しました!")

このシナリオは非常によくあります。決算前にコールを買うエンジニアは、方向を正しく予測しても、ボラティリティの収縮が方向的な利益を上回るため、しばしば損失を被ります。

間違い3: 不適切なポジションサイジング

1つのオプション取引に過大な資金を配分することは、口座を破綻させる最も一般的な方法です。オプションはゼロになり得ます。株式の場合、50%のドローダウンから回復するには100%の利益が必要ですが、無価値で満期を迎えたオプションは投資額の100%の損失となります。

間違い4: 権利行使リスクの理解不足

アメリカンスタイルのオプションは満期前のいつでも行使される可能性があります。イン・ザ・マネーのショートオプション、特に満期近くのプットや配当落ち日が近いコールは、早期行使リスクが高くなります。権利行使を望まない場合は、満期前に必ずポジションをクローズまたはロールオーバーしてください。

間違い5: 戦略の過度な複雑化

4レッグ以上のマルチレッグ戦略は、手数料コストが高く、ビッド・アスクスプレッドが広いため、理論上のエッジを蝕むことが多いです。シンプルな戦略(カバードコール、キャッシュ担保プット)から始め、実証されたエッジがある場合にのみ複雑さを追加してください。

実践的ワークフロー: 分析から執行まで

import numpy as np
from scipy.stats import norm

def screen_iron_condor(spot: float, sigma: float, T: float, r: float,
                       target_delta: float = 0.16,
                       spread_width: float = 5.0) -> dict:
    """
    目標ショートストライクデルタでアイアンコンドルをスクリーニングする。

    デルタ約0.16は約1標準偏差OTMに相当し、
    約68%の利益確率を提供する。
    """
    # BSの逆関数を使用して目標デルタのストライクを見つける
    # プットの場合: |デルタ| = target_deltaとなるKを見つける
    # コールの場合: デルタ = target_deltaとなるKを見つける

    # 二分探索でストライクを見つける
    def find_strike(target_d, option_type, lo, hi, tol=0.01):
        for _ in range(100):
            mid = (lo + hi) / 2
            d1 = (np.log(spot / mid) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
            if option_type == 'call':
                current_d = norm.cdf(d1)
            else:
                current_d = abs(norm.cdf(d1) - 1)

            if abs(current_d - target_d) < tol:
                return round(mid)
            if option_type == 'call':
                if current_d > target_d:
                    lo = mid
                else:
                    hi = mid
            else:
                if current_d > target_d:
                    hi = mid
                else:
                    lo = mid
        return round(mid)

    call_short_K = find_strike(target_delta, 'call', spot, spot * 1.5)
    put_short_K = find_strike(target_delta, 'put', spot * 0.5, spot)
    call_long_K = call_short_K + spread_width
    put_long_K = put_short_K - spread_width

    # 各レッグの価格計算
    def bs_price(K, opt_type):
        d1 = (np.log(spot / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)
        if opt_type == 'call':
            return spot * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
        return K * np.exp(-r * T) * norm.cdf(-d2) - spot * norm.cdf(-d1)

    credit_call_spread = bs_price(call_short_K, 'call') - bs_price(call_long_K, 'call')
    credit_put_spread = bs_price(put_short_K, 'put') - bs_price(put_long_K, 'put')
    total_credit = credit_call_spread + credit_put_spread
    max_loss = spread_width - total_credit

    return {
        'put_long': put_long_K,
        'put_short': put_short_K,
        'call_short': call_short_K,
        'call_long': call_long_K,
        'credit_received': round(total_credit, 2),
        'max_loss': round(max_loss, 2),
        'risk_reward': f"1:{total_credit/max_loss:.2f}",
        'credit_pct_of_width': f"{total_credit/spread_width*100:.1f}%",
    }

# SPYアイアンコンドルのスクリーニング
result = screen_iron_condor(spot=500, sigma=0.18, T=30/365, r=0.05)
print("=== アイアンコンドル スクリーニング結果 (SPY, 30日満期, IV 18%) ===")
for key, val in result.items():
    print(f"  {key}: {val}")

まとめ

オプションはすべてのエンジニアの金融ツールキットにおける強力なツールです。主要なポイントは以下の通りです。

  1. Black-Scholesモデルを習得する -- 数式だけでなく、前提条件とその限界を理解すること。
  2. グリークスを深く理解する -- 異なるシナリオでポジションがどのように振る舞うかを正確に教えてくれます。
  3. リスク限定戦略から始める -- 複雑なマルチレッグ取引に挑む前に、カバードコール、プロテクティブプット、垂直スプレッドから始めること。
  4. ポジションサイジングがすべて -- どんな戦略も不適切なリスク管理では生き残れません。
  5. インプライドボラティリティは方向性より重要 -- 特にオプション買い手にとって。
  6. まずペーパートレードを行う -- 実資本をリスクにさらす前に、QuantLibやオプションペーパートレーディングプラットフォームを活用すること。

本記事のコード例は、独自のオプション分析ツールキットを構築するための基盤を提供します。実際の市場データ、バックテスト機能、自動スクリーニングを追加して、オプション取引への体系的なアプローチを開発してください。

参考文献

  • Hull, John C. Options, Futures, and Other Derivatives, 11th Edition. Pearson, 2022. デリバティブの価格決定とリスク管理に関する決定版テキスト。
  • Black, Fischer; Scholes, Myron. "The Pricing of Options and Corporate Liabilities." Journal of Political Economy, Vol. 81, No. 3, 1973, pp. 637-654. Black-Scholesモデルを紹介した原論文。
  • CBOE Options Education -- シカゴ・オプション取引所によるオプション戦略と仕組みに関する教育リソース。
  • QuantLib Documentation -- 包括的なオプション価格計算を実装した定量ファイナンスのオープンソースライブラリ。
  • Investopedia Options Guide -- オプションの概念、戦略、用語に関するわかりやすい入門ガイド。
  • Natenberg, Sheldon. Option Volatility and Pricing, 2nd Edition. McGraw-Hill, 2014. ボラティリティ取引とオプション価格決定に関する必読書。
  • Macroption Black-Scholes Formula Reference -- すべてのグリークスを含むBlack-Scholes公式の詳細な解説。