Pythonでコンパウンド・オプションの価格計算(解析解)

本記事の目的と対象

本記事の目的は、コンパウンド・オプションの価格を解析的に表現し、Pythonを用いてコーディングする方法について述べることである。

対象者は以下の条件を満たす読者である。
  • 基本的なオプションについて理解している人
  • Pythonを使う環境が整っている人
  • Pythonを使ってコンパウンド・オプションの価格を計算したい人
また、以下の記事の内容を前提とする。

なお、本記事の内容は下記書籍の内容を参考にしているため、合わせて参照してほしい。

目次

コンパウンド・オプションとはなにか

コンパウンド・オプションとは、オプションのオプション、すなわちオプションを買うもしくは売る権利である。

オプションにはコールとプットがあるため、コンパウンド・オプションは、以下の4種類が存在する。
  • コールのコール
  • コールのプット
  • プットのコール
  • プットのプット
オプションがヨーロピアンの場合には通常、権利行使価格と権利行使日が設定されるので、オプションのオプションたるコンパウンド・オプションには、二つの権利行使価格と権利行使日がある。

コンパウンド・オプションの保有者は、第一権利行使日\(T_1 \)において、第一権利行使価格\( K_1\)にて、原オプションを購入もしくは売却する権利を有する。

このとき、第一権利行使日\( T_1\)における原オプション価値が、第一権利行使価格\( K_1\)を上回るとき、コンパウンド・オプションは行使される。

さらにこの原オプションは、第二権利行使日\(T_2 \)において、第二権利行使価格\( K_2\)で、原資産を購入もしくは売却する権利である。

以下では、コールオプションを買う権利、つまりコールオプションのコールオプションの価格を解析的に表現し、それを用いてPythonで計算する。

コンパウンド・オプションの解析解

コール・オプションに対するヨーロピアン・コール・オプションの価格は、 \begin{eqnarray*} S_0e^{-qT_2}M(a_1,b_1;\sqrt{T_1/T_2})-K_2e^{-rT_2}M(a_2,b_2;\sqrt{T_1/T_2})-K_1e^{-rT_1}N(a_2) \end{eqnarray*}
で表される[1]。

ただし、\( M(a,b;\rho)\)は相関係数\( \rho\)の二次元正規分布の累積分布関数であり、第一変数が\( a\)以下かつ、第二変数が\( b\)以下のときの値である。

また、
\begin{eqnarray*} a_1&=&\frac{\ln(S_{0}/S^*)+(r-q+\sigma^2/2)T_1}{\sigma\sqrt{T_1}}\\ a_2&=&a_1-\sigma\sqrt{T_1}\\ b_1&=&\frac{\ln(S_{0}/K_2)+(r-q+\sigma^2/2)T_2}{\sigma\sqrt{T_2}}\\ b_2&=&b_1-\sigma\sqrt{T_2} \end{eqnarray*} であり、変数\(S^* \)は\( C_{T_{1}}(S_{T_1},K_2,T_{2}-T_{1})=K_1\)を満たす\( S_{T_1}\)、つまり原オプションが時点\(T_1 \)でAt-The-Moneyとなるような原資産価格である。

Pythonでのコード例

まず必要なモジュールをインポートする。

scipy.stats.normは一次元正規分布の累積密度関数を、scipy.stats.mvnは二次元の正規分布の累積密度関数を使用するために必要である。

また、scipyに組み込まれたいくつかの関数と、方程式を解くためのサブモジュールscipy.optimizeをインポートする。

さらに、数値の組を扱うための配列を呼び出すため、numpyをインポートする。
from scipy.stats import norm, mvn
from scipy import exp,log,sqrt,optimize,pi,sign
import numpy as np

基本的なブラック・ショールズ式の関数を定義する。

これは変数\( S^*\)を計算するために必要である。
def BS_Call(S, sigma ,r,q,T,K):
    d1 = (log(S / K) + (r -q+ sigma**2 / 2) * T) / (sigma * sqrt(T))
    d2 = d1 - sigma * sqrt(T)
    BS_Call = S * exp(-q * T) * norm.cdf(x=d1, loc=0, scale=1) \
             -K * exp(-r * T) * norm.cdf(x=d2, loc=0, scale=1)
    return BS_Call

変数\( S^*\)を計算するために\( C_{T_{1}}(S_{T_1},K_2,T_{2}-T_{1})=K_1\)を解く必要がある。

左辺を移行し、\( h(S_{sol})=C_{T_{1}}(S_{sol},K_2,T_{2}-T_{1})-K_1=0\)として方程式を解くため、関数を定義する。
def h(S_sol):
    return BS_Call(S_sol, sigma ,r,q,T2-T1,K2)-K1

二次元正規分布の累積分布関数を定義する。

第一変数が\( a\)、第二変数が\( b\)、相関係数が\( c\)であるときの、scipy.stats.mvnモジュールを用いた二次元正規分布の累積分布関数\( M_{MVN}(a,b;c)\)は、以下のようにコーディングすればよい。
def M_MVN(a,b,c):
    mean = np.array([0,0])
    Sigma = np.array([[1,c],[c,1]])
    lower=np.array([-10000,-10000])
    upper=np.array([a,b])
    p,i=mvn.mvnun(lower,upper,mean,Sigma)   
    return p

以上を踏まえ、コール・オン・コール・コンパウンド・オプションの価格は、以下のようなプログラムで計算可能である。
def CoC_AN_MVN(S0,T1,T2,K1,K2):
    S_star=float(optimize.fsolve(h,100)) #配列形式を数値に変換
    a1 = (log(S0 / S_star) + (r-q + sigma**2 / 2) * T1) / (sigma * sqrt(T1))
    a2 = a1 - sigma * sqrt(T1)
    b1 = (log(S0 / K2) + (r-q + sigma**2 / 2) * T2) / (sigma * sqrt(T2))
    b2 = b1 - sigma * sqrt(T2)
    CoC_AN = S0  * exp(-q * T2)*M_MVN(a1,b1,sqrt(T1/T2))\
          - K2 * exp(-r * T2) *M_MVN(a2,b2,sqrt(T1/T2))\
          -K1* exp(-r * T1) *norm.cdf(x=a2, loc=0, scale=1)
    return CoC_AN

計算結果

以下のインプットで計算する。
S0=100
sigma=30/100
r=5/100
q=0/100
T1=1
T2=2
K1=20
K2=100
rho=0.3
SampleT1=100
SampleT2=100
SampleOP=100

計算結果は以下の通り。
>>> CoC_AN_MVN(S0,T1,T2,K1,K2)
9.1383287801281021

検算(『フィナンシャルエンジニアリング』の結果と照合)

『フィナンシャルエンジニアリング』[1]に付属しているソフトウェアDerivaGemを用いると、様々な金融商品を評価することが出来る。

コンパウンド・オプションの価格も計算可能である。

本節では前節での計算結果が、DerivaGemの計算結果と一致することを確かめる。

DevivaGemをCD-ROMからインストールすると、3つのExcelマクロファイルが利用できる。そのうちDG300 functions.xlsを開く。

このブックのうち、Exotic Optionsシートにコンパウンド・オプションの計算フォームが載っている。
ここに前節と同様のデータをインプットすると、
9.138265559
という計算結果が得られる。

前節の結果9.1383287と小数第3位まで一致している。

小数第4位以降で差異が生じているのは、正規分布の累積分布関数の定義が異なっているためであると考えられる。

まとめ

本記事では、コンパウンド・オプションの解析解をPythonで実装する際のコード例を提供し、インプットデータを与え実際に価格の計算を行った。

その結果は、著名な金融工学の書籍である『フィナンシャルエンジニアリング』に付属の計算ソフトの結果に一致した。

参考文献

スポンサードリンク