0. はじめに
0-1. この記事を読めば
- わかる
- TIBORスワップの商品性
- そもそもTIBORとは
- TIBORスワップの評価(手計算)
- TIBORスワップの評価(QuantLib-Python)
- QuantLib-Pythonによる評価の検証
- わからない(書ききれなかったこと)
- スワップの歴史
- 中央清算機関での清算の詳細
- ヘッジ会計など
0-2. 誰のための記事か
マーケット部門の従事者で1~2年目の方を読者として想定している.マルチンゲール・アプローチについて知っていると詳細な議論が終えるようになっている.マルチンゲール・アプローチの参考文献としては村上が挙げられる.
0-3. 金利スワップとは
スワップについては以下のような説明がある1.
「スワップ取引」とは,将来のキャッシュ・フローを交換する取引である.2当事者間の相対取引(OTC:Over The Counter)であり,お互いに取引相手の信用リスクを負うが,合意にさえ至ればどのようなキャッシュ・フローもスワップ取引の対象となりうる.
さらに金利スワップについても説明が続く.
同種通貨のキャッシュ・フローの交換取引が金利スワップである.(中略)最も典型的な金利スワップは固定金利(fixed rate)と変動金利(floating rate)を交換するスワップである.
以下では金利スワップを考えていく.固定金利を受ける(resp. 払う)側をレシーバー(resp. ペイヤー)といい,このポジションを組むことを金利スワップをレシーブ(resp. ペイ)するという.
0-4. QuantLib-Pythonとは
QuantLib-PythonとはQuantLibというC++で書かれた「金融商品の時価評価ライブラリ」をPythonから呼び出すためのラッパーである.QuantLibではスワップやオプションといった金融商品の時価を計算することができ,それを手軽にPythonから呼び出せるQuantLib-Python(以下これを単にQuantLibと呼ぶ)は大変便利なものである.詳細は公式HPへ.
0-5. 前提条件
- 計算環境
- Windows 11
- Python 3.12
- pandas 2-2-2
- QuantLib 1.33
- 数理ファイナンスの知識
- 割引キャッシュ・フロー法による現在価値の考え方
- (マルチンゲール・アプローチの考え方)
1. TIBORスワップ
1-1. 商品性
TIBORスワップは金利スワップの1つで,変動金利としてTIBOR(1-2. TIBOR)を参照するものを指す.以下,これを単にスワップと呼ぶ.
TIBORスワップはCCPを通じて信用リスクを乗せないフェアなプライシングがされていると解釈できる2.
現在はほとんどの標準化されたデリバティブについては中央清算機関(CCP)を通じて清算されることが義務づけられている.これは,そのようなデリバティブが取引所で取引される先物と同じように扱われることを意味する.
これについては3にも記載がある.
「スワップレートは観察可能である」とよくいわれるが,このことの意味は,スワップが十分な流動性をもって市場で取引されているため,情報ベンダーが情報端末で提供する以下のような画面で容易に市場の気配値を得ることができるという意味である.
X年X月X日 | 円/円スワップレート(ABC銀行) |
---|---|
offer - bid | |
1年 | 0.12 - 0.08 |
2年 | 0.17 - 0.13 |
3年 | 0.25 - 0.21 |
4年 | 0.37 - 0.33 |
5年 | 0.52 - 0.48 |
6年 | 0.70 - 0.66 |
7年 | 0.88 - 0.84 |
8年 | 0.90 - 0.86 |
9年 | 1.14 - 1.10 |
10年 | 1.36 - 1.32 |
ここでTIBORスワップの具体例を挙げる4.TIBORスワップはこのような条件で取引される.
日本円TIBORスワップ | |
---|---|
通貨 | 日本円 |
想定元本 | 13億円 |
期間 | 2024年10月15日~2026年10月15日 |
支払日 | 2025年4月15日を第1回とし,以降毎年期限までの4月15日と10月15日 Modified Following Business Day Convention |
営業日 | 東京 |
金利 | 固定金利 2.00%(Actual/365 (Fixed),半年後払い) 変動金利 6ヶ月TIBOR(Actual/365 (Fixed),半年後払い) |
TIBOR | 金利計算期間の初日の2東京営業日前に発表される6ヶ月日本円TIBOR |
1-2. TIBOR
変動金利として参照するTIBOR(東京銀行間取引金利,Tokyo InterBank Offered Rate)について基本的な事項をまとめる.TIBORについては5に説明がある.
TIBORは短期金融市場の1つであるインターバンク市場の取引の実勢を反映する円の金利指標で,現在ではわが国の金融機関が実際の国内ローンに利用している金利形態である.
TIBORの数理モデルについてまとめる.
一般に金利にはスポット・イールドとフォワード・イールドがあり,TIBORについてそれぞれを$Y(t,t+\Delta)$と$Y(t,T,T+\Delta)$で表す.ただし前者は観測時点$t$における足許(スポット)時点$t$から時点$t+\Delta$までの間にかかる金利(年率)を,後者は観測時点$t$における将来(フォワード)時点$T$から時点$T+\Delta$までの間にかかる金利(年率)を指す.定義から
\begin{align*}
Y(t,t+\Delta) = Y(t,t,t+\Delta)
\end{align*}
となっている.LIBORは商慣習として連続複利ではなく(半年)単利で計算されるため,満期が$T$時点の割引債の$t$時点で観測した価格$P(t,T)$を用いて
\begin{align*}
Y(t,t+\Delta) & = \frac{1}{\Delta} \frac{1 - P(t,t+\Delta)}{P(t,t+\Delta)}, \\
Y(t,T,T+\Delta) & = \frac{1}{\Delta} \frac{P(t,T) - P(t,T+\Delta)}{P(t,T+\Delta)}
\end{align*}
と表される.
割引債の価格からTIBORを導いている,すなわちTIBORを割引金利として用いていることに注意されたい.いわゆるフォワードと割引で同じカーブを用いることがTIBORスワップの評価の肝である.
1-3. フェア・レート
相対で取引するスワップでは需給によってスワップ・レートが変わってくる.しかし無裁定な市場を仮定したときの,レシーバーとペイヤーが公平な固定金利の水準を考えることができ,これをフェア・レートと言う.このレートを導出することをスワップを評価するという.
ここでは具体的に以下の条件で金利スワップをレシーブしたとする.
レシーバー | |
---|---|
想定元本 | 1(単位円) |
受払日 | 半年ごとにN年間 |
固定金利 | S |
変動金利 | $Y(T,T+\text{半年})$ |
(イメージ図)t=0時点では受け渡しは発生しない.(→の先が×になっている)
このとき実際のスワップレート$S$とは別に理論的に導出されるフェア・レート$R$は
\begin{align*}
R = \frac{2 \times (1-P(0,N))}{P(0,\text{半年})+P(0,\text{1年})+\cdots+P(0,N)}
\end{align*}
と表される.
フェア・レートは足許の割引カーブから計算できる.
需給によってフェア・レート$R$とマーケットのスワップ・レート$S$の大小関係は決まってくる.
以下,導出を2つの形で述べるが適宜省略されたい.どちらも上式の評価式が導出される.
マルチンゲール・アプローチによる導出
固定(変動)金利のキャッシュ・フローをまとめて固定(変動)レグと呼ぶことにする.取引が成立している事実から当事者間の期待利得は等しいといえる.すなわち取引時点に限れば固定レグと変動レグの現在価値は等しくなっている.その時の$R$を求めるため,まずは固定レグの将来$t$時点の現在価値を計算していく.
固定レグは一定の金額を定期的に支払うキャッシュ・フローという意味でAnnuityの一例である.その現在価値$PV_{\mathrm{固}}(t)$は半年ごとに受け取る$R/2$を割り引いて
\begin{align*}
PV_{\mathrm{固}}(t) & = \left(\frac{R}{2}, \frac{R}{2}, \ldots , \frac{R}{2}\right) \cdot \underset{=:\mathrm{Annuity}(t)}{\underline{{}^t\! \left(P(t,\lfloor t \rfloor), P(t,\lfloor t \rfloor+\text{半年}), \ldots, P(t,N)\right)}} \\
& = \left(\frac{R}{2}, \frac{R}{2}, \ldots , \frac{R}{2}\right) \cdot \mathrm{Annuity}(t) \\
& = \frac{R}{2} \times \mathrm{sum}(\mathrm{Annuity}(t))
\end{align*}
と計算できる.ただし$\lfloor t \rfloor$は半年単位で見た$t$の次の時点を表す.(例:$t$が9ヶ月後なら$\lfloor t \rfloor$は1年後.)実務上は$\mathrm{Annuity}(t)$をあらかじめ計算しておくことも多い.
一方で変動レグの現在価値$PV_{\mathrm{変}}$を計算することは難しい.それは将来の利払日$(T+\text{半年})$-時点において支払う金利$Y(T,T+\Delta)$が$T$-時点にならないと確定せず,したがってペイオフも同様に不確定であることによる.
しかし無裁定という仮定を用いれば変動レグの現在価値はマルチンゲール・アプローチから計算できる.このとき将来$T$時点のスポットLIBOR$L(T,T+\Delta)$はあたかも足許$t$時点のフォワードLIBOR$L(t,T,T+\Delta)$であるかのように計算される,
【仮定】市場は無裁定である.
変動レグの現在価値はスポットTIBORを用いて
\begin{align*}
PV_{\mathrm{変}}(t)
= \left(
\frac{Y(\lfloor t \rfloor-\text{半年}, \lfloor t \rfloor)}{2},
\ldots,
\frac{Y(N-\text{半年}, N)}{2}\right)
\cdot \mathrm{Annuity}(t)
\end{align*}
と表される.この右辺を計算していく.
ここである測度(具体的にはフォワード・メジャー)が存在して,すべての年限の割引債の価格がその測度のもとでマルチンゲールとなるのであった(マルチンゲール・アプローチ).
$\lfloor t \rfloor$-時点のペイオフのみを考えた時,その現在価値は
\begin{align*}
E\left[\frac{Y(\lfloor t \rfloor-\text{半年}, \lfloor t \rfloor)}{2}\times P(t,\lfloor t \rfloor) \middle | \mathcal{F}_t \right]
\end{align*}
である.これを以下のように計算していく.
\begin{align*}
& E\left[\frac{Y(\lfloor t \rfloor-\text{半年}, \lfloor t \rfloor)}{2}\times P(t,\lfloor t \rfloor) \middle | \mathcal{F}_t \right] \\
& = E\left[\frac{1}{\text{半年}} \frac{P(\lfloor t \rfloor-\text{半年},\lfloor t \rfloor-\text{半年}) - P(\lfloor t \rfloor-\text{半年},\lfloor t \rfloor)}{P(\lfloor t \rfloor-\text{半年},\lfloor t \rfloor)}\times P(t,\lfloor t \rfloor) \middle | \mathcal{F}_t \right] \\
& = 2 \times (P(t,\lfloor t \rfloor-\text{半年}) - P(t,\lfloor t \rfloor))
\end{align*}
それぞれを$ 0 \le (\lfloor t \rfloor-\text{半年}) < N $について和を取って
\begin{align*}
& PV_{\mathrm{変}}(t) \\
& = 2\times \{(P(t,\lfloor t \rfloor-\text{半年}) - P(t,\lfloor t \rfloor)) + (P(t,\lfloor t \rfloor) - P(t,\lfloor t \rfloor + \text{半年})) \\
& \quad \ + \cdots + (P(t,N-\text{半年}) - P(t,N))\} \\
& = 2 \times (P(t,\lfloor t \rfloor-\text{半年}) - P(t,N))
\end{align*}
を得る.取組時点($t$=0)の現在価値が固定レグと変動レグで等しいことから
\begin{align*}
\frac{R}{2} \times \mathrm{sum}(\mathrm{Annuity}(0)) = PV_{\text{固}}(0) = PV_{\text{変}}(0) = P(0,0) - P(0,N)
\end{align*}
を解いて
\begin{align*}
R & = \frac{2 \times (P(0,0) - P(0,N))}{\mathrm{sum}(\mathrm{Annuity}(0))} \\
& = \frac{2 \times (1-P(0,N))}{P(0,\text{半年})+P(0,\text{1年})+\cdots+P(0,N)}
\end{align*}
が導かれる.
複製ポートフォリオによる導出
無裁定スワップレートが上で与えられるとしたとき,それをもとにした固定レグの現在価値は$1-P(0,N)$となる.これを元手に変動レグの支払いを複製できる.
時点 | 受取 | 支払 | 受取-支払 |
---|---|---|---|
0 | $1-P(0,N)$ | 1 TIBORに投資 - P(0,N) 割引債に投資 | 0 |
1/2 | $1+Y(0,1/2)/2$ 元本償還+利息 | 1 TIBORに再投資 | $Y(0,1/2)/2$ |
1 | $1+Y(1/2,1)/2$ 元本償還+利息 | 1 TIBORに再投資 | $Y(1/2,1)/2$ |
… | … | … | … |
T+半年 | $1+Y(T,T+1/2)/2$ 元本償還+利息 | 1 TIBORに再投資 | $Y(T,T+1/2)/2$ |
… | … | … | … |
N | $1+Y(N-1/2,N)/2$ 元本償還+利息 | 1 割引債償還 | $Y(N-1/2,N)/2$ |
2. QuantLib-Pythonを用いた評価
2-1. 商品
1-1. 商品性で例として挙げた商品を考える(再掲).
日本円TIBORスワップ | |
---|---|
通貨 | 日本円 |
想定元本 | 13億円 |
期間 | 2024年10月15日~2026年10月15日 |
支払日 | 2025年4月15日を第1回とし,以降毎年期限までの4月15日と10月15日 Modified Following Business Day Convention |
営業日 | 東京 |
金利 | 固定金利 2.00%(Actual/365 (Fixed),半年後払い) 変動金利 6ヶ月TIBOR(Actual/365 (Fixed),半年後払い) |
TIBOR | 金利計算期間の初日の2東京営業日前に発表される6ヶ月日本円TIBOR |
2-2. フェア・レート
プロット用のpandas含めてライブラリをインポートする.
import QuantLib as ql
import pandas as pd
評価日を設定し,時間計算の条件を決める.
today = ql.Date(15,10,2024)
ql.Settings.instance().evaluationDate = today
tenor = ql.Period(ql.Semiannual) # 利払間隔
calendar = ql.Japan() # 祝休日
business_day_convention = ql.ModifiedFollowing # 営業日調整
day_count = ql.Actual365Fixed() # 日数計算法
rule = ql.DateGeneration.Backward # 日付生成のルール
end_of_month = False # 月末日ロール
settle_days_spot_next = ql.Period('2D') # スワップのラグ
ゼロ・イールドから割引カーブを引く.ql.Periodクラスは掛け算ができるので楽をする.
zero_yields = [
(today, 0.010), # today
(today + tenor, 0.015), # 半年
(today + 2*tenor, 0.018), # 1年
(today + 3*tenor, 0.019), # 1年半
(today + 4*tenor, 0.020) # 2年
]
dates, zero_rates = zip(*zero_yields)
zero_curve = ql.ZeroCurve(dates, zero_rates, day_count) # ゼロ・カーブ生成
discount_curve = ql.DiscountCurve(
dates,
[zero_curve.discount(date) for date in dates],
day_count,
calendar
) # 割引カーブ生成
display(
pd.DataFrame([(d, discount_curve.discount(d)) for d in dates]
, columns=['date', 'df']
).round(3)
)
discount_curve.enableExtrapolation() # グリッド外を補間
display
の結果はこのようになる.
date | df |
---|---|
October 15th, 2024 | 1.000 |
April 15th, 2025 | 0.993 |
October 15th, 2025 | 0.982 |
April 15th, 2026 | 0.972 |
October 15th, 2026 | 0.961 |
1-3. フェア・レートで求めた価格式を用いればフェア・レートは次のように計算できる.
\begin{align*}
R = \frac{2 \times (1-0.961)}{0.993+0.982+0.972+0.961} = 2.0070\ \%
\end{align*}
実際に計算したのが以下である.複雑に見えるが上の計算と照らし合わせて理解されたい.
annuity = [discount_curve.nodes()[_][1]
for _ in range(1, len(discount_curve.nodes()))
]
fair_rate = (
2 * (1-discount_curve.nodes()[-1][1]) /
sum(annuity)
)
print(f'フェア・レートは{fair_rate:.4%}') # フェア・レートは2.0070%
次にQuantLIbのvannilaSwapクラスを用いてフェア・レートを計算する.
まずTIBORのフォワードカーブを引く.1-3. フェア・レートで述べたようにフォワードと割引で同じカーブを用いる.また2営業日前のフィキシング・レートを設定しておく.これがなければ最初の受払におけるTIBORの参照ができない.ここでは適当に固定レート合わせて2%とする.
正しいフィキシングの計算方法は3. QuantLib-Pythonによるプライシングの検証を参照.
discount_curve_handle = ql.YieldTermStructureHandle(discount_curve)
tibor = ql.Tibor(tenor, discount_curve_handle)
tibor.clearFixings()
tibor.addFixing(calendar.advance(today, - settle_days_spot_next), 0.02)
スワップの設定を決めていく.ここで2-1. 商品の設定を反映する.
type = ql.VanillaSwap.Receiver
nominal = 1300000000
schedule = ql.Schedule(today, # 開始日
today + 4*tenor, # 終了日
tenor,
calendar,
business_day_convention,
business_day_convention,
rule,
end_of_month
)
print(schedule.dates())
# Date(15,4,2025), Date(15,10,2025), Date(15,4,2026), Date(15,10,2026))
fixed_schedule = float_schedule = schedule
fixed_rate = 2 # %単位
spread = 0
swap = ql.VanillaSwap(type, nominal,
fixed_schedule, fixed_rate/100, day_count,
float_schedule, tibor, spread, tibor.dayCounter()
)
スワップの情報が想定通りであるか確認する.
assert swap.type() == type
assert swap.nominal() == nominal
assert [d for d in swap.fixedSchedule()] == list(schedule.dates())
assert swap.fixedDayCount() == day_count
assert swap.fixedRate() == fixed_rate/100
assert [d for d in swap.floatingSchedule()] == list(schedule.dates())
assert swap.spread() == spread
assert swap.floatingDayCount() == tibor.dayCounter()
assert swap.startDate() == schedule.startDate() # 2024年10月15日
assert swap.maturityDate() == schedule.endDate() # 2026年10月15日
価格付けエンジンを定め,フェア・レートを算出し価格式での導出と比較する.誤差は5%程であり,ある程度大きいと判断できる.
engine = ql.DiscountingSwapEngine(discount_curve_handle)
swap.setPricingEngine(engine)
print(f'フェアレートは{swap.fairRate():.4%}') # フェア・レートは2.1322%
print(f'差分は{fair_rate - swap.fairRate():.4%}') # 差分は-0.1253% (-12.53bp)
3. QuantLib-Pythonによるプライシングの検証
誤差が大きいためキャッシュ・フローとNPVを確認する.キャッシュ・フローに違和感はなく,NPVについても最初の割引カーブに沿って計算されていることがわかる.
df_fixed_cf = pd.DataFrame([(cf.date(), cf.amount())for cf in swap.fixedLeg()],
columns=['date', 'fixed_cf']
)
df_float_cf = pd.DataFrame([(cf.date(), cf.amount())for cf in swap.floatingLeg()],
columns=['date', 'float_cf']
)
display(
pd.merge(df_fixed_cf, df_float_Cf, on = 'date').round(-3)
)
assert (swap.floatingLegNPV() ==
-1 * df_float_cf['float_cf'].dot(pd.Series(annuity)))
display
の結果はこのようになる.
date | fixed_cf | float_cf |
---|---|---|
April 15th, 2025 | 12964000.0 | 12964000.0 |
October 15th, 2025 | 13036000.0 | 13749000.0 |
April 15th, 2026 | 12964000.0 | 13688000.0 |
October 15th, 2026 | 13036000.0 | 15071000.0 |
スケジュールは毎半年の15日に立っているためどうやらフィキシング周りで誤差が出ていると考えられる.実際,フィキシング日を確認すると
display(
pd.DataFrame([{
'fixingDate': cf.fixingDate().ISO(),
'accrualStart': cf.accrualStartDate().ISO(),
'accrualEnd': cf.accrualEndDate().ISO(),
'paymentDate': cf.date().ISO(),
'fixing/forward': cf.indexFixing(),
'rate': cf.rate(),
'amount': cf.amount()
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1))]).round({'amount':-3})
)
フィキシング日が2営業日前にずれている.
fixingDate | accrualStart | accrualEnd | paymentDate | fixing/forward | rate | amount |
---|---|---|---|---|---|---|
2024-10-10 | 2024-10-15 | 2025-04-15 | 2025-04-15 | 0.020000 | 0.020000 | 12964000.0 |
2025-04-11 | 2025-04-15 | 2025-10-15 | 2025-10-15 | 0.021094 | 0.021094 | 13749000.0 |
2025-10-10 | 2025-10-15 | 2026-04-15 | 2026-04-15 | 0.021116 | 0.021116 | 13688000.0 |
2026-04-13 | 2026-04-15 | 2026-10-15 | 2026-10-15 | 0.023122 | 0.023122 | 15071000.0 |
ここから1-3. フェア・レートで求めた価格式とQuantLibによる実装の違いは
- 最初のフィキシング・レートが2%である
- 決済日(settlement days)が2日後
という点であることが原因と考えられるため,これらを修正していく.そのために条件を満たすIborIndexクラスを作成していく.また先ほどのtiborをiborに変更したスワップを作成する.その他の引数は同じものを用いている.
ibor = ql.IborIndex('Ibor',
tenor,
0, # 決済日との日数の差
ql.JPYCurrency(), # 通貨
calendar,
ql.Unadjusted, # 営業日調整
end_of_month,
day_count,
discount_curve_handle
)
swap_ibor = ql.VanillaSwap(type, nominal,
fixed_schedule, fixed_rate/100, day_count,
float_schedule, ibor, spread, day_count
)
決済日とフィキシング日とのずれを0としている点に注意されたい.フィキシングの詳細を見ると,
display(
pd.DataFrame([{
'fixingDate': cf.fixingDate().ISO(),
'accrualStart': cf.accrualStartDate().ISO(),
'accrualEnd': cf.accrualEndDate().ISO(),
'paymentDate': cf.date().ISO(),
'fixing/forward': cf.indexFixing(),
'rate': cf.rate(),
'amount': cf.amount()
} for cf in map(ql.as_floating_rate_coupon, swap_ibor.leg(1))]).round({'amount':-3})
)
フィキシング日と決済日のずれがなくなっていることがわかる.
fixingDate | accrualStart | accrualEnd | paymentDate | fixing/forward | rate | amount |
---|---|---|---|---|---|---|
2024-10-15 | 2024-10-15 | 2025-04-15 | 2025-04-15 | 0.015056 | 0.015056 | 9760000.0 |
2025-04-15 | 2025-04-15 | 2025-10-15 | 2025-10-15 | 0.021094 | 0.021094 | 13749000.0 |
2025-10-15 | 2025-10-15 | 2026-04-15 | 2026-04-15 | 0.021116 | 0.021116 | 13688000.0 |
2026-04-15 | 2026-04-15 | 2026-10-15 | 2026-10-15 | 0.023122 | 0.023122 | 15071000.0 |
この新しいスワップについてフェア・レートを計算すると,
swap_ibor.setPricingEngine(engine)
print(f'フェアレートは{swap_ibor.fairRate():.4%}') # フェア・レートは2.0070%
print(f'差分は{fair_rate - swap_ibor.fairRate():.4%}') # 差分は-0.0000%
のように差分が0になった.
したがって最初のフェア・レートとのずれはフィキシング日とレートの違いによるものでることがわかった.
おまけ
プライス
スワップのプライスについては以下で求まる.
print(f'フェア・レートは{swap.NPV():.3e}') # レシーバー側のプライスは-3.359e+06
-1. おわりに
本記事は社内勉強会の資料を加筆・修正したものである.初学者の助けとなれば幸いである.
また本記事を執筆するにあたって以下の文献を参考にした.
杉本浩一, 福島良治, 若林公子 著 (2023年)『スワップ取引のすべて 第6版』 金融財政事情研究会
ジョン ハル 著ほか (2016年)『フィナンシャルエンジニアリング : デリバティブ取引とリスク管理の総体系 第9版』 金融財政事情研究会
木島正明 著 (1999年)『期間構造モデルと金利デリバティブ (シリーズ<現代金融工学> ; 3)』 朝倉書店
服部孝洋 著 (2023年) 『日本国債入門』 金融財政事情研究会
村上秀記 著 (2015年) 『マルチンゲールアプローチ入門 』 近代科学社