2. "Implementing QuantLib"の和訳
Chapter VIII The Finite-Difference Framework (有限差分法のフレームワーク)
8.2 The New Framework (新しいフレームワーク)
8.2.4 Initial, Boundary and Step Conditions: 初期条件、境界条件 および 時間ステップ条件
読者の方は覚えているかも知れませんが、古いフレームワークでは、基本となる価格エンジンは仮想関数を宣言し、その具体的な内容は派生クラスで実装される仕組みを取っていました。その仕組みは、微分方程式問題の初期条件についても同様でした。新しいフレームワークでは、その plug-and-play (線をつなげば、すぐに使えるような状態)の性質は残したまま、初期条件の計算は (派生クラスではなく)別のオブジェクト群にカプセル化され、他の価格エンジンでも使えるようにしています。
そういったオブジェクトのベースクラスは FdmInnerValueCalculatorクラスで、その内容を下記 Listing 8.27 に示します。
class FdmInnerValueCalculator {
public:
virtual ~FdmInnerValueCalculator() {}
virtual Real innerValue(const FdmLinearOpIterator& iter, Time t) = 0;
virtual Real avgInnerValue(const FdmLinearOpIterator& iter, Time t) = 0;
};
class FdmLogInnerValue : public FdmInnerValueCalculator {
public:
FdmLogInnerValue(const shared_ptr<Payoff>& payoff,
const shared_ptr<FdmMesher>& mesher,
Size direction);
Real innerValue(const FdmLinearOpIterator& iter, Time) {
Real s = std::exp(mesher_->location(iter, direction_));
return (*payoff_)(s);
}
Real avgInnerValue(const FdmLinearOpIterator& iter, Time);
};
class FdmLogBasketInnerValue : public FdmInnerValueCalculator {
public:
FdmLogBasketInnerValue(
const shared_ptr<BasketPayoff>& payoff,
const shared_ptr<FdmMesher>& mesher);
Real innerValue(const FdmLinearOpIterator& iter, Time) {
Array x(mesher_->layout()->dim().size());
for (Size i=0; i < x.size(); ++i)
x[i] = std::exp(mesher_->location(iter, i));
return (*payoff_)(x);
}
Real avgInnerValue(const FdmLinearOpIterator& iter, Time) {
return innerValue(iter, t);
}
};
このインターフェースの内、innerValue( )メソッドは、引数で渡された Mesher上の点における初期値 (満期Payoff) を返し、avgInnerValue( )メソッドは、引数で指定された Mesher上の特定の点の、周辺ポイントの平均値を返します。後者は、Payoff関数の持つ非連続な場所をスムーズにする方法を提供しています (訳注:有限差分法では、Schemeによっては非連続点となる Strike近辺で不自然な振動が発生する可能性があり、それを抑える為のテクニック)。いずれのメソッドも、純粋仮想関数として宣言されており、こうする事は innerValue( ) については至極適切であり、avgInnerValue( )についても間違った方法ではありません。しかし後から振り返ると、いくつかの派生クラスでは、平均値の計算が複雑すぎて、計算をあきらめ、単に中心ポイントの値を返しているだけ (即ちinnerValue( )の値を返しているだけ)なので、(純粋仮想関数とせず、ベースクラスにおいて)それをデフォールトの実装としても良かったかも知れません。 (注:あるいは、平均値の計算は、やはり派生クラスを実装しようとする人の選択に任せるべきであるという意見があっても、それが間違っているとは思いません。)
上記のListingには、2つの具体的な派生クラスで、仮想関数を実装している様子も含まれています。FdmLogInnerValueクラスは、対象資産が1つで、その価格の対数値のメッシュをもとに、Payoff値を計算しています。このクラスのコンストラクターは、引数として、①Payoffインスタンス、②メッシュ、および③変数が変化する方向軸を取ります。最後の引数(方向軸)は、メッシュが多次元の場合に必要になります。 (注:対象資産が1つなのに、メッシュが多次元になる例としては、対象資産は株価だけでも、状態変数は、株価とそのボラティリティーの2つ、あるいは株価と金利の2つかも知れないし、あるいはそれら3つの確率変数すべてかも知れません。対象資産の価格のメッシュが1番目の次元になるとは限らないので、③の引数が必要になります。)
innerValue( )メソッドは、引数のイテレータが指し示す、メッシュ上の点の対象資産価格(の対数)を取りだし、それを Payoffインスタンスに渡します。avgInnerValue( )メソッドも、それと似たような動作を行いますが、それと同時に数値積分および boost::bind( ) の動作も行います。これについては、ややこしいので、ここでは説明を省略します(またboost::bind( )については、ラムダ式 (訳注:c++11で導入された無名の関数オブジェクト) が使えるようになれば取り除きたいと思っています。
2番目の例は FdmLogBasketInnerValueクラスで、Payoff関数が複数の対象資産の価格に依存する場合に使われます (その場合でもバスケットの総額に依存するのではありません。そういう意味ではクラス名はやや不適当かも知れません )。innerValue( )メソッドは、複数の次元の対象資産価格のメッシュから値を取りだし、(この場合、複数の対象資産価格以外の確率変数 (例えばVolなど) は無いと仮定しています)、それらを Payoffインスタンスに渡しています。avgInnerValue( )メソッドは、残念ながら具体的な計算をあきらめ、innerValue( )メソッドを呼び出しているだけです。
< Dispelling magic 魔法をどうやって取り除けばよいか? >
このセクションの最初に示した価格エンジンのプログラム中にある、Inner-Value の Calculatorインスタンスの生成の所を見れば、引数として渡すインデックスの次元について 0 というマジックナンバーを渡しているのがわかると思います (訳注:本来ならMesherインスタンスから、何等かの変数名を使って、取りだしてくるべきなのに、1次元方向を想定して無理やり 0 を入れている)。もちろん、これは最善の方法ではありません。問題は、どの次元かの情報が Mesherクラスの定義の中にハードコードされており、プログラムを使ってその情報を取りだせない事です。また、その情報へアクセスする方法を、Mesherクラスの共通なインターフェースとして定義するのも簡単ではありません。
私自身、まだ解決策を思いつきません。昔からある、こういった場合の代替法としては、このマジックナンバーに名前を付ける方法も一考かもしれません。例えば、FdmBlackScholesMesherクラスが staticな変数として x_dimension のような名前を提供することも可能です。しかし、こういった変数によい名前をつけるのも簡単ではありません。また、その方法だと、最初の方に述べた Mesherを、派生クラスで動作を実装する polymorphicな構造にせず、Factoryパターンの関数群に置き換えるという方向性にも反します。
**********************************************************************************
新しいフレームワークでは、境界条件用のインターフェースを、新たに定義していません。価格エンジンの中で使われている FdmBoundaryConditionSetクラスは、古いフレームワークで使われていた OperatorTraitsを使って、次のように定義されています。
typedef OperatorTraits<FdmLinearOp>::bc_set
FdmBoundaryConditionSet;
従って、前のセクションで説明した BoundaryConditionクラステンプレートを利用しています。ここで唯一新しいのは、(テンプレート引数として)FdmLinearOpクラスを使って、テンプレートを具体化している所だけです。
もちろん、新しく作られたクラス群と協働できるよう、それ用の境界条件も実装されています。例えば、次の Listing 8.28 にある FdmDirichletBoundaryクラスのインターフェースです。このコンストラクターは、引数として、①方程式の定義域を規定するMesherインスタンス、②境界におけるオプション価格、③境界値をコントロールしたい次元軸、および④境界がメッシュの上方か下方かを示す数字、を取っています。ここではコンストラクターの実装内容を示していませんが、Mesherインスタンスを使って、境界条件が適用される配列の要素を特定しています。
Listing 8.28 : FdmDirichletBoundaryクラスのインターフェース(一部)
class FdmDirichletBoundary
: public BoundaryCondition<FdmLinearOp> {
public:
FdmDirichletBoundary(const shared_ptr<FdmMesher>& mesher,
Real valueOnBoundary,
Size direction, Side side);
void applyBeforeApplying(operator_type&) const;
void applyBeforeSolving(operator_type&, array_type&) const;
void applyAfterApplying(array_type&) const;
void applyAfterSolving(array_type&) const;
void setTime(Time) {}
};
**********************************************************************************
最後に、StepConditionですが、これも古いフレームワークで定義されたクラステンプレート (StepCondition<class array_type>) から派生させて定義しています。ただし、当然ながら新しいフレームワークで定義されたクラス群を使っています。そのようなクラスのひとつ FdmAmericanStepCondition の実装内容を下記 Listing 8.29 に示します。このクラスはアメリカンオプションが行使されるのかどうかを決定します。
Listing 8.29 : FdmAmericanStepConditionクラスのインターフェース
class FdmAmericanStepCondition : public StepCondition<Array> {
public:
FdmAmericanStepCondition(
const shared_ptr<FdmMesher>&,
const shared_ptr<FdmInnerValueCalculator>&);
void applyTo(Array& a, Time) const {
shared_ptr<FdmLinearOpLayout> layout = mesher_->layout();
for (FdmLinearOpIterator iter = layout->begin();
iter != layout->end(); ++iter) {
Real innerValue = calculator_->innerValue(iter, t);
if (innerValue > a[iter.index()]) {
a[iter.index()] = innerValue;
}
}
}
private:
shared_ptr<FdmMesher> mesher_;
shared_ptr<FdmInnerValueCalculator> calculator_;
};
実装内容は、皆さんの予想通りです。 applyTo( )メソッドは、メッシュの配列の値を順番に読み込んでいき、各ポイントにおいて、行使価値とオプション価値を比較し、それによって対応する配列の要素を書き換えます (訳注:行使価値がオプション価値を上回っていれば、配列にあるオプション価値を行使価値に書き換える)。コンストラクターは、この applyTo( )メソッドが必要とする情報を引数で取り、メンバー変数に格納します。
新しいフレームワークでは、FdmStepConditionCompositeクラスも定義しています。このクラスは便利なクラスで、Compositeパターンを使って、複数の境界条件をグループ化し、ひとつのオブジェクトのように受け渡し出来るようにしています。このクラスのインターフェースを下記 Listing 8.30 に示します。このクラスも applyTo( )メソッドを実装しており、すべての Step Condition を、いくつかの Utilityメソッドと共に、順番に適用していきます。殆どのメソッドは汎用的で、格納されている情報に対するインスペクター関数や、追加の StepConditionを、StepConditionsのグループに加えるメソッドなどです。
Listing 8.30 : FdmStepConditionCompositeクラスのインターフェース
class FdmStepConditionComposite : public StepCondition<Array> {
public:
typedef list<shared_ptr<StepCondition<Array> > > Conditions;
FdmStepConditionComposite(
const list<vector<Time> > & stoppingTimes,
const Conditions & conditions);
void applyTo(Array& a, Time t) const;
const vector<Time>& stoppingTimes() const;
const Conditions& conditions() const;
static shared_ptr<FdmStepConditionComposite> joinConditions(
const shared_ptr<FdmSnapshotCondition>& c1,
const shared_ptr<FdmStepConditionComposite>& c2);
static shared_ptr<FdmStepConditionComposite>
vanillaComposite(
const DividendSchedule& schedule,
const shared_ptr<Exercise>& exercise,
const shared_ptr<FdmMesher>& mesher,
const shared_ptr<FdmInnerValueCalculator>& calculator,
const Date& refDate,
const DayCounter& dayCounter);
};
しかし、staticとして宣言されている vanillaComposite( )メソッドは若干異なります。このメソッドは Factoryメソッドで、多数の引数を使って Step Condition の集合体(Composite)を構築し、それを他の微分方程式問題でも使えるようにしています。その機能は良い事ですが、他のクラスと比べると少し特殊です。注意しておきますが、そんなに特殊という事ではありません。というのは、QuantLibライブラリーの中をざっとサーチしてみると、バリアオプションや Swaptionsといった商品の価格エンジンでも使われている事が判ります。しかし、この機能を拡大していくのなら、このクラスの中で増殖していくのは好ましくありません。この関数を独立した関数として定義した方が、より明瞭でしょう。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス