2. "Implementing QuantLib"の和訳
Chapter VIII The Finite-Difference Framework (有限差分法のフレームワーク)
8.1 The Old Framework (古いフレームワーク)
8.1.7 Time-Dependent Operators : 時間に依存する差分演算子
これまでの説明では、モデルのパラメータが時間に依存している可能性を無視してきました。しかし、それに対応する為に、このフレームワークのベースクラス群に加えるべき修正は、僅かなものです。下記 Listing 8.15に、(そういった場合の)追加の修正(方法)を示します。
Listing 8.15: 時間に依存する有限差分モデルをサポートするコード
class TridiagonalOperator {
public:
class TimeSetter {
public:
virtual ~TimeSetter() {}
virtual void setTime(Time t,
TridiagonalOperator&) const = 0;
};
...
bool isTimeDependent() const { return !!timeSetter_; }
void setTime(Time t) {
if (timeSetter_)
timeSetter_->setTime(t, *this);
}
protected:
...
boost::shared_ptr<TimeSetter> timeSetter_;
};
template <class Operator>
class BoundaryCondition {
public:
...
virtual void setTime(Time t) = 0;
};
template <class Operator>
void MixedScheme<Operator>::step(array_type& a, Time t) {
for (Size i=0; i<bcs_.size(); i++)
bcs_[i]->setTime(t);
if (theta_!=1.0) { // there is an explicit part
if (L_.isTimeDependent()) {
L_.setTime(t);
explicitPart_ = I_-((1.0-theta_) * dt_)*L_;
}
// continue as before
}
if (theta_!=0.0) {
// the same for the implicit part
}
}
TridiagonalOperatorクラスに、TimeSetterクラスという内部クラスを持たせ、メンバー変数に、TimeSetterインスタンスへのポインター (nullでもかまわない)を保持させます。その内部クラスのインターフェースはいたって簡単で、デストラクターを除いては、setTime( )と呼ばれるメソッドをひとつ持っているだけです。そのメソッドは TridiagonalOperatorインスタンスの参照を引数として取り、現在の時間(それも引数として渡されたものです)に合わせて、その Operatorの要素を設定します。もちろん、そのメソッドは純粋仮想関数として宣言され、実際のロジックは、派生クラスで実装されます。TridaiagonalOperatorクラス自身の setTime( )メソッドは、メンバー変数とし保持している TimeSetterインスタンスにその動作を委託しており (訳注:setTime( )メソッドの実装コードの中で TimeSetter_->setTime(t, *this)の部分)、これは Strategyパターンの実装方法としてみる事ができます。
BoundaryConditionクラスは、同じパターンを使っていません。その代わり、このクラスは、必要なセットアップを実行する setTime( )メソッドを、単純に純粋仮想関数として宣言しています。Libraryの中で提供されている単純なディリクレ境界条件やノイマン境界条件の場合は、このメソッドは何も行いません。
最後に、Evolution Schemeのプログラムコードも、時間に依存するパラメータを取り扱えるようにする為に、修正が必要です。一例として、上記の Listing 8.15に、MixedSchemeクラステンプレートの step( )メソッドの修正版を示しています。このプログラムコードと、セクション 8.1.3 で示したバージョンと比べてみてください。修正版では、まず、すべての境界条件について、その setTime( )メソッドを呼び出します。そして、もし Operatorも時間に依存するものであれば、その Operator自身の setTime( )メソッドを呼び出し、陽的演算子と陰的演算子すなわち \(I-(1-\theta )∙\Delta t∙L\ と\ I +\theta ・ \Delta t∙L\) を再計算します。残りの計算は、修正前のコードと同じ様に行われます。
残念ながら、実際の時間依存 Operatorクラスでやっている実装は、ベースクラスで必要となる修正ほど単純ではありません。下記 Listing 8.16に、時間依存の係数を持つ Black-Scholes-Merton演算子の実装に使われているコードを示します。
Listing 8.16 : BSMTermOperatorクラスと関連するクラス
class PdeSecondOrderParabolic {
public:
virtual ~PdeSecondOrderParabolic() {}
virtual Real diffusion(Time t, Real x) const = 0;
virtual Real drift(Time t, Real x) const = 0;
virtual Real discount(Time t, Real x) const = 0;
virtual void generateOperator(Time t,
const TransformedGrid& tg,
TridiagonalOperator& L) const {
for (Size i=1; i < tg.size() - 1; i++) {
Real sigma = diffusion(t, tg.grid(i));
Real nu = drift(t, tg.grid(i));
Real r = discount(t, tg.grid(i));
Real sigma2 = sigma * sigma;
Real pd = -(sigma2/tg.dxm(i)-nu)/ tg.dx(i);
Real pu = -(sigma2/tg.dxp(i)+nu)/ tg.dx(i);
Real pm = sigma2/(tg.dxm(i) * tg.dxp(i))+r;
L.setMidRow(i, pd,pm,pu);
}
}
};
class PdeBSM : public PdeSecondOrderParabolic {
public:
PdeBSM(const shared_ptr<GeneralizedBlackScholesProcess>&);
...
};
template <class PdeClass>
class PdeOperator : public TridiagonalOperator {
public:
PdeOperator(const Array& grid, const PdeClass& pde)
: TridiagonalOperator(grid.size()) {
timeSetter_ =
boost::shared_ptr<GenericTimeSetter<PdeClass> >(
new GenericTimeSetter<PdeClass>(grid, pde));
}
};
typedef PdeOperator<PdeBSM> BSMTermOperator;
先ほどの例で説明した価格エンジンと同じように、いくつかの動作は再利用できるよう、別々のクラスに収められています。例えば、PdeSecondOrderParabolicクラスは次のような形(2階の放物線形)の偏微分方程式に従うモデルで使われます。(訳注:各項の係数が、定数でなく時間依存の関数になっています)
\[ \frac{\partial f}{\partial t}=-ν(t,x) \frac{\partial f}{\partial x}-\frac{1}{2} σ^2 (t,x) \frac{\partial^2 f}{\partial x^2}+r(t,x)f \]このクラスは、generateOperator( )メソッドを定義しており、引数として与えられた、グリッドと時間tを使って、対応するTridaiagonalOperator(三重対角演算子)の係数を計算します。上の方程式におけるν、σ、rの各係数は、それぞれ派生クラスでオーバーライドされたdrift( )、diffusion( )、discount( )メソッドから提供されます。気づかれたかも知れませんが、discount( )のメソッド名は不適当かもしれません。そのメソッドが計算する係数rは(割引率ではなく)Risk Freeの瞬間金利だからです。(注:不適当な名前ということでは、generateOperator( )メソッドの名前もそうです。このメソッドは演算子を生成するのではなく、既存の演算子の係数を計算するか更新する為のものです)
実際にこれらのメソッドをオーバーライドする派生クラスは、この例では PdeBSMクラスで、そこで Black-Scholes-Merton過程の各係数を特定します。各メソッドは、(簡略化のために内容を示していませんが)確率過程オブジェクトから、それぞれ必要とする情報を取りだして実装されています。
同じ Listing中にある PdeOperatorクラステンプレートは、TridiagonalOperatorクラスから派生しています。このクラスはテンプレート引数として、PdeBSMのようなクラスを取り、それが提供している generateOperator( )メソッドを、その名前通りの用法で使っています。コンストラクターは、引数として取った①テンプレート引数クラスのインスタンスと、②状態変数のグリッド、を使い、TimeSetterインスタンスを構築します。TimeSetterが生成され保存されたら、この PdeOperatorインスタンスは TridiagonalOperatorインスタンスとして、安全に切り出されて利用されます (訳注:PdeOperatorをベースクラスに型変換し、インスタンスのベースクラス部分だけを他のオブジェクトで利用する)。そこで生成される TimeSetterは、(ここではその実装内容は示されていませんが) GenericTimeSetterクラステンプレートのインスタンスです。このクラスは、setTime( )メソッドを実装していますが、その実装方法はシンプルで、①引数として与えられた時間 t、メンバー変数に保存された②Grid、および③Operatorインスタンスを、generateOperator( )メソッドに渡すだけです。
最後に、PdeBSMクラスを使って PdeOperatorテンプレートクラスを具体化して、時間依存の係数を持つ Black-Scholes-Merton演算子を得る事ができます。具体化されたクラスは、typedefを使って、クラス名を付けています。
再度申し上げますが、以上の構造が、プログラムコードの読みやすさと再利用可能性との間で、最善のバランスが取れたものかどうか判りません。前の例と同じように、すべての動作を BSMTimeSetterのような名前のクラスにほうりこんで、階層構造を単純化する事もできます。そうすると、そのコンストラクターは、グリッドと Processインスタンスを保存し、setTime( )メソッドはいくつかのクラスに分かれていた動作をひとつにまとめることができます。いずれにしても、どちらの方法がベストか証明する実例がまだ十分集まっていません。
***********************************************************************************
ここで解説した有限差分フレームワークについては、他にも説明したい事がたくさんあります。その一つに、前に触れた、OperatorFactoryクラスがあります。このクラスのメソッド群は StochasticProcessインスタンスを取り、それをうまく加工処理して、対応する Operatorインスタンスを返します。そういった動作を、ライブラリーは Javaではなく C++を使っているので、オーバーロードされた関数群で実装する事も可能でした。また、PdeShortRateクラスがあり、1ファクター短期金利モデルとして、PdeBSMと同じような機能を持っています。さらに、OperatorTraitsクラスがあり、フレームワークの中で使われているいくつかのデータ型を定義する為に使われています。その他にも、思い出せないような物まで含めて、多々あります。
しかし、それらについては読者の方に探索をおまかせします。ここからは前に進め、新しい有限差分法のフレームワークの説明に進みます。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス