2. "Implementing QuantLib"の和訳
Chapter-VII The Tree Framework: Tree を使った価格モデルのフレームワーク
7.2 Tree および Tree-Based Lattice
7.2.3 TreeLatticeクラステンプレート
Treeクラスでかなり回り道をしましたが、再び Latticeクラスに戻りましょう。次の Listing 7.13 に示す、TreeLatticeクラスは、Lattice ベースクラスからの派生クラスで、1つあるいは複数の Tree からなる Lattice を実装しています。クラス名を TreeBasedLattice とすべきでしたが、開発者は、おそらく短い名前を好んだのでしょう。後で、すばらしいワープロの文書校正機能で、英文法的には TreeBasedLattice の方が良いと気づきました。
Listing 7.13:TreeをベースにしたLatticeクラスの実装
template <class Impl>
class TreeLattice : public Lattice, public CuriouslyRecurringTemplate<Impl> {
public:
TreeLattice(const TimeGrid& timeGrid, Size n);
void initialize(DiscretizedAsset& asset, Time t) const {
Size i = t_.index(t);
asset.time() = t;
asset.reset(this->impl().size(i));
}
void rollback(DiscretizedAsset& asset, Time to) const {
partialRollback(asset,to);
asset.adjustValues();
}
void partialRollback(DiscretizedAsset& asset, Time to) const {
Integer iFrom = Integer(t_.index(asset.time()));
Integer iTo = Integer(t_.index(to));
for (Integer i=iFrom-1; i>=iTo; --i) {
Array newValues(this->impl().size(i));
this->impl().stepback(i, asset.values(), newValues);
asset.time() = t_[i];
asset.values() = newValues;
if (i != iTo) // skip the very last adjustment
asset.adjustValues();
}
}
void stepback(Size i, const Array& values, Array& newValues) const {
for (Size j=0; j<this->impl().size(i); j++) {
Real value = 0.0;
for (Size l=0; l<n_; l++) {
value += this->impl().probability(i,j,l) * values[this->impl().descendant(i,j,l)];
}
value *= this->impl().discount(i,j);
newValues[j] = value;
}
}
Real presentValue(DiscretizedAsset& asset) const {
Size i = t_.index(asset.time());
return DotProduct(asset.values(), statePrices(i));
}
const Array& statePrices(Size i) const;
};
このクラステンプレートは、インターフェースを提供している Lattice クラスと、実装の為に使われる Tree クラステンプレートの間で接続点としての役割を果たしています。ここでも CRTP(Curiously Recurring Template Pattern) を使っています (Treeクラスではあえて必要無い設計手法でしたが、とりあえずそのままにしています)。Latticeベースクラスで宣言されたメソッドが、この派生クラスで、動作の実装がなされています。より汎用的な構造にする為、このTreeLatticeクラスの中ではTreeクラス(で宣言されたメソッド)は出てきません。どのようなTreeクラスを選択して、それをどのように使うかは、派生クラスに任されています。
このクラスのコンストラクターは、単純明快です。引数として TimeGrid と、Tree の分岐数 ( 2項Treeであれば 2、3項Treeであれば 3 という具合) を示す整数 n を取り、それぞれメンバー変数に保存します。また1~2点の事前チェックを行い、データを一時保管する為の2つのメンバー変数を初期化します。ここでは詳細な説明は飛ばします。
興味深い点は、Lattice クラスのメソッドの実装部分で、前のセクションで示した概要に従った動作を行います。initialize( )メソッドは、まず引数で受取った時間 t に対応するインデックス番号を、メンバー変数である TimeGridインスタンスから導出します。次に、asset.time( ) を設定し (訳注:DiscretizedAssetインスタンスの該当日を設定)、最後に Tree の Level(時間軸)に対応する Node数を、DiscretizedAssetの reset( )メソッドに渡します。Node数は CRTP を経由した(派生クラスの) size( )メソッドを呼び出して得ます。このメソッドは、派生クラスで実装されなければなりませんが、(他のCRTPで使われる同様のメソッドと同じように) Treeクラスにおける、同じ機能を持つメソッドと同じメソッド名にしています。
rollback( )と partialRollback( )は同じ動作を実行します。異なる点は、rollback( )メソッドは遡及した最後の時点で資産価格の調整を行うのに対し、partialRollback( )ではそれを行っていません。従って、前者のメソッドは後者のメソッドを使って実装できます。すなわち rollback( )は、まず (rollbackが必要な時間軸まで) partialRollback( )を実行し、その後で adjustValues( )メソッドを呼び出せばいいだけです。
資産価格を遡及していくプロセスは partialRollback( )メソッドのプログラムコードの中に記述されています。まず TimeGrid から、現時点(from)と、目標とする時点(to) のインデックス番号を見つけ、ループを使って現時点から目標点まで (離散時間をステップ毎に) 遡って行きます。次に、各ステップにおいて、stepback( )メソッドを呼び出します。このメソッドは、実際の数値計算を実装しており、Treeのレベル i+1 上の資産価格からレベル i 上の資産価格を計算します。その値を使って資産価格の値を更新し、最後のステップを除くすべてのステップで adjustValues( )メソッドを呼び出します。
stepback( )メソッドは、(CRTPを使って)派生クラスで実装されるインターフェースを使って実装されています。(まだ c++11 のコンセプトを導入していなかったので、この方法が唯一まともな方法でした。) このメソッドは各 Node の資産価格を、分岐先の Node の資産価格とその分岐先への遷移確率を使って計算します。その値を、さらにステップ期間に相当する割引率で調整します。これらの動作をすべて実行する為に必要なインターフェースは、既に説明した size( )メソッド、および probability( )と descendant( )メソッド (これらはTreeクラスが提供する、同じ機能を持つメソッドと、同じメソッド名です)、さらに discount( )メソッドです (Level と Node のインデックスから、その Node と分岐先間の割引率を返す)。 (注:割引率は分岐先に独立して決まるとの前提です)。
最後に presentValue( )メソッドは、資産価格の配列と TimeGrid上の時点の状態変数のドット積 (訳注:ベクトル同士の内積) を返すよう実装されています。状態変数の計算方法の説明は、あえて無視しましょう。これを使うと、資産価値の遡及計算を t=0 まで遡るより、遥かに効率的であるとだけ申し上げておきます。
さて、なぜ TreeLatticeクラスは、Treeインスタンスを保持してそのメソッドを呼び出すのではなく、Treeクラスのメソッドと同じ名前のメソッドを (わざわざ定義して、それを)呼び出す様にしているのでしょうか?(その結果、派生クラスで、それらのメソッドの実装が必要になります。) もし対象資産の Tree を 1つ持っているだけなら、Treeのメソッドを使うのは簡単な事です。実際に 1次元 Latticeクラスのメソッドの実装内容は、Treeインスタンスの同じ名前のメソッドを呼び出すだけです。しかし、これはその他の Latticeクラス (例えば2次元のもの) ではうまく動作しません。そういった場合、stepback( )メソッドの実装の中で使われている Wrapperメソッドを使う事によって、対象資産の Tree構造がどのような次元であっても、共通のインターフェースに適合させる事ができるのです。
QuantLibライブラリーでは、両方のタイプの Latticeクラスが用意されています。単純な方のグループのほとんどは TreeLattice1Dテンプレートクラスからの派生クラスで、その実装内容を Listing 7.14 に示します。このクラスは、TreeLatticeクラスで必要となるメソッドを何も実装していません。このクラスが実装している唯一のメソッド ( Latticeベースクラスの純粋仮想関数であるgrid( )メソッド) も、実際には他のメソッド (それは underlying( )メソッドです)を必要としています。このクラスは特定のカテゴリーのクラスをまとめる役割以外には、ほとんど何もしていません。対象資産のTreeインスタンスの保存と管理も、やはり派生クラスに任されています。
(注:TreeLattice1Dクラスで必要なメソッドの既定値を実装すべきではないかとの議論もあります。おそらく、そうする方がいいかもしれません。既定値を使える場合は、派生クラスでの実装が省けますし、特別な Latticeで必要な場合は、オーバーライドする事も可能でしょう。)
Listing 7.14:TreeLattice1Dクラス
template <class Impl>
class TreeLattice1D : public TreeLattice<Impl> {
public:
TreeLattice1D(const TimeGrid& timeGrid, Size n);
Disposable<Array> grid(Time t) const;
};
そのような派生クラスのひとつに内部クラスである OneFactorModel::ShortRateTree クラスがあり、その実装内容を下記 Listing 7.15 に示します。コンストラクターは、引数として、①特定の Short-Rate Model を使って構築された TrinomialTree のインスタンス、②ShortRateDynamicsクラスのインスタンス(ここでは説明を飛ばします)、および③TimeGridインスタンスを取ります。TimeGrid は Treeインスタンスから取りだせるので、なぜここで引数として渡す必要があったのか不明です。TimeGridインスタンスは、Treeの分岐数(当然3です)と共に、ベースクラスのコンストラクターに渡されます。Tree と Dynamics のインスタンスはメンバー変数に保存されます。
Listing 7.15:OneFactorModel::ShortRateTreeクラスの実装
class OneFactorModel::ShortRateTree
: public TreeLattice1D<OneFactorModel::ShortRateTree> {
public:
ShortRateTree(
const boost::shared_ptr<TrinomialTree>& tree,
const boost::shared_ptr<ShortRateDynamics>& dynamics,
const TimeGrid& timeGrid)
: TreeLattice1D<OneFactorModel::ShortRateTree>(timeGrid, 3),
tree_(tree), dynamics_(dynamics) {}
Size size(Size i) const {
return tree_->size(i);
}
Real underlying(Size i, Size index) const {
return tree_->underlying(i, index);
}
Size descendant(Size i, Size index, Size branch) const {
return tree_->descendant(i, index, branch);
}
Real probability(Size i, Size index, Size branch) const {
return tree_->probability(i, index, branch);
}
DiscountFactor discount(Size i, Size index) const {
Real x = tree_->underlying(i, index);
Rate r = dynamics_->shortRate(timeGrid()[i], x);
return std::exp(-r*timeGrid().dt(i));
}
private:
boost::shared_ptr<TrinomialTree> tree_;
boost::shared_ptr<ShortRateDynamics> dynamics_;
};
予想通り、必要なインターフェースの大半は、対応する Treeクラスのメソッドを呼び出す様に実装されています。ただ一つの例外は discount( )メソッドで、これは対応するメソッドが Treeクラスに無いからです。このメソッドの実装内容は、まず Treeクラスに対し、特定の Node における対象資産の価値を求め、それを使って Dynamicsクラスから短期金利を導出し、その Node から分岐先までの期間に対応する割引率を計算するようになっています。(訳注: Hull-Whiteモデルに代表される、金利の Term-Structure にフィットした短期金利の拡散過程を、3項Treeで表現する場合の、Hull-Whiteが提示したテクニックを思い出して下さい。そこでは、まず ドリフト=0、期待値=0 として、与えられた Volatilityの期間構造を使って状態変数の Tree構造を構築し、そこから Term-Structure にフィットするような短期金利の拡散過程を表現する 3項Tree に変換しています。ShortRateTreeクラスが、前者を表現し、ShortRateDynamicsクラスが、変換後の後者を表現しています。従って、ある Node から次のLevelまでの Discount Factor は、上記 Code にあるように、ShortRateDynamics に状態変数 x を与えて、それを短期金利に変換し、それを使って計算されています。ShortRateTreeクラスにおける underlying()は、英語の語感からくる‘対象資産’の値というより、‘状態変数’の値と訳した方が良かったかもしれません。)
注目して頂きたいのは、ShortRateDynamicsを使って、Treeの構造を変えることなく、各 Nodeにおける短期金利の値を変更する事が可能という事です。(訳注: ShortRateDynamics に与える中心回帰パラメータなどを動かして、金利の Term-Structure にフィットさせるアルゴリズムのこと) この動作は、Tree を現在の金利の TermStructure にフィットさせる為に、いくつかのモデルで行われています。ShortRateTree クラスは、この Listing では示していませんが、もうひとつのコンストラクターを用意しており、フィットさせる動作の為、追加のパラメータを引数として取るようになっています。
*******************************************************************************
もう一方のカテゴリーの Lattice の例として、次の Listing 7.16 に示す TreeLattice2D クラスを見て下さい。このクラスは2変数モデルに対応する Lattice のベースクラスとなり、TreeLatticeクラスで使われるメソッドの大半を実装しています。(この点は、そういったメソッドを全く実装していない TreeLattice1Dクラスとの相違点です。クラス階層の対称性を考えると、TreeLattice2Dクラスでもそういった実装をせず、派生クラスに実装を移してもよかったかも知れません。ただ、今の所、それは見栄えだけの為のようにも見えます)
Listing 7.16: TreeLattice2Dテンプレートクラスの実装
template <class Impl, class T = TrinomialTree>
class TreeLattice2D : public TreeLattice<Impl> {
public:
TreeLattice2D(const boost::shared_ptr<T>& tree1,
const boost::shared_ptr<T>& tree2,
Real correlation)
: TreeLattice<Impl>(tree1->timeGrid(), T::branches*T::branches),
tree1_(tree1), tree2_(tree2), m_(T::branches,T::branches), rho_(std::fabs(correlation)) { ... }
Size size(Size i) const {
return tree1_->size(i)*tree2_->size(i);
}
Size descendant(Size i, Size index, Size branch) const {
Size modulo = tree1_->size(i);
Size index1 = index % modulo;
Size index2 = index / modulo;
Size branch1 = branch % T::branches;
Size branch2 = branch / T::branches;
modulo = tree1_->size(i+1);
return tree1_->descendant(i, index1, branch1) +
tree2_->descendant(i, index2, branch2)*modulo;
}
Real probability(Size i, Size index, Size branch) const {
Size modulo = tree1_->size(i);
Size index1 = index % modulo;
Size index2 = index / modulo;
Size branch1 = branch % T::branches;
Size branch2 = branch / T::branches;
Real prob1 = tree1_->probability(i, index1, branch1);
Real prob2 = tree2_->probability(i, index2, branch2);
return prob1*prob2 + rho_*(m_[branch1][branch2])/36.0;
}
protected:
boost::shared_ptr<T> tree1_, tree2_;
Matrix m_;
Real rho_;
};
2つの確率変数の拡散過程を、相関を持つ2つの 3項Tree を使って、モデル化しています。ここで、説明の為の図をお見せしたとしても、混乱させるだけだと思います。しかし、考え方は、2つの確率変数の状態を、それぞれの Tree における Node のペアとして表現するという事です。それは即ち、確率変数の遷移は、ペアからペアへの遷移として認識される事になります。また、2変数のペアの可能な組合せすべてにインデックス番号を付け、そのペアを一個のインデックス番号で取りだせるようにして、必要なインターフェースと整合を取るようにしています。
例えば、2つの 3項Tree のケースで見てみましょう。今、レベル i に居るとします (2つの Tree は同じ TimeGrid (同じインデックス) を持っていなければなりません。そうで無いと、全く訳が分からなくなります)。1つ目の Tree上の確率変数の Node が j にあり、2つ目の変数の Node が k にあるとします。1つ目の Tree構造では、次のレベルでの分岐先 Node は \( j'_0,\ j'_1,\ j'_2\) で、それぞれ異なった確率で分岐するとします。2つ目の Tree構造では、次の Level での分岐先 Node は \(k'_0,\ k'_1,\ k'_2\) であったとします。この確率変数のペアが遷移すると考えると、次のレベルでの分岐先のペアとしては、\( (j'_0,\ k'_0),\ (j'_1,\ k'_0),\ (j'_2,\ k'_0),\ (j'_0,\ k'_1),\ (j'_1,\ k'_1),\ (j'_2,\ k'_1)\ ・・・\ (j'_2,\ k'_2) \) となり、合計 3 x 3 = 9 通りの可能性があります。これらのペアを、今説明した通り、辞書の順番 (アルファベット x 数字の順番) のように並べて、\( (j'_0,\ k'_0)\) を 0、\( (j'_1,\ k'_0)\) を 1、・・・ \( (j'_2,\ k'_2)\) を 8 のように、インデックス番号を対応させます。同じようにして、ある Level (離散時間における特定の時点) において、1つ目の Tree に n 個の Node があり、2つ目の Tree に m 個の Node があったとすると、合計で n ✕ m 個のペアが出来ますが、それらについても辞書の順番に並べてインデックス番号を付けます。そうすると (j,k) のペアには \(k \times n + j\) のインデックス番号がふられます。
ここまで説明すれば、実装内容の意味がより理解しやすくなるでしょう。TreeLattice2Dクラスのコンストラクターは、TrinomialTreeインスタンスを2つと、2変数の間の相関係数を引数として取り、それらをメンバー変数に保存します。ベースクラスである TreeLatticeクラスのコンストラクターが呼び出され、1番目の Tree から取りだされた TimeGrid インスタンスと Lattice の次元が引数として渡されます。この場合 Lattice の次元は (訳注:この場合、次元というより分岐数の意味)、2つのTreeの次元を掛け合わせたもので、2つの 3項Treeであれ 3 x 3 = 9になります。またコンストラクターは、後で使うことになる行列 m_ の初期化も行います。
ある Level(時点)における Lattice のサイズ(=Nodeの数) は、2つの Tree のサイズの掛け算 n ✕ m になります。size( )メソッドの実装内容は、この数をそのまま返すだけです。
次に説明する 2種類のメソッドを見れば、実装内容はもっと興味深いものになります。descendant( )メソッドは、引数として、①TrinomialTree上の Level を示すインデックス i、②Lattice上の Nodeインデックス (それは実際には (j,k) のペアにインデックス番号を付したものになります)、および③分岐先のNodeを示すインデックス (これも同じように分岐先の Node のペアに付されたインデックス番号を示します) を取ります。このメソッドが最初にする事は実際のペアを取りだすことです。すでに説明した通り、引数で渡される (レベル i での Node のペアの)インデックスは k ✕ n + j になります。このインデックスを使えば、2つの Tree上の Nodeインデックスはそれぞれ index % n および index / n として取り出す事が出来ます (訳注: index=(k ✕ n + j) を、%を使って n で割ると割り算の余りである j の値を返し、/ で割ると、代入先の変数型が整数になっているので自動型変換で小数点以下が切り捨てられ、結果 k の値が返される)。同じ計算ロジックで、2つの Treeの分岐先インデックスも導出出来ます。n を1番目の Tree の分岐先インデックス( 0 か 1 か 2)で入れ替えるだけです (訳注:分子の方は分岐先の 9個のペアに付されたインデックス)。
すべての必要なインデックス番号が特定できた所で、メソッドは 2つの Tree の descendant( )メソッドを呼び出し、2つの Tree からそれぞれの分岐先の Nodeインデックスである、\(j' と k'\) を得ます。そこで、1番目の Tree から分岐先レベルの Node数 \(n'\) を取りだし、分岐先 Nodeペアのインデックス \(k' \times n' + j'\) を返します。
probability( )メソッドも、途中までは同じ動作を行います (訳注:分岐元と分岐先のペアのインデックスの特定)。それが終わったら、2つの対象資産の Tree からそれぞれの遷移確率を取りだします。もし、この2つの遷移確率が相関していなければ、ある特定のペアへの遷移確率は、それぞれの遷移確率の積と一致します。通常は相関があると想定されているので、相関の項が加えられています。相関の項は、当然相関係数に依存します。その修正項の値については、すでに何度か説明しているので、ここでは飛ばします。
以上で実装は完成です。このクラスは 1次元のモデルと比べると、かなりのコード量になりますが、TreeLattice2Dクラスは、これでも未完成です。実際の Latticeクラスは、このクラスから派生させ、CRTP のループを閉じさせなければなりません。また、まだ実装していない discount( )メソッドも実装する必要があります。
********************************************************************************
最後に、あった方がいいにも拘わらず実際には Latticeクラスで漏れている機能について触れる事で、このセクションの説明を終えたいと思います。TreeLatticeクラスの実装を示す Listing 7.13 に戻って頂くと、stepback( )メソッドが前提としている確率変数は、現金価値を想定しており、従って常に割引率が掛けられています。もし割引率を掛けないで確率変数の値を遡及させていく方法があれば、それも役にたつかも知れません。例えば、オプションが行使される確率の計算は、行使条件が満たされる Node を 確率=1 に設定した上で、その値を、遷移確率を使って期待値計算を行い Tree を現時点まで遡れば求まるでしょうが、その場合は割引率の適用は不要でしょう。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス