2
1

QuantLib-Pythonによるスワップションの時価評価

Last updated at Posted at 2024-04-22

0. はじめに

0-1. QuantLib-Python

QuantLib-PythonとはQuantLibというC++で書かれた「金融商品の時価評価ライブラリ」をPythonから呼び出すためのラッパーである.QuantLibではスワップやオプションといった金融商品の時価を計算することができ,それを手軽にPythonから呼び出せるQuantLib-Python(以下これを単にQuantLibと呼ぶ)は大変便利なものである.詳細は公式HPへ.

0-2. このページで解説すること

QuantLibを用いた

  1. ブートストラップ
  2. スワップの時価評価
  3. スワップション(バミューダン)の時価評価

0-3. 前提

  • 計算環境
    • Windows 11
    • Python 3.12
    • pandas 2-2-2
    • QuantLib 1.33
  • 数理ファイナンスの知識
    • スワップ,スワップションの商品性
    • 金融商品を評価するうえでの会計知識
    • QuantLibについて基本的な文法

1. ソースコード

次の条件のスワップを考える.

  • スワップ
    • 受け
    • スポット・スタート
    • 想定元本:100
    • 満期:5年
    • 固定レグ:6ヶ月ごとに0.5%
    • 変動レグ:3ヶ月ごとに日本円TIBOR 3M,最初の利払いは0.4%
    • スプレッド:なし

これを原資産とするスワップションを考える.

  • スワップション
    • コール
    • 行使日:バミューダン
    • ストライク:0.35%

これらのプライシングを記述したの次のソースコードである.

import QuantLib as ql

eval_date = ql.Date(31,3,2023)
ql.Settings.instance().evaluationDate = eval_date

settle_days_over_night = 0                      # 決済日
settle_days_spot_next = 2                       # 決済日
calendar = ql.Japan()                           # 祝休日
business_day_convention = ql.ModifiedFollowing  # 営業日調整
end_of_month = False                            # 月末日ロール

instruments = [
    # 商品    年限 レート
    ('depo', '1M', 0.1), # 預金
    ('depo', '3M', 0.1),
    ('depo', '6M', 0.2),
    ('fra',  '6M', 0.2), # 金先
    ('ois',  '1Y', 0.2), # スワップ
    ('ois',  '2Y', 0.3),
    ('ois',  '3Y', 0.4),
    ('ois',  '5Y', 0.5)
]

overnight_index_tona = ql.OvernightIndex('TONA',                 # インデックスの名前
                                         settle_days_over_night, # 決済日
                                         ql.JPYCurrency(),       # 通貨
                                         ql.Japan(),             # 休祝日カレンダー
                                         ql.Actual365Fixed()     #利息付利ルール
                                         )

helpers = ql.RateHelperVector()
for instrument, tenor, rate in instruments:    
    if instrument == 'depo':
        helpers.append(
            ql.DepositRateHelper(
                rate/100.0,
                ql.Period(tenor),
                settle_days_spot_next,
                calendar,
                business_day_convention,
                end_of_month,
                ql.Actual360()
                )
            )
    if instrument == 'fra':
        months_to_start = ql.Period(tenor).length()
        ibor_index = ql.Tibor(ql.Period('3M'))
        # ibor_index = ql.Euribor6M(yts)
        helpers.append(
            ql.FraRateHelper(
                rate/100.0,
                months_to_start,
                ibor_index
                )
            )
    if instrument == 'ois':
        helpers.append(
            ql.OISRateHelper(
                settle_days_spot_next,
                ql.Period(tenor),
                ql.QuoteHandle(ql.SimpleQuote(rate/100.0)),
                overnight_index_tona,                        # iborIndex
                paymentLag = 2,                              # 複利後決めのPayment Delay
                paymentConvention = business_day_convention,
                paymentCalendar = ql.Japan()
                    )
            )

log_cubic_discount = ql.PiecewiseLogCubicDiscount(settle_days_spot_next,
                                                  ql.Japan(),
                                                  helpers,
                                                  ql.Actual365Fixed()
                                                  )

effective_date = ql.Date(1,4,2023)                # 開始日
termination_date = calendar.advance(effective_date,
                                    5, ql.Years
                                    )             # 終了日
tenor = ql.Period(ql.Semiannual)                  # 間隔
termination_day_convention = ql.ModifiedFollowing # 終了日調整
date_generation = ql.DateGeneration.Forward       # 日付生成

tenor_fixed = ql.Period(ql.Semiannual)
schedule_fixed = ql.Schedule(effective_date, termination_date, 
                             tenor_fixed, calendar,
                             business_day_convention, termination_day_convention,
                             date_generation, end_of_month)

tenor_float = ql.Period(ql.Quarterly)
schedule_float = ql.Schedule (effective_date, termination_date, 
                              tenor_float, calendar,
                              business_day_convention, termination_day_convention,
                              date_generation, end_of_month)

fixed_rate = 0.5
day_counter = ql.Actual365Fixed() #日本円TIBOR
spread = 0

yts = ql.YieldTermStructureHandle(log_cubic_discount)
ibor_index = ql.Tibor(ql.Period(3, ql.Months), yts) #プロジェクションカーブ
ibor_index.addFixing(ql.Date(30, 3, 2023), 0.4/100) # 2023年3月30日のフィキシング

swap = ql.VanillaSwap(ql.VanillaSwap.Receiver, # 払いor受け
                              100,             # 元本
                              schedule_fixed,  # 固定のスケジュール
                              fixed_rate/100,  # 固定金利
                              day_counter, 
                              schedule_float,
                              ibor_index,
                              spread,
                              ibor_index.dayCounter()
                              )

swap_engine = ql.DiscountingSwapEngine(yts)
swap.setPricingEngine(swap_engine)

bermudan_dates = [ _ for _ in schedule_fixed][:-1]
bermudan_exercise = ql.BermudanExercise(bermudan_dates)

black_engine = ql.BlackSwaptionEngine(yts,
                                      ql.QuoteHandle(ql.SimpleQuote(0.55)),
                                      day_counter
                                      )
swaption = ql.Swaption(swap, bermudan_exercise)
swaption.setPricingEngine(black_engine)

以上のソースコードをいくつかに分解してそれぞれ解説していく.

1-1. ライブラリのインポート

途中式を出力するためにpandasを合わせてインポートする.

import pandas as pd
import QuantLib as ql

1-2. 評価日の設定

eval_date = ql.Date(31,3,2023)
ql.Settings.instance().evaluationDate = eval_date

1-3. 決済周りの設定

決済日を2パターン設定し,前者はOISについて,後者はTIBORについて用いる.
祝休日として日本カレンダーを設定する.
日本カレンダーにおいて利払日が非営業日となる場合,次の営業日に利払日を変更させる.ただし次の営業日が月をまたぐ場合は前の営業日に利払日を変更させるものとする.これらのルールは利払日が月末であっても例外なく適用する.

settle_days_over_night = 0                      # 決済日
settle_days_spot_next = 2                       # 決済日
calendar = ql.Japan()                           # 祝休日
business_day_convention = ql.ModifiedFollowing  # 営業日調整
end_of_month = False                            # 月末日ロール

1-4. ブートストラップ

預金と金利先渡とスワップのパーレートからDFカーブを引く.

instruments = [
    # 商品    年限 レート
    ('depo', '1M', 0.1), # 預金
    ('depo', '3M', 0.1),
    ('depo', '6M', 0.2),
    ('fra',  '6M', 0.2), # 金先
    ('ois',  '1Y', 0.2), # スワップ
    ('ois',  '2Y', 0.3),
    ('ois',  '3Y', 0.4),
    ('ois',  '5Y', 0.5)
]

overnight_index_tona = ql.OvernightIndex('TONA',                 # インデックスの名前
                                         settle_days_over_night, # 決済日
                                         ql.JPYCurrency(),       # 通貨
                                         ql.Japan(),             # 休祝日カレンダー
                                         ql.Actual365Fixed()     #利息付利ルール
                                         )

helpers = ql.RateHelperVector()
for instrument, tenor, rate in instruments:    
    if instrument == 'depo':
        helpers.append(
            ql.DepositRateHelper(
                rate/100.0,
                ql.Period(tenor),
                settle_days_spot_next,
                calendar,
                business_day_convention,
                end_of_month,
                ql.Actual360()
                )
            )
    if instrument == 'fra':
        months_to_start = ql.Period(tenor).length()
        ibor_index = ql.Tibor(ql.Period('3M'))
        # ibor_index = ql.Euribor6M(yts)
        helpers.append(
            ql.FraRateHelper(
                rate/100.0,
                months_to_start,
                ibor_index
                )
            )
    if instrument == 'ois':
        helpers.append(
            ql.OISRateHelper(
                settle_days_spot_next,
                ql.Period(tenor),
                ql.QuoteHandle(ql.SimpleQuote(rate/100.0)),
                overnight_index_tona,                        # iborIndex
                paymentLag = 2,                              # 複利後決めのPayment Delay
                paymentConvention = business_day_convention,
                paymentCalendar = ql.Japan()
                    )
            )

log_cubic_discount = ql.PiecewiseLogCubicDiscount(settle_days_spot_next,
                                                  ql.Japan(),
                                                  helpers,
                                                  ql.Actual365Fixed()
                                                  )

引いたDFカーブをpandasのDataFrame形式で確認.

print(pd.DataFrame(log_cubic_discount.nodes(), columns=['スポット日', 'DF']))

1-5. 原資産のスワップの定義

固定レグと変動レグのそれぞれについて利払スケジュールを定める.

effective_date = ql.Date(1,4,2023)                # 開始日
termination_date = calendar.advance(effective_date,
                                    5, ql.Years
                                    )             # 終了日
tenor = ql.Period(ql.Semiannual)                  # 間隔
termination_day_convention = ql.ModifiedFollowing # 終了日調整
date_generation = ql.DateGeneration.Forward       # 日付生成

tenor_fixed = ql.Period(ql.Semiannual)
schedule_fixed = ql.Schedule(effective_date, termination_date, 
                             tenor_fixed, calendar,
                             business_day_convention, termination_day_convention,
                             date_generation, end_of_month)

tenor_float = ql.Period(ql.Quarterly)
schedule_float = ql.Schedule (effective_date, termination_date, 
                              tenor_float, calendar,
                              business_day_convention, termination_day_convention,
                              date_generation, end_of_month)

固定レグの利払日を確認.

print(pd.DataFrame([d for _, d in enumerate(schedule_fixed)][1:], columns=['固定利払い日']))

変動レグの利払日を確認.

print(pd.DataFrame([d for _, d in enumerate(schedule_float)][1:], columns=['変動利払い日']))

変動レグの参照金利を定義.

fixed_rate = 0.5
day_counter = ql.Actual365Fixed() #日本円TIBOR
spread = 0

yts = ql.YieldTermStructureHandle(log_cubic_discount)
ibor_index = ql.Tibor(ql.Period(3, ql.Months), yts) #プロジェクションカーブ
ibor_index.addFixing(ql.Date(30, 3, 2023), 0.4/100) # 2023年3月30日のフィキシング

swap = ql.VanillaSwap(ql.VanillaSwap.Receiver, # 払いor受け
                              100,             # 元本
                              schedule_fixed,  # 固定のスケジュール
                              fixed_rate/100,  # 固定金利
                              day_counter, 
                              schedule_float,
                              ibor_index,
                              spread,
                              ibor_index.dayCounter()
                              )

固定レグの利息を確認.

print(
    pd.DataFrame(
        [(cf.date(), cf.amount()) for _, cf in enumerate(swap.leg(0))],
        columns=['利払い日', '固定利息']
        )
    )

変動レグの利息を確認.

print(
    pd.DataFrame(
        [(cf.date(), cf.amount()) for _, cf in enumerate(swap.leg(1))],
        columns=['利払い日', '変動利息']
        )
    )

1-3.で定義したスポットカーブを用いて時価評価する.

swap_engine = ql.DiscountingSwapEngine(yts)
swap.setPricingEngine(swap_engine)

プライスと無裁定レートを確認.

print(f'スワップのプライスは{swap.NPV():.3f}')
print(f'無裁定レートは{swap.fairRate():.2%}')

1-6. スワップションを定義

固定金利の利払日(6ヶ月ごと)を行使日とする.ただし満期日は除く.

bermudan_dates = [_ for _ in schedule_fixed][:-1]
bermudan_exercise = ql.BermudanExercise(bermudan_dates)

行使日を確認.

print(pd.DataFrame(bermudan_dates, columns=['行使日']))

スワップションをブラックモデルで時価評価.

black_engine = ql.BlackSwaptionEngine(yts,
                                      ql.QuoteHandle(ql.SimpleQuote(0.55)),
                                      day_counter
                                      )
swaption = ql.Swaption(swap, bermudan_exercise)
swaption.setPricingEngine(black_engine)

プライスを確認.

print(f'スワップションのプライスは{swaption.NPV():.3f}')

2. おまけ

スワップは期中評価に対応しているがスワップションは対応していないようだ.

new_eval_date = ql.Date(31,3,2024)
ql.Settings.instance().evaluationDate = new_eval_date
ibor_index.addFixing(ql.Date(28,3,2024), 0.6/100) # 3月29日のフィキシング

print(f'スワップのプライスは{swap.NPV():.3f}')
print(f'無裁定レートは{swap.fairRate():.2%}')
swaption.setPricingEngine(black_engine)
print(f'スワップションのプライスは{swaption.NPV():.3f}')
# ERROR: date (April 3rd, 2023) before reference date (March 31st, 2024)

-1. おわりに

以上で見たようにQuantLibは簡単なコードでスワップションのような複雑な商品の時価評価ができる便利なものである.それにも関わらずWeb上の文献数は少なく,これから普及していく段階にあるように思える.拙文ながら本記事がQuantLib普及の一助となれば幸いである.

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1