2. "Implementing QuantLib"の和訳
Chapter-VI The Monte Carlo Framework
6.1 Pathの生成
乱数によるPath生成の作業は、いくつかの土台となるパーツとコンセプトを、ひとつに取り纏めたものです。我々のライブラリーの中での実装方法は、さらにいくつかのプログラミングのテクニックをそれに加えたものです。この章では、そのような土台となるパーツについての説明と、最終的にそれらを使ってどのように Pathを生成するクラス (PathGeneratorクラス)を構築していくかについて、説明します。
6.1.1 乱数の生成
我々の課題の為に必要な、基本的な組立部品のひとつが、乱数の生成装置です(以下RNG:Random-Number Generator)。その中でも最も基本的なものは、一様乱数の RNGです (訳注:一様乱数とは、0から 1の間で一様分布する乱数)。QuantLibライブラリーは、いくつかの一様乱数 RNGを提供していますが、その中でも、疑似乱数の生成装置としてはメルセンヌ・ツイスター(Mersenne Twister)、準乱数列の生成装置としては Sobolが最も有名です。ここでは、これらのプログラムコードの実装方法と、どの種類の RNGをいつ、どの様なケースで使えばよいのかについて、概観します。このような情報は(さらにモンテカルロ法に関する様々な事柄については)私よりも、もっとうまく説明している文献が多数ありますので、それらを参考にして下さい。(例えばGlasserman [1]、Jäckel [2]参照)
下記 Listing 6.1に一様乱数 RNG のインターフェースを示しますが、あまり興味をそそるようなものではありません。
template <class T>
struct Sample {
typedef T value_type;
T value;
Real weight;
};
class MersenneTwisterUniformRng {
public:
typedef Sample<Real> sample_type;
explicit MersenneTwisterUniformRng(unsigned long seed = 0);
sample_type next() const;
};
class SobolRsg {
public:
typedef Sample<std::vector<Real> > sample_type;
SobolRsg(Size dimensionality,
unsigned long seed = 0);
const sample_type& nextSequence() const;
Size dimension() const { return dimensionality_; }
};
これらのクラスのメソッドは、読者の方が予想されるであろう通りの内容です。疑似乱数の RNGのコンストラクターはシード値 (訳注:最初の乱数を生成する為にプログラムコードに与えられる任意の数値)を引数として取り、次の乱数を生成する next( )メソッドが用意されています。また準乱数列の RNGは、シード値に加え、乱数列の次元を引数に取るコンストラクターと、乱数の配列を返す nextSequence( )メソッドが用意されています。(考え直してみると、nextSequence( )メソッドの名前は単にnext( )でもよかったと思います。関数名に sequenceというトークンを含めるとハンガリアン記法を思い起こさせ、どこかへの goto命令文のように誤解される危険性があります。) ひとつだけ注意点を述べると、Sample構造体は、生成された乱数を返す為に使われますが、その乱数の加重 (weight)の値も保持します。加重を加えたのは、RNGが生成した乱数に何らかのバイアスをかける場合を想定したものです (例えば、モンテカルロ法の中でImportant Samplingを使った場合。訳注:Important Samplingはモンテカルロシミュレーションの収束速度を上げる為に、特定の範囲、例えばオプションの行使価格付近に集まった乱数について加重をかけるようなテクニック。) しかし、現時点で QuantLibライブラリーの中で、加重を必要とするような RNGは実装されておらず、ライブラリーで提供されている RNGはすべて同一ウェイトのサンプルを返すようになっています。
一様乱数の RNGの説明は以上です。しかし、この部分は最も生の原材料であり、ここから加工を加えていく必要があります。そのプロセスは、一様乱数 RNGのインスタンスを取り込んで、別の形態の乱数を生成するクラス群 (WrappersやCombinatorsと呼ばれるクラスを使います。あるいは、Decoratorパターンの実装と言ってもかまいません)によって為されて行きます。それには、2種類の Decoratorクラスが必要です。ひとつ目は、1つだけの乱数ではなく、乱数の集合が必要になります。すなわち、M個の確率変数が N個に分解された離散時間を辿っていく変動をシミュレーションしていくとすると、M x N個の乱数が必要になります。準乱数列の RNGについては、すでに乱数の集合(配列)を返すようになっています (注:そもそも、準乱数列の生成方法がそういうものです。与えられた次元数に合わせて、分布からの乖離が最も小さくなるように乱数を揃えるからです。) インターフェースを汎用的にしたいので、疑似乱数の RNGについても、一個一個生成された乱数をひとまとめにして、それを返せるようにしたいと思います。それを行うクラス Templateは、RandomSequenceGeneratorクラスと呼ばれ、内容を下記 Listing 6.2に示します。
Listing 6.2: Sketch of the RandomSequenceGenerator class template.
template<class RNG>
class RandomSequenceGenerator {
public:
typedef Sample<std::vector<Real> > sample_type;
RandomSequenceGenerator(Size dimensionality,
const RNG& rng)
: dimensionality_(dimensionality), rng_(rng),
sequence_(std::vector<Real>(dimensionality), 1.0) {}
const sample_type& nextSequence() const {
sequence_.weight = 1.0;
for (Size i=0; i<dimensionality_; i++) {
typename RNG::sample_type x(rng_.next());
sequence_.value[i] = x.value;
sequence_.weight *= x.weight;
}
return sequence_;
}
private:
Size dimensionality_;
RNG rng_;
mutable sample_type sequence_;
};
もうひとつ、実際のモンテカルロシミュレーションで必要なのは、一様乱数ではなくガウス乱数 (訳注:ガウス分布、すなわち正規分布となる乱数)です。ガウス乱数を生成する方法はいくつかありますが、どの方法でも、すべてのケースで最善な方法という事ではありません。例えば、N個のガウス乱数を生成する為に M個の一様乱数を使うような場合では、準乱数列の RNGではうまくいきません (注: 特に、Rejectionテクニックを使うように、必要なMの個数が生成ごとに変化するような場合)。もし汎用的な方法を使いたいのなら、最も簡単な方法は、標準正規分布関数の逆関数を使って一様乱数からガウス乱数を導出する方法でしょう。QuantLibライブラリーの中では、基本的にこの方法を使っています。それを実装したクラス Templateである InverseCumulativeRsgクラスを、下記 Listing 6.3に示します。このクラスは、標準正規分布関数の逆関数オブジェクトを Templateの引数として取るよう実装されています。この関数には解析解が存在しないため、近似式を使っています。通常のケースでは、ライブラリーの中で提供されている近似式で十分ですが、計算速度よりも正確さが必要であったり(あるいはその逆であったり)する様な場合には、その関数を自分が必要とする関数に入れ替えてもかまいません。
Listing 6.3: Sketch of the InverseCumulativeRsg class template.
template <class USG, class IC>
class InverseCumulativeRsg {
public:
typedef Sample<std::vector<Real> > sample_type;
InverseCumulativeRsg(const USG& uniformSequenceGenerator,
const IC& inverseCumulative);
const sample_type& nextSequence() const {
typename USG::sample_type sample =
uniformSequenceGenerator_.nextSequence();
x_.weight = sample.weight;
for (Size i = 0; i < dimension_; i++) {
x_.value[i] = ICD_(sample.value[i]);
}
return x_;
}
private:
USG uniformSequenceGenerator_;
Size dimension_;
mutable sample_type x_;
IC ICD_;
};
この時点ですでに、読者の方は、以上のプログラムの枠組みに対して、心地よくないと感じ始めたかもしれません。この段階では未だ乱数についてだけしか説明していませんが、すでに RNGのインスタンスを作るのに3つの選択肢があり、そこから2つを選んでいます。最後には、1ダース近くの Template引数を持たざるを得なくなるのではないかと。それについては、あと数ページ我慢して下さい。後の章で、QuantLibライブラリーがその問題をどのように軽減しようとしているか説明します。
最後の注意点です。気づかれているかも知れませんが、上のコードで示されている乱数列の RNGはメンバー変数として mutableな sequenceを持ち、乱数のコピーを避けるため都度新しい乱数を生成してデータを埋めています。もちろん、そのことによって、そのようなクラスのインスタンスが、他の異なるスレッドとシェアされる事を防いでいます。マルチスレッド環境におけるQuantLibの持つ問題点がすべてこのように対応できていたならば、私は大変な幸せ者です。
< Aside : より通行量の多い道 >
C++2011バージョンでは、既存の Boostの実装方法を基に、標準ライブラリーの中に RNGが含まれることになりました。どこかの段階で、おそらくそちらのインターフェースに変更するでしょうが、いつ、どのようにしてやるかは、まだ決まっていません。今あるプログラムコードを捨てて標準ライブラリーに変更するかもしれません。あるいは、今あるクラスのインターフェースだけを変更して今のプログラムコードを残す事もできます。あるいは、Adaptorsを使って2つのインターフェースを提供する事もできます。いずれにしても、過去のバージョンとの互換性を失わせる事になるでしょうから、その作業は QuantLibライブラリーの全面的なバージョン変更の時まで待たなければならないでしょう。
[1] P. Glasserman, Monte Carlo Methods in Financial Engineering. Springer, 2003.
[2] P. Jäckel, Monte Carlo Methods in Finance. John Wiley and Sons, 2002.
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス