2. "Implementing QuantLib"の和訳
Appendix A Odds and Ends 周辺の話題
A.5 Global Settings :ライブラリー全体の変数と定数の設定
下記Listing A-21に示すSettingsクラスは、Singleton(後で説明します)の一種でQuantLibライブラリー全体に渡る情報を格納するオブジェクトです。
Listing A-21:Settingsクラスの概要
class Settings : public Singleton <Settings> {
private:
class DateProxy : public ObservableValue<Date> {
DateProxy();
operator Date() const;
...
};
... // more implementation details
public:
DateProxy& evaluationDate();
const DateProxy& evaluationDate() const;
boost::optional<bool>& includeTodaysCashFlows();
boost::optional<bool> includeTodaysCashFlows() const;
...
};
このクラスのメンバー変数は、ほとんどが(何等かの状態を示す)フラッグで、それぞれの意味については 正式なDocumentationをご覧下さい。仮にその意味が判らなくても、ライブラリーの使用は問題ありません。ユーザーがきちんと管理しなければならない情報は Evaluation Date(評価日)だけです。そのデフォールト設定は“本日”で、それは(通常)金融商品の時価評価基準日であり、かつ各種インデックスの Fixing値を決める日でもあります。
この情報をどう取り扱うかは難問でした。金融商品の価格計算は、Evaluation Dateに依存しており、日付が変更になれば当然、その通知を受ける必要があります。その機能は、必要な情報を、proxyクラス(訳注:特定のクラスのインスタンスをメンバー変数として取り込み、別の機能を加えた上で、あたかもその特定クラスの代理として使われるクラス)の中に包み込んで、間接的に返す事によって実現されています。このクラスにある、その機能を担うメソッド群を見れば、それが判ります。この proxyクラス(訳注:上記コードにある DateProxyクラス)は ObservableValueクラステンプレート (次のListing A-22参照) から派生しています。このクラスは、黙示的に Observableクラスに転換され、さらに代入演算子をオーバーロードして、データ変更時に(登録されているObserver群に)変更通知の機能を与えられています。それと同時に、この proxyクラスは、内包されたデータ(訳注:ここでは Dateクラスのインスタンス)に自動的に変換する機能も持っています。
Listing A-22:ObservableValueクラスの概要
template <class T>
class ObservableValue {
public:
// initialization and assignment
ObservableValue(const T& t)
: value(t), observable_(new Observable) {}
ObservableValue<T>& operator=(const T& t) {
value_ = t;
observable_->notifyObservers();
return *this;
}
// implicit conversions
operator T() const { return value_; }
operator boost::shared_ptr<Observable>() const {
return observable_;
}
private:
T value_;
boost::shared_ptr<Observable> observable_;
};
このクラスを使えば、今説明した機能を、自然な構文で使えます。まず、Observerインスタンスを Observable(訳注:ここでは evaluationDate()が返す DateProxyインスタンス) に登録するには、次の様な構文で出来ます。
registerWith(Settings::instance().evaluationDate());
一方で、次のような構文で、evaluationDate()の返し値を、Dateインスタンスのように使えます。
Date d2 = calendar.adjust(Settings::instance().evaluationDate());
この構文は、(evaluationDate()が返す DateProxyインスタンスを)自動的に Date型に変えています (訳注:オーバーロードされた代入演算子”=”が、その機能を提供している)。さらに3点目として、下の構文のように evaluationDate( )が返すDateProxyインスタンスに、特定の日付を代入できます。(訳注:これも代入演算子“=”をオーバーロードして機能させている。)
Settings::instance().evaluationDate() = d;
さらに、この代入動作の直後、日付の更新があった事を、すべてのObserverに通知します。
***********************************************************************************
もちろん、誰もが気づいている問題は、グローバル変数としての Evaluation Date(時価評価基準日)しか持っていないという点です。そこから来る明らかな弱点は、異なる Evaluation Dateを使って商品の時価評価を、並行して行えないという事です(少なくとも、QuantLibライブラリーの Configuration(環境設定)をそのまま使うとすればですが)。
という事は、環境設定を操作すれば、何とかなりそうですが、問題は簡単ではありません。まず、(ユーザーが若干手を加えれば) Settingsインスタンスを Thread毎に持たせるようにコンパイル時のフラッグを設定する事は可能ですが、後で説明する通り、これですべての問題が解決される訳ではありません。また別の問題として、シングル Threadでプログラムを走らせていても、この Global変数はあまり好ましくない現象を起こす可能性があります。仮に、あるひとつの商品だけを、異なる Evaluation Dateを使って時価評価した場合、Evaluation Dateを元の日に戻した時点で、その日付変更によって、他のすべての商品の時価の再計算を起動してしまうことになります。
この状況を解決するには、Global Settingsクラスを何等かの Contextクラスに取り換えるべきとの方向性を示唆しています(この問題について、我々の間で話をした時、何人かの鋭い人からも同じアイデアが出ました)。しかし、ある特定の価格計算において、どうやって context (訳注:どのスレッドで、そのプロセスが選択されたのかを示す指標のようなもの)を選択すればいいのでしょうか?
Instrumentsクラスの中にsetContext( )メソッドを加えるアイデアは、魅力的かもしれません。そうする事によって、計算の際に、Instrumentsは価格エンジンに対しcontextを伝え、その価格エンジンからさらに価格計算に使われるTermStructureインスタンスなどにそれを伝達するのです。しかし、このロジックを実装するのは簡単ではありません。
まず、Instrumentsインスタンスも価格エンジンインスタンスも、価格計算に必要な TermStructureクラスが常に判っている訳ではありません。例えば、Swapは、いくつかのクーポンキャッシュフローを持っていますが、その一部は価格計算において Forecastingカーブの情報を参照している可能性があります。その情報 (訳注:どのクーポンが、Forecastingカーブの情報を参照しているかの情報) を得る為には、関連するすべてのクラスに、必要な機能を加える必要があります。そもそも Couponオブジェクトに context を設定するのは、いいアイデアとは思いません。
次に、そしてより重要な点ですが、価格エンジンに context を設定するのは、そのデータが変更される動作 (mutating operation) を内包しています。Instrumentsに context設定の役割をまかせると、constメソッドであるはずの NPV( )メソッドを呼び出している最中に、価格エンジン中の context情報が変更される可能性があります。この状況は、簡単に race condition (同じ動作で異なった計算結果を出す状況)を発生させます。例えば、一見害のなさそうな計算として、2つの商品が、異なる Evaluation Dateを基準に、同じ Discountカーブを使って時価評価しようとすると、どうなるでしょう。並列処理プログラミングの経験がほとんどないユーザーが、夢にも思わなかった事が起こり得ます。例えば、同時に走っている2つのスレッドから、同じ (TermStructureインスタンスへの)ハンドルにリンクされてしまう事があり得ます。仮にそうなっても、データ変更の動作が constメソッドの中に隠されているので、ユーザーはそれに気づかないかもしれません。
(注:“ちょっと待って。NPV( )を呼び出している間に、他にもデータを変更するような動作があるのでは?”と言われるかも知れません。よく気づかれました。このセクションの最後にある Asideの項を見てください。)
そうすると、どうも価格計算を始める前に context を設定する必要がありそうです。であれば、Instrumentsにそれをまかせる方法は排除されます(なぜなら、ある商品に contextを設定させた場合、その商品の価格計算に使われる TermStructureインスタンスを、既に使っていた別の商品により設定された context を無効にしてしまう可能性があるからです)。おそらく、価格計算に使う複数の TermStructureインスタンスに、明示的に context を設定しなければならない方向に向かうでしょう。その方向性を取った場合のメリットは、知らずに同じオブジェクトに対し同じ contextを設定しようとしてrace conditionに陥るリスクを無くせる事です。逆にデメリットは、設定がより複雑になる事と、異なる contextを同時に設定する場合は、TermStructureインスタンスの複製を作る必要があるという事でしょう。例えば、OISディスカウンティングカーブを2つ用意しないといけなくなるとか。かつ、その場合、スレッド事の Singletonを管理しないといけなくなるでしょう。
最後に、contextが渡されても保存されないような場合について簡単に触れましょう。その場合、メソッドの呼び出し方法が次のような形になるでしょう。
termStructure->discount(t, context);
これだと、cashingを完全に壊し、すべての関係者に不愉快な思いをさせそうです。もしこのような物を望むなら、Haskellでプログラムを書いたでしょう。
まとめると、このセクションンを暗い気持ちで終わるのは嫌ですが、どれもこれもうまく行きません。Global変数の設定は制約になりますが、私自身解決策を持ち合わせていません。さらに悪いことに、取り得る変更方法は、複雑さを増大させます。QuantLibライブラリーを初めて使う人に、Black-Scholesの公式を探しているのなら、TermStructure や Quotes や Instrumentsや Engineといったクラスだけでなく、contextも必要ですと説明する事になるでしょう。このセクションの説明で少しは役に立つでしょうか?
< Aside : more mutation than in a B movie (B級映画以上にmutationが登場)
残念ながら、constメソッドであるはずの Instrument::NPV( )が呼び出されている最中に、データが変更されてしまう事態は、他にもありました。
まず、価格エンジンの中に取り込まれる arguments と resultsインスタンスから始めましょう。これらのインスタンスは価格計算の最中に読み込まれ、そして書き込まれます。従って、同じ価格エンジンのインスタンスを使って、他の Instruments の価格計算を走らせる事はできません。この点は、価格エンジンに(鍵)を加える事で修正できるかも知れません(そうすると、複数商品の価格計算を順番に並べて行うことになります(訳注:同時に出来ないので、マルチスレッドのメリットが享受できない))、あるいはインターフェースを変更して、価格エンジンクラスの calculate( )メソッドが arguments構造体をパラメータとして取り込み、 results構造体を返すようにしてもいいかもしれません。
次に、Instrumentクラスそのものの中に mutableなメンバー変数が含まれており、それらは価格計算の最後に書き込まれます。これが問題になるかどうかは、実行しようとしている計算の種類によります。その Instrumentの価格を2回、同時に走っている2つのスレッドで計算すると、同じメンバー変数に計算結果が2回書きこまれて終わりになる可能性があります。
最後に思い浮かぶのは、hidden mutation(隠れたデータ変更)で、おそらくそれが最も危険でしょう。TermStructureインスタンスを価格計算の最中に使おうとすると、Bootstrapの処理を起動する可能性があり、別々のスレッドで行われている価格計算が同じTermStructureインスタンスを使って同時に走ってしまうと、お互いの計算結果をおかしくしてしまう可能性があります。Bootstrappingの計算ロジックが再帰的であるので、(ひとつの計算プロセスの最中に他のエンジンからのアクセスを制限する為の)鍵をかける事が可能かどうか、確信がありません。従って、もしユーザーの方が、(注意深く、すべての設定をあらかじめ行い、同じ Evaluation Dateを使う条件で)並列計算を実行しようとするなら、計算をスタートする前に、Bootstrappingのプロセスを完全に終わらせてから計算するようにして下さい。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス