【QuantLib-Pythonの使い方】第2回:Black-Sholesとバニラオプション

はじめに

今回は前回に動かしたコードの内容を解説していく。
コードではバニラオプションをBlack-Scholesモデルで評価するだけで、金融工学界隈のHello World的なものであり実戦では使い物にならない。しかし、初心者はまず構造がシンプルで明快な、小さいサンプルコードから学ぶことによって、QuantLib-Pythonで金融商品を評価する際の、一般的な流れがつかみやすいと思う。

前回の記事

ソースコード全体

import QuantLib as ql

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

# market data
spot = 130
risk_free = 0.001
dividend = 0.02
vol = 0.20

# trade data
maturity = ql.Date(1, 4, 2021)
strike = 130
option_type = ql.Option.Call

# 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.EuropeanExercise(maturity)
option = ql.VanillaOption(payoff, exercise)

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

# make pricing engine
engine = ql.AnalyticEuropeanEngine(bsm_process)

# set pricing engine
option.setPricingEngine(engine)

# run calculation
print(option.NPV())

実行結果

6.683946573403789

ソースコードの解説

冒頭

import QuantLib as ql

QuantLibをインポートする。QuantLib界隈では略称はqlとするのが普通である。以下ではQuantLibのクラスや関数、列挙型などを使うには、先頭にql.をつけて参照する。

日付の設定

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

日付関連の設定をする。
評価基準日を2020年10月1日とする。QuantLibは欧州を拠点に開発されたので、日付型であるDateは、日、月、年の順番で指定。

day_countは、日付から日付の間隔を年換算する方法を指定する。これは金利やボラティリティなどの期間構造を表すオブジェクトを生成する際にインプットとして与える必要がある。day_countの方法はDay Count Conventionといって、かなり多くの方法があり、そのうちどれを使うのかは実務では重要だが、ここでは全てAct/365(Fixed)を使う。

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

マーケットデータ

# market data
spot = 130
risk_free = 0.001
dividend = 0.02
vol = 0.20

ここでは評価に必要なマーケットデータを設定する。
エクイティオプションの文脈で書いているが、スポット株価、無リスク金利、配当利回り、(Black)ボラティリティ、である。
この段階では生の数値だが、以下ではこれらをQuantLibのオブジェクトに変換していくことになる。

取引データ

# trade data
maturity = ql.Date(1, 4, 2021)
strike = 130
option_type = ql.Option.Call

評価対象の取引データを設定する。
満期日、ストライク、Call/Putの別。
Call/PutはQuantLibの列挙型Optionを使う。

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

# 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のオブジェクトを作る際に、そのコンストラクタに渡す部品である。

スポット株価はql.QuoteHandle()で作る。QuoteHandleは、QuoteのHandleを意味するが、以下でQuoteとHandleについてざっくり簡単に説明する。

Quoteは、マーケットデータが変化すればそれをPricingEngine(モデルのようなもの)やInstrument(評価対象の商品)などに知らせる役割を果たす。Instrumentは、マーケットデータが変化すればNPVを再計算し、変化していなければ、直近に計算して保持してあるNPVをそのまま返す。そのためには、マーケットデータが変化したかどうかをモニタリングする部品が必要だが、それがQuoteである。

HandleはC++でいうところのshared_ptrのshared_ptrであるが、使い道としては、複数のデータソースから取ってきた複数のマーケットデータを切り替えて、時価を再計算するのに使う。データソースAで時価を計算し、その後、データソースBに切り替えて時価を再計算する、というような場合に役立つ。最終的に評価に使うPricingEngineは多くの部品を組み合わせてできているので、普通は、そのもとになるデータソースを変更すると、根っこにある部品から順番に組み直さないといけなくなる。しかしHandleは部品であるポインタを指すポインタなので、部品Aから部品Bに変更する場合には、ポインタが指す先を部品Aから部品Bに変更するだけでよい。根っこの部品から組み直す必要がなくなるわけである。

YieldTermStructureHandleも同様で、YieldTermStructureを指すHandleである。Black-Sholesなので無リスク金利も配当利回りも一定であり、FlatForwardでYieldTermStructure(イールドカーブ)を生成している。

BlackVolTermStructureHandleはBlackVolTermStructureを指すHandleである。Black-Sholesなのでボラティリティも一定であり、BlackConstantVolでBlackVolTermStructure(ボラティリティサーフェイス)を生成している。BlackVolTermStructureは基本となるクラスであり、これを継承するクラスが多数ある。一定ならコンスタントボラティリティであり、期間構造のみならボラティリティカーブであり、ストライク方向の構造つまりスマイルがあればボラティリティサーフェイスであり、金利系のように複数のUnderlyingがあればボラティリティキューブである。これらいろいろなボラティリティ構造をひとつにまとめて表現しているのがBlackVolTermStructureということになる。

取引のオブジェクト生成

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

取引データから取引のオブジェクトを生成する。
バニラオプションはペイオフと権利行使タイプをインプットする。
最終的にはInstrumentクラスを継承するクラスのオブジェクトを作ればよい。ここではそれがVanillaOptionである。

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

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

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

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

# make pricing engine
engine = ql.AnalyticEuropeanEngine(bsm_process)

モデル関連のオブジェクトを組み合わせて、PricingEngineのオブジェクトを生成する。
今回の評価対象はVanillaOptionであり、それをBlack-Scholesモデルで評価するためのPricingEngineは複数ある。それは数値計算法の数だけ別のPricingEngineがある(解析解、数値積分、ツリー、FDM、モンテカルロ)わけだが、普通は解析解を使うだろう。ここでは解析解のPricingEngineであるAnalyticEuropeanEngineを使う。

取引にプライシングエンジンをセット

# set pricing engine
option.setPricingEngine(engine)

Instrumentクラスを継承するクラスはsetPricingEngine()をメンバに持っているのでそれを使ってPricingEngineをセットする。これでプライシングの準備完了となる。

取引のNPVを計算

# run calculation
print(option.NPV())

最後に取引オブジェクトからNPV()を呼び出して時価を計算する。
Greeksもoption.delta()やoption.vega()で求められる。

おわりに

今回はバニラオプションをBlack-Scholesモデルで評価するコードについて解説した。初心者向けにできるだけ簡単な例を示したので、これではありがたみがわからないかもしれない。次回はエクイティでよく見られるAmericanOptionをFDMやツリーで評価してみる。

関連記事