【QuantLib-Pythonの使い方】第3回:Black-Sholesとアメリカンオプション

はじめに

今回はBlack-Scholesモデルでアメリカンオプションを評価する方法について説明する。アメリカンオプションとは、所定の期間であればいつでも権利行使できるオプションで、エクイティ(株式)オプションなどで見られる。

前回の記事

ソースコード全体

import QuantLib as ql
import pandas as pd

# date settings
todaysDate = ql.Date(10, ql.October, 2020)
day_count = ql.Actual365Fixed()
calendar = ql.Japan()

# market data
spot = 120.0
risk_free = 0.001
dividend = 0.0163
vol = 0.20

# trade data
maturity = calendar.advance(todaysDate, 6 * ql.Months, ql.ModifiedFollowing)
strike = 130.0
option_type = ql.Option.Call

# numerical method settings
steps = 201
fdm_time_steps = 201
fdm_state_grid_points = 200

# make market objects
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot))
flat_rf_ts = ql.YieldTermStructureHandle(
    ql.FlatForward(todaysDate, risk_free, day_count)
)
flat_div_ts = ql.YieldTermStructureHandle(
    ql.FlatForward(todaysDate, dividend, day_count)
)
flat_vol_ts = ql.BlackVolTermStructureHandle(
    ql.BlackConstantVol(todaysDate, calendar, vol, day_count)
)

# make trade objects
payoff = ql.PlainVanillaPayoff(option_type, strike)
exercise = ql.AmericanExercise(todaysDate, maturity)
option = ql.VanillaOption(payoff, exercise)

# make model objects
bsm_process = ql.BlackScholesMertonProcess(
    spot_handle, flat_div_ts, flat_rf_ts, flat_vol_ts)

# make pricing engines
engines = {
    # make analytical approximation pricing engines
    "BaroneAdesiWhaley": ql.BaroneAdesiWhaleyEngine(bsm_process),
    "BjerksundStensland": ql.BjerksundStenslandEngine(bsm_process),
    "JuQuadraticApprox": ql.JuQuadraticApproximationEngine(bsm_process),
    # make binomial tree pricing engines
    "BinomialCRR": ql.BinomialCRRVanillaEngine(bsm_process, steps),
    "BinomialEQP": ql.BinomialEQPVanillaEngine(bsm_process, steps),
    "BinomialJR": ql.BinomialJRVanillaEngine(bsm_process, steps),
    "BinomialLR": ql.BinomialLRVanillaEngine(bsm_process, steps),
    "BinomialTian": ql.BinomialTianVanillaEngine(bsm_process, steps),
    "BinomialTrigeorgis": ql.BinomialTrigeorgisVanillaEngine(bsm_process, steps),
    "BinomialJoshi4": ql.BinomialJ4VanillaEngine(bsm_process, steps),
    # make FDM pricing engines
    "FDM": ql.FdBlackScholesVanillaEngine(bsm_process, fdm_time_steps, fdm_state_grid_points)
}

# run calculation
results = []
for name, engine in engines.items():
    option.setPricingEngine(engine)
    results.append((name, option.NPV()))

# show results
df = pd.DataFrame(results, columns=["Method", "BS Option NPV"])
print(df)

実行結果

                Method  BS Option NPV
0    BaroneAdesiWhaley       1.308971
1   BjerksundStensland       1.303244
2    JuQuadraticApprox       1.307288
3          BinomialCRR       1.310654
4          BinomialEQP       1.316055
5           BinomialJR       1.304800
6           BinomialLR       1.308482
7         BinomialTian       1.306432
8   BinomialTrigeorgis       1.310718
9       BinomialJoshi4       1.308482
10                 FDM       1.307815

ソースコードの解説

冒頭

import QuantLib as ql
import pandas as pd

QuantLibとpandasをインポートする。
pandasについては、結果の表示にDataFrameを使うためである。

日付の設定

# date settings
todaysDate = ql.Date(10, ql.October, 2020)
day_count = ql.Actual365Fixed()
calendar = ql.Japan()

日付関連の設定をする。

評価基準日を2020年10月10日とする。

day_countは、日付から日付の間隔を年換算する方法を指定する。これは金利やボラティリティなどの期間構造を表すオブジェクトを生成する際にインプットとして与える必要がある。

calendarは、金融機関の営業日を求める際に用いる休日参照都市を指定する。ここではBlackConstantVolのコンストラクタに渡す際に使っている。日本ならql.Japan()とする。

マーケットデータ

# market data
spot = 120.0
risk_free = 0.001
dividend = 0.0163
vol = 0.20

ここでは評価に必要なマーケットデータを設定する。
エクイティオプションの文脈で書いているが、スポット株価、無リスク金利、配当利回り、(Black)ボラティリティ、である。
離散配当を考慮する方法は次回に取り上げる。

この段階では生の数値だが、以下ではこれらをQuantLibのオブジェクトに変換していくことになる。

取引データ

# trade data
maturity = calendar.advance(todaysDate, 6 * ql.Months, ql.ModifiedFollowing)
strike = 130.0
option_type = ql.Option.Call

評価対象の取引データを設定する。
満期日、ストライク、Call/Putの別である。
満期日は基準日から6か月後とする。ql.ModifiedFollowingは休日に当たった場合の調整方法
Call/PutはQuantLibの列挙型Optionを使う。

数値計算法の設定

# numerical method settings
steps = 201
fdm_time_steps = 201
fdm_state_grid_points = 200

二項ツリーやPDEを解く際の設定を与える。
stepsはツリーのタイムステップ数で、たしか基準日を含めた数値だった気がする。fdm_time_stepsも同様。fdm_state_grid_pointsはPDEを解く際の原資産価格の区間数を与える。

マーケットのオブジェクト生成

# make market objects
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot))
flat_rf_ts = ql.YieldTermStructureHandle(
    ql.FlatForward(todaysDate, risk_free, day_count)
)
flat_div_ts = ql.YieldTermStructureHandle(
    ql.FlatForward(todaysDate, dividend, day_count)
)
flat_vol_ts = ql.BlackVolTermStructureHandle(
    ql.BlackConstantVol(todaysDate, calendar, vol, day_count)
)

マーケットデータの数値をQuantLibのオブジェクトに変換していく。
いずれも最終的には、BlackScholesMertonProcessのオブジェクトを作る際に、そのコンストラクタに渡す部品である。
ここは前回と同様なので、第2回の記事を参照されたい。

取引のオブジェクト生成

# make trade objects
payoff = ql.PlainVanillaPayoff(option_type, strike)
exercise = ql.AmericanExercise(todaysDate, maturity)
option = ql.VanillaOption(payoff, exercise)

取引データから取引のオブジェクトを生成する。
バニラオプションはペイオフと権利行使タイプをインプットする。

ペイオフにはCall/Putの別とストライクを設定する。
権利行使タイプには、今回はAmericanExerciseとして、行使可能期間の開始日と終了日を設定する。ここでは簡単のため基準日から満期日までとしている。

最終的にはInstrumentクラスを継承するクラスのオブジェクトを作ればよい。ここではそれがVanillaOptionである。

モデル関連のオブジェクト生成

# make model objects
bsm_process = ql.BlackScholesMertonProcess(
    spot_handle, flat_div_ts, flat_rf_ts, flat_vol_ts)

マーケットデータのオブジェクトを組み合わせて、モデル関連のオブジェクトを生成する。
最終的にはPricingEngineを作ればいいのだが、PricingEngineのコンストラクタに与える部品を作る必要がある。Black-Scholesモデルの場合は、PricingEngineを作るのに必要な部品はBlackScholesMertonProcessのオブジェクトだけである。

プライシングエンジンのオブジェクト生成

# make pricing engines
engines = {
    # make analytical approximation pricing engines
    "BaroneAdesiWhaley": ql.BaroneAdesiWhaleyEngine(bsm_process),
    "BjerksundStensland": ql.BjerksundStenslandEngine(bsm_process),
    "JuQuadraticApprox": ql.JuQuadraticApproximationEngine(bsm_process),
    # make binomial tree pricing engines
    "BinomialCRR": ql.BinomialCRRVanillaEngine(bsm_process, steps),
    "BinomialEQP": ql.BinomialEQPVanillaEngine(bsm_process, steps),
    "BinomialJR": ql.BinomialJRVanillaEngine(bsm_process, steps),
    "BinomialLR": ql.BinomialLRVanillaEngine(bsm_process, steps),
    "BinomialTian": ql.BinomialTianVanillaEngine(bsm_process, steps),
    "BinomialTrigeorgis": ql.BinomialTrigeorgisVanillaEngine(bsm_process, steps),
    "BinomialJoshi4": ql.BinomialJ4VanillaEngine(bsm_process, steps),
    # make FDM pricing engines
    "FDM": ql.FdBlackScholesVanillaEngine(bsm_process, fdm_time_steps, fdm_state_grid_points)
}

モデル関連のオブジェクトと、数値計算の設定を組み合わせて、PricingEngineのオブジェクトを生成する。

今回の評価対象はAmericanExerciseのVanillaOptionであり、それをBlack-Scholesモデルで評価するためのPricingEngineは数多くある。数値計算法の数だけ別のPricingEngineがある(解析近似解、ツリー、FDM、アメリカンモンテカルロ、など)が、ここでは解析近似解、二項ツリー、FDM(PDEを有限差分法で解く)を取り上げる。
各PricingEngineについて、名前の文字列とオブジェクトをディクショナリにセットする。

解析近似解は以下の3つ:

・Barone-Adesi and Whaley (1987)
・Bjerksund and Stensland (1993)
・Ju, N. (1999) “An Approximate Formula for Pricing American Options”, Journal of Derivatives Winter 1999

二項ツリーは以下の7つ:

・CRR:Cox-Ross-Rubinsteinの方法 (multiplicative equal jumps)
・EQP:(additive equal probabilities)
・JR:Jarrow-Ruddの方法 (multiplicative equal probabilities)
・LR:Leisen & Reimerの方法 (multiplicative approach)
・Tian:Tianの方法 (third moment matching, multiplicative approach)
・Trigeorgis:Trigeorgisの方法 (additive equal jumps)
・Joshi4:Mark S. Joshiの方法

最も有名なのはCRRだろう。
二項ツリーのPricingEngineには時間軸のステップ数を与える。

二項ツリーには株価の期待成長率を表現する方法が大きく2つあり、

・上昇確率と下降確率を同じにして、上昇幅と下降幅に差をつける方法
・上昇幅と下降幅を同じにして、上昇確率と下降確率に差をつける方法

に分かれる。加えて、上昇幅・下降幅の表現方法として、

・相対変化、つまり割合 (multiplicative) で表す方法
・絶対変化、つまり差分 (additive) で表す方法

に分かれる。

Black-ScholeのPDEを直接解くFDMのPricingEngineには、時間軸のステップ数と原資産価格の軸のステップ数を与える。

取引にプライシングエンジンをセットしNPVを計算

# run calculation
results = []
for name, engine in engines.items():
    option.setPricingEngine(engine)
    results.append((name, option.NPV()))

上で生成したディクショナリからプライシングエンジンの名前とオブジェクトを一つずつ取り出して、
・.setPricingEngine()でPricingEngineをセットし、
・.NPV()でオプション価値を計算する。
結果をPricingEngineの名前とともにresultsにタプルで格納する。
Greeksもoption.delta()やoption.vega()などで求められる。ただし少しマイナーな商品の場合は、Greeksをサポートしていないので、自分で数値微分するなどして求める必要がある。

QuantLibのプライシングエンジンは全てPricingEngineクラスを継承しており、このように異なるプライシングエンジンを統一的に扱うことができる。

結果の表示

# show results
df = pd.DataFrame(results, columns=["Method", "BS Option NPV"])
print(df)

最後に結果を列ラベルとともにDataFrameに渡して出力する。

おわりに

今回はアメリカンオプションをBlack-Scholesモデルで評価するコードについて解説した。いったんBlackScholesMertonProcessのオブジェクトを作ってしまえば、容易に多数のPricingEngineを作ってそれぞれ評価することができる。

次回はエクイティ特有のトピックとして離散配当を考慮する方法を取り上げる。
その後は為替系商品に移り、通貨オプションをVanna-Volga法(バンナボルガ法)で評価する方法などを扱った後、イールドカーブ生成など金利系商品に入っていく予定。

関連記事