第1回の記事 日本国債の特異体質とJGBオブジェクト...の2節で作成したJGBクラスに対し、今回は、同クラスの使い勝手を向上させ、日本式複利のメソッドを実装するコードを紹介する。以下では次の3点を説明する。
- 第1回の2節で作成した単利用メソッドSimpleYield、SimplePriceの3つの引数を1つに減らす。
- その際に登場するデコレータ@ propertyの使い方を紹介
(デコレータの一般的な説明はテキスト36ページ 脚注15を参照) - QuantLib のように状態が動的に変わるオブジェクトでのメンバ変数(プロパティ)の注意点
- その際に登場するデコレータ@ propertyの使い方を紹介
- QuantLibのソルバーBrentクラスによる複利利回り算出のコード例
(テキスト229ページ、図7.5ではボラティリティをBrentクラスで算出)- 忘れがちなので、等比数列の公式から複利式の導出法もメモしておく
- JSDAが公表する売買参考統計値の複利利回りの再現法
1. JGBクラスの実行例
-
初めにコードの実行例を示そう。下図はJSDAが発表した2025年11月18日引けのマーケットデータ。(左上の2025年11月19日とは翌日に発表する日付を意味)

-
超長期国債101回(以下 JL101 )の単利が 0.925%、その価格
(クリーン価格)は 103.37円 と発表 (価格は100円単位で少数3桁目は切捨て) -
この計算の前提は受渡日が今日(11月18日)、つまりT+0で単利から価格を計算
-
複利 0.937% は価格103.37からT+0で算出 (複利は%単位で少数4桁目を切捨て)
-
「 市場建値の単利 $\rightarrow$ クリーン価格 $\rightarrow$ 複利 」の順に計算
-
この計算の中に経過利息の計算が無い為、"JSDAは複利をクリーン価格から算出し、日本式複利が生まれた" と筆者は邪推している
-
-
この計算を再現するコードと実行例が下図であり、出力された2列目は上図と同じ値が算出できている。
(このコードはテキストのサポートページのファイル名 JL101-2.ipynb)

-
出力例の3列目は切捨て前(No rounddown)の計算結果
-
この出力はprint文で作ったため、コード23行以下 11行もの行数が必要になった
-
本質的な計算は6行~21行で完了する非常にシンプルなコード
-
-
3番目に確認すべき点は、出力例の2行目と3行目で日本複利と欧米複利を計算したが、両者の差は0.15bp(=0.0015%)程度
-
第1回の記事で欧米の複利があれば、「日本式複利は無視」できるとした根拠
-
「差が小さいこと」は日本国債のデュレーション等の計算は第1回 1.4節で示したQuantLibの
BondFunctionsのメソッドをそのまま使っても問題ないことを意味
-
2. 実行例のコード概要
- #1. スケジュールオブジェクトと債券オブジェクト
- (1行) いつものようにmyABBRとmyUtilモジュールの読込み
(不慣れな読者はテキスト24ページ、1.4節参照) - (3, 4行)
-
tradeDT:第1節で記したように、JSDAがT+0の計算をしているため、tradeDTを前日に指定。(オブジェクトをT+0で作成してもよいが、JSDA専用オブジェクトとなり、不便) -
mPRC, mYLD:市場価格、市場利回り (mはmarketの略)
-
- (8, 9行)
JL101, JL101aという2つのオブジェクトを第1回 2節で用意したJGBクラスから作成(2つのオブジェクトを作成する理由は第1回記事を参照) - JGBクラスのコードはmyUtilモジュールの310行目以降で記述
- (1行) いつものようにmyABBRとmyUtilモジュールの読込み
- #2. 各種日数
- (12行)
advanceにより、受渡日を計算(テキスト3ページ、図1.4参照) - (13行)
previousDateにより、前回利払日を計算(テキスト115ページ、図4.3参照) - (14行) 閏年を考慮し、前回利払日からの経過日数を
dayCountにより算出し、accDS変数を設定(dcA365オブジェクトは第1回 (1.1)節を参照)
- (12行)
- #3. 利回り計算等
- (17, 18, 19行)
SimplePrice, SimpleYield, JapanCompoundYieldはJGBクラスで作成した3つのメソッド-
SimplePrice, SimpleYieldは第1回 2節で作成済みだが、引数を1つに修正 -
JapanCompoundYieldがJGBクラスに新たに追加した日本式複利を計算するメソッド。第3節で詳細を説明
-
- (20行)
bondYieldは欧米式複利で第1回 (1.2)節で説明済み - (21行) 第1回同様、JL101aオブジェクトで
accruedAmountを算出(第1回 (1.3)節を参照)
- (17, 18, 19行)
- #4. print文 (かなり見苦しいコードです。各print文で改行すると若干見やすくなります...)
3. JGBクラスのコード説明
ではmyUtilモジュールの310行目以下に記述されたJGBクラス(下図)の説明に移ろう。
-
まずは第1回 2節で作成したコードと比較した場合、310行~315行、323行~327行まではほぼ同じコードとなっていることを確認しよう。ただし 次の2点の修正が入っている。
-
(323~327行) SimplePriceとSimpleYieldの2つのメソッドの引数を1つに修正
-
第1回 2節にあったメソッドの引数
cpnRTとmatDSを無くし、クラス内で計算するようにしたため、2つのメソッドの計算式ではself.cpnRTとself.matDSに修正
-
-
(316行) メンバ変数としてself.cpnRTに債券クーポンの2.40%を設定。右辺のql.as_coupon(self.cashflows()[0]).rate() は若干複雑で以下簡単に説明する
(詳細はテキスト121ページ 図4.6の説明参照)-
(1) 債券のキャッシュフローをself.cashflows()で取得し、その最初のオブジェクトをスライス[0]で指定
-
(2) as_couponでキャストし、rateメソッドで債券のクーポンを取得
-
-
(319~321行) @ propertyでデコレートされたmatDSメソッドを設定
- この@ propertyでデコレートされたmatDSメソッドはメンバ変数のようにアクセス可能
- 例えば、324行目のようにself.matDSと記述。このデコレータの簡単な例は3.1節で説明
- コメントアウトした317行のように、self.cpnRTと同様な変数としてself.matDSとコーディングしない理由は3.2節を参照
-
(329~337行) 日本式複利を計算するJapanCompoundYieldメソッドを設定
- 筆者の理解では、JSDAの複利は微分計算が必要なニュートン法で求めているが、このメソッドは微分が不要なBrent法を利用
- Brentクラスの説明、コード記入法は
テキスト229ページ 図7.5を参照 - (331~335行) prSLVR(yld) 関数は複利yldを与えると、334行目で債券価格
PRCを算出し、335行目でメソッドの引数のクリーン価格clnPRとの差を戻すテキスト図7.5のvolSLVR関数と同じ役割 - (337行) Brentクラスのsolveメソッドの第1引数にこのprSLVR関数を設定
- Brentクラスの説明、コード記入法は
- Brentクラスの計算手順のイメージ
- (1) solveメソッドの第3引数はprSLVR関数に
yldとして与えられる最初の値- この初期値は331行で計算した単利
sYLDとしている - 単利と複利が近い値のため、初期値を単利としている
- この初期値は331行で計算した単利
- (2)
sYLDの値がprSLVR関数に与えられ、335行目からPRC-clnPRが戻される - (3) 戻された値が
1e-6より大きければ、Brent法によって、次のyldが計算され、再度prSLVR関数を実行- 戻された値が
1e-6以下なら、prSLVR関数に与えられたyldがBrent法で見つかった複利となる
- 戻された値が
- (1) solveメソッドの第3引数はprSLVR関数に
- 筆者の理解では、JSDAの複利は微分計算が必要なニュートン法で求めているが、このメソッドは微分が不要なBrent法を利用
3.1 @ propertyの簡単な例
-
上コードの説明
- (1~3行) クラスAのメソッドとして、@ propertyでデコレートされたxを定義
- このメソッドxは定数123を戻す
- (5行) クラスAのオブジェクトaOBJを作成
- (6行) xメソッドを呼び出す方法はメンバ変数のようにaOBJ.xでアクセス
6行目のprint文から123を出力
-
つまり、@ propertyはメソッドをメンバ変数のようにするデコレータ
-
Pythonではこのデコレータで書かれたメソッドのみをプロパティと呼ぶ
(テキストのプロパティの定義が誤っているため、第2刷で修正予定)
3.2 self.matDSをメンバ変数としない理由
-
JGBクラスで作成されるオブジェクトは実行例のコード12行目
setEvDT(tradeDT)の指定によって、クラスのコード321行目のself.settlementDate()が動的に変化するオブジェクトとなっている -
もし、317行のコメント行のようにオブジェクト作成時にメンバ変数としてself.matDSを設定すると、オブジェクトの外で
setEvDT(tradeDT+1)等の変更がなされても、self.matDSの値は古いまま -
matDSを更新させるため、敢えてメソッドとして
def matDS(self)を定義し、matDSが呼び出される度にdayCount(self.settlementDate(), ...)を計算させている -
@ propertyのデコレートは、matDSメソッドへのアクセスをメンバ変数風にしているお飾り
3.3 等比数列の公式を利用した複利式
まだクラスのコードの332~334行を説明していない。この3行のコードは第1回 3節で紹介した日本式複利を記述している。以下ではテキスト4.2節で示した欧米式複利を等比数列の公式によって変形し、日本式複利に似た式を導出する。
両者を比較することで、日本式複利が正しいIRR(内部収益率)を算出していないことが明らかになる。
- 第1回で示した複利式を年1回利払い
(p.a.と記す)に修正した次式の導出を示そう。
$$
{\small
\text{価格} \times (1 + y)^{\text{残存年数}} = \text{クーポン} \times \frac{(1 + y)^{\text{残存年数} } - 1}{y}+ 100\text{%}
}
$$ -
テキスト111ページ 式4.3では3年債 クーポン0.37% (p.a.)の債券が0.07年経過した複利式を次のように示した。
$$
{\small
97.0257円 \times (1+y)^{2.93}
=
0.37円\times (1+y)^{2}+0.37円\times (1+y)^{1}+0.37円+100円
}
$$- 残存年数:2.93年 (= 3年 - 0.07年)、 複利:$y$
- ダーティー価格:97.0257円 (=クリーン 97.00+ 経過利息 0.0257)
- この式を次の記号で書き換える。
- 経過年数:AY、残存年数:3-AY
(AYはaccrued yearの略) - クーポン:C(実数)、額面:1円(または 100%)
- ダーティー価格:Pd、クリーン価格:Pc
- これらの定義より、テキストの式は次式で表される
$$
{\small
Pd \times (1+y)^{3-AY}
=
C (1+y)^{2}+C(1+y)^{1}+C+100\text{%}
}
$$ - この式のクーポン部分
(右辺の100%を除いた部分)に次の等比数列の公式を当てはめよう(nは左辺の項数)
$$
\text{(等比数列の公式) }1+r+r^{2}+\cdots+r^{n-1}=\frac{1-r^{n}}{1-r}
$$ - まず、記号を軽くする為、$Y:=1+y$として、両辺を$Y^{3}$で割る
$$
\begin{aligned}
Pd \times Y^{-AY}
=C\left[Y^{-1}+Y^{-2}+Y^{-3}\right]+100\text{%}\times Y^{-3}
\end{aligned}
$$ - クーポン部分に対し、$Y^{-1}$を公比として等比数列の公式を使う。元本の100%は1円であり、省略
$$
\begin{split}
Pd \times Y^{-AY}
&=
C\times Y^{-1}\bigl[1+Y^{-1}+Y^{-2}\bigr]+ Y^{-3}
\\
&=
C\times Y^{-1}\frac{1-Y^{-3}}{1-Y^{-1}}+ Y^{-3}
=
C \frac{1-Y^{-3}}{Y-1}+Y^{-3}
\\
&=
\frac{C}{Y-1}+\left[1-\frac{C}{Y-1}\right] Y^{-3}
\end{split}
$$ - $Y$を$1+y$へ戻し、ダーティー価格を計算する次式を得る
$$
\begin{split}
Pd \times (1+y)^{-AY}
&=
\frac{C}{y}+\left[1-\frac{C}{y}\right] (1+y)^{-3}
\text{、 より}
\\
Pd
&=
\frac{C}{y}(1+y)^{AY}+\left[1-\frac{C}{y}\right] (1+y)^{-(3-AY)}
\end{split}
$$ - この式の右辺最後の項は複利$y$で計算した債券満期時点のディスカウントファクターとなっているので、次式に書き換える。この式を$\mathbf{Pd}$式と参照する。
$$
\begin{split}
DF&:=(1+y)^{-(3-AY)}
\\
Pd
&=
\frac{C}{y}(1+y)^{AY}+\left[1-\frac{C}{y}\right] DF
\end{split}
$$ (初回に投稿した記事は上式で計算をミスしていたため、修正した)
- 経過年数:AY、残存年数:3-AY
3.4 日本式複利の実装
-
3.3節の最初に示した日本式複利を上の記号で書き換える。
$$
\begin{split}
Pc \times DF^{-1}
&=
C\left[\frac{DF^{-1} - 1}{y}\right]+100\text{%}
=
\frac{C}{y}DF^{-1}+\left[1-\frac{C}{y}\right]
\end{split}
$$- 両辺にDFを掛けて、次式を得る (以下 $\mathbf{Pc}$式と参照)
$$
\begin{split}
Pc
&=
\frac{C}{y}+\left[1-\frac{C}{y}\right]DF
\end{split}
$$ - 上式を$Pd$式と比べた場合、次の2点が異なっている
- 左辺がダーティー価格とクリーン価格
- 右辺 第1項に$(1+y)^{AY}$が無い
- 両辺にDFを掛けて、次式を得る (以下 $\mathbf{Pc}$式と参照)
-
結論として、日本式複利=欧米式複利が成立するのはAY=0の時。つまり、利払日が受渡日の場合のみ、2つの複利は一致。
-
これらの理解を前提にクラスのコード332~334行に戻ろう。
- (332, 333行)
CyとDFは上式の$\frac{C}{y}$と$DF$ -
DFはmyABBRモジュールで定義されたfreqSA(=2)を使い、年2回払いに修正 (年2回払いの複利式は第1回 3節を参照) - (334行) 日本式複利の$Pc$式をコード化し、与えられた
yldから債券価格を計算。100倍している理由は額面100円に修正するため(算出される価格を100円単位にするため)
- (332, 333行)
-
この日本式複利を実装したJGBクラスは、第1節で示したように売買参考統計値を正確に再現出来ていることを思い出そう。
まとめ
- JGBクラスは「売買参考統計値」を正確に再現する
- 日本式複利と欧米式複利の差は大きくない。このことはQuantLibの
BondFunctionsのduration等のリスク関連メソッドをそのまま使っても問題ないことを示唆 - ただし、経過利息用のオブジェクトJL101aは相変わらず必要
- デコレータ@ propertyはメソッドをメンバ変数風にする
- 日本式複利を手計算するには、Brentクラスと$Pc$式を利用
- 上記コードはテキストのサポートページ より、取得可能。
(ファイル名はJL101-2.ipynb, myABBR.py, myUtil.py)
追記
次回は国内社債を予定。ただし、JGBクラスでは操作できない複雑な日付の作成が必要となり、コードが完成するにはかなりの日数が必要。最悪の場合、C++で新しい日数クラスを作成する?


