2. "Implementing QuantLib"の和訳
Chapter-VI The Monte Carlo Framework
6.3 Putting it all together : すべての部品を使って組み立てる (つづき)
6.3.4 具体例: バスケットオプション
この Chapterを終えるにあたって、これまでに説明してきた Monte Carloシミュレーションの仕組みを使って、PricingEngineをどの様に構築するか、具体例を使って説明しましょう。例として使う商品は、株のバスケットを対象資産とするシンプルなヨーロピアンオプションです。オプションの保有者は、株のバスケット全体を、事前に決めた価格で買う権利あるいは売る権利を保有します。バスケットの中の各株式の数量は事前に契約で決められています。
簡略化のため、対象商品(株のバスケット)のクラス (BasketOptionクラス)の実装内容は示しません。このクラスは、Chapter 2で説明した VanillaOptionクラスと非常に似ており、いくつかの定量的な変数がメンバー変数に追加されているだけです (Instrumentクラスだけでなく、この商品のPricing Engineで使うargumentsクラスにも同様の追加が為されています)。Greeksの説明もここでは行いません。しかし、仮に BasketOptionクラスの Greeksを定義するとすれば、delta( )や gamma( )といったメソッドは(対象商品の数に対応する)配列で値を返すことになります。
この例の中心となるクラスは、実装された PricingEngineになります。そのクラス、すなわち MCEuropeanBasketEngineクラステンプレートの実装内容を下記 Listing 6.16に示します。ご推察の通り、このクラスは BasketOption::engine クラスから publicクラスとして派生します。また、このクラスは McSimulationクラステンプレートからも、privateクラスとして派生します。
Listing 6.16: Implementation of the McEuropeanBasketEngine class template.
template <class RNG = PseudoRandom, class S = Statistics>
class MCEuropeanBasketEngine
: public BasketOption::engine,
private McSimulation<MultiVariate,RNG,S> {
public:
typedef McSimulation<MultiVariate,RNG,S> simulation_type;
typedef typename simulation_type::path_generator_type
path_generator_type;
... // same for the other defined types
MCEuropeanBasketEngine(
const shared_ptr<StochasticProcess>&,
const Handle<YieldTermStructure>& discountCurve,
Size timeSteps,
Size timeStepsPerYear,
bool antitheticVariate,
Size requiredSamples,
Real requiredTolerance,
Size maxSamples,
BigNatural seed);
void calculate() const {
simulation_type::calculate(requiredTolerance_,
requiredSamples_,
maxSamples_);
const S& stats = this->mcModel_->sampleAccumulator();
results_.value = stats.mean();
if (RNG::allowsErrorEstimate)
results_.errorEstimate = stats.errorEstimate();
}
private:
TimeGrid timeGrid() const;
shared_ptr<path_generator_type> pathGenerator() const;
shared_ptr<path_pricer_type> pathPricer() const;
shared_ptr<StochasticProcess> process_;
Handle<YieldTermStructure> discountCurve_;
Size timeSteps_, timeStepsPerYear_;
Size requiredSamples_;
Size maxSamples_;
Real requiredTolerance_;
BigNatural seed_;
};
template <class RNG, class S>
TimeGrid MCEuropeanBasketEngine<RNG,S>::timeGrid() const {
Time T = process_->time(arguments_.exercise->lastDate());
if (timeSteps_ != Null<Size>()) {
return TimeGrid(T, timeSteps_);
} else if (timeStepsPerYear_ != Null<Size>()) {
Size steps = timeStepsPerYear_*T;
return TimeGrid(T, std::max<Size>(steps, 1));
} else {
QL_FAIL("time steps not specified");
}
}
template <class RNG, class S>
shared_ptr<typename MCEuropeanBasketEngine<RNG,S>::
path_generator_type>
MCEuropeanBasketEngine<RNG,S>::pathGenerator() const {
Size factors = process_->factors();
TimeGrid grid = timeGrid();
Size steps = grid.size() - 1;
typename RNG::rsg_type gen =
RNG::make_sequence_generator(factors*steps, seed_);
return shared_ptr<path_generator_type>(
new path_generator_type(process_, grid, gen));
}
template <class RNG, class S>
shared_ptr<typename MCEuropeanBasketEngine<RNG,S>::
path_pricer_type>
MCEuropeanBasketEngine<RNG,S>::pathPricer() const {
Date maturity = arguments_.exercise->lastDate();
return shared_ptr<path_pricer_type>(
new EuropeanBasketPathPricer(
arguments_.payoff, arguments_.quantities,
discountCurve_->discount(maturity)));
}
C++の文法では、継承時に privateの修飾子を使うと、「~を使って実装される”is implemented in terms of”」という関係が構築されます。我々は、McSimulationクラスからの継承の部分では publicでの継承を必要としません。それを使うと「~のままに:“is a”」の関係が構築されてしまいます。我々の概念的なオブジェクトモデルでは、MCEuropeanBasketEngineクラスは、あくまでバスケットオプション用の Pricing Engineであり、シミュレーション方法をモデル化したものではありません。複数のクラスから派生させる事は、一部の方からは敬遠されており、実際にこのケースではそれを避ける事も可能でした。このクラスを継承ではなく コンポジション(結合)で作る方法、すなわちMcSimulationクラスのインスタンスを持たせる事も可能でした。しかし、そうすると、ベースクラスの持つ純粋仮想関数を実装する為に、新しいシミュレーション用の派生クラスを作る必要がでてきます (pathGenerator( )やpathPricer( )など)。その結果、Pricing Engineのデザインがさらに複雑になるといった影響がでるでしょう。McSimulationクラスから派生させる事によって、そのような純粋仮想関数を PricingEngineの中での実装が可能になります。(このジレンマを避ける為には、McSimulationクラスを書き直して、Template Methodパターンを使わないようにしなければならないでしょう。すなわち、そのクラスを PathGeneratorと PathPricerのインスタンスを引数として取る具体的クラスとして作り直す事です。過去のバージョンとの互換性の問題もさることながら、その手間をかける意味があるかどうか確信が持てません。)
最後に、Templateパラメータを見て下さい。RNGについてはユーザーの選択に任せていますが、MonteCarloModelの traitsクラスについては、対象資産が複数あるバスケットオプションのモデルなので MultiVariateクラスをデフォールトで指定しています。
コンストラクターは (実装内容は上記 Listingでは省略)、引数として、対象資産の確率過程インスタンス、現在価値割引用のイールドカーブ、およびシミュレーションに必要ないくつかのパラメータを取っています。それらをメンバー変数にコピーし (antitheticVariateフラッグを除く。これはMcSimulationのコンストラクターに渡されます)、自らをObserverとして、Stochastic Processインスタンスと Term Structureインスタンスに登録します。
calculate( )メソッドは、PricingEngineクラスのインターフェースとして(仮想関数なので)実装が必要です。ここでは、McSimulationクラスの持つ、同じ名前のメソッドを呼び出し、それに必要なパラメータを渡します。そこから戻り値として statisticsインスタンスを受け取り、そこから平均値と、(計算されていれば)推定誤差を取りだして保存します。この動作は、バスケットオプションに限ったものではなく、それを汎用的な McEngineクラスを作って、そのインターフェースとして抽象化する事も可能です。しかし、そうすると概念的なオブジェクトモデルをごちゃまぜにしてしまいます。そのようなクラスは McSimulationクラスから派生させるでしょうが、同時に PricingEngineクラスから派生させる事も十分合理的に推定できます。しかし、仮にその様にしてしまうと、(この具体的なMCEuropeanBasketEngineクラスは)McEngineクラスと BasketOption::engineクラスの両方から派生させる事になり、忌まわしい継承のダイアモンド形が出来てしまいます。もし PricingEngineから派生させず別のクラス経由にしたとすると(それをMcEngineAdapterなどと呼んでもいいと思います)、継承の階層にさらに複雑さを加えることになります。なぜなら、そのクラスはMcSimulationクラスと、この MCEuropeanBasketEngineクラスの間に、新たな階層を加える事になり、複雑性を全く取り除けていない事になります。従って、今のままにしておくのがいいと思います。
Listingで示したコードで残りの部分は、McSimulationクラスの3つのインターフェースを実装しており、これによって、このクラスは実際に動作するクラスとなります。timeGrid( )メソッドは、シミュレーションに必要な離散時間のステップを、行使日を基準にした最終期日と、ステップの数を使って生成します。PricingEngineのコンストラクターに渡される引数によって、与えられた総ステップ数であったり(最初のif文)、1年あたりのステップ数であったりします(次のif文)。いずれの場合も、総ステップ数に換算されて、TimeGridクラスのコンストラクターに渡されます。いずれの指定も無かった場合は(else文の後)、エラー処理が走ります。
pathGenerator( )メソッドは、まず、Processインスタンスに確率変動成分の数を要求し、今説明した timeGrid( )メソッドの結果に従って離散時間のステップ数を確認し、正しい次元の数の分だけ (当然ながら、上記の2つの数字の積です)、疑似乱数列の生成装置を構築します。次に、この乱数列生成装置と、対象資産の Processインスタンス、および timeGridを使って、MultiPathGeneratorインスタンスを生成します。
最後に、pathPricer( )メソッドは、各 Pathにおける Payoffを判定するのに必要なデータを集めます。すなわち、Payoffオブジェクトそのもの、行使日における残高および割引率(いずれもこのメソッドが事前に計算している)で、それらを使って、EuropeanBasketPathPricerクラスのインスタンスを生成します。このクラスの実装内容を下記 Listing 6.17に示します。
Listing 6.17: Sketch of the EuropeanMultiPathPricer class.
class EuropeanBasketPathPricer : public PathPricer<MultiPath> {
public:
EuropeanBasketPathPricer(const shared_ptr<Payoff>& payoff,
const vector<Real>& quantities,
DiscountFactor discount);
Real operator()(const MultiPath& path) const {
Real basketValue = 0.0;
for (Size i=0; i<quantities_.size(); ++i)
basketValue += quantities_[i] * path[i].back();
return (*payoff)(basketValue) * discount_;
}
private:
shared_ptr<Payoff> payoff_;
vector<Real> quantities_;
DiscountFactor discount_;
};
この PathPricerは、引数で取ったデータを保存し、それを使ってオプションの実現価値を計算します。計算のロジックは operator( )関数をオーバーロードして実装されています。実装内容は、オプション期日における対象商品のバスケットの価値を計算し、その計算結果(すなわちPayoff)を現在価値に割引いて返します。バスケットの価値は、単純な loop計算で、保存された対象商品の数量の配列と、オプション満期時におけるそれらの価格(各商品に対応するPathの最後の値)を使って為されます。
これで、PricingEngineは完成ですが、まだ少しインスタンスを生成するには使いづらい所があります。ここに、さらに滑らかなインターフェースを持った Factoryクラスを加えてもいいかも知れません。そうすると、次のようなプログラムコードになるでしょう。
engine = MakeMcEuropeanBasketEngine<PseudoRandom>(process,
discountCurve)
.withTimeStepsPerYear(12)
.withAbsoluteTolerance(0.001)
.withSeed(42)
.withAntitheticVariate();
ここでは、その実装内容を示しませんが、QuantLibライブラリーでこのような例がたくさんありますので、目を通してみて下さい。
そうすると、気づかれると思いますが、これらのプログラム例は、できるだけシンプルに作ったので、汎用性のあるプログラムコードにしなかった部分もあります。例えば、別のPathPricerでは、オプション期日とは異なる日付の価格を使う場合もあり、それに合わせてTimeGridインスタンスも生成されなければなりません。あるいは、Payoffが、Pathの経路に依存しているケースもあり、その場合PathPricerはPathの最後の値のみならず、経路の途中の価格も見に行く必要があります。これらに対応するようなプログラムコードを書くのは、ユーザーの方にはそれ程難しい事では無いと思います。
< Aside : “need-to-know basis : 知る必要のある人にしか伝えない”の原則 >
これまで通り、今まで説明してきたいくつかのクラスについて、どの(ソース)ファイルに置くべきか示してきませんでした。基本原則として、「カプセル化できるものは、できるだけそうする」ようにしています。従って、helperクラスなどは、publicなインターフェースからは、隠すべきでしょう。例えば、EuropeanBasketPathPricerクラスなどは、PricingEngineの中で使われるのみであり、ユーザーが再利用できないように、隠した方が良いでしょう。その為の最適な方法は、.cppファイルの中の匿名のnamespaceの中で定義する事でしょう。しかし、それが常に可能な訳ではありません。この Chapterで説明した Pricing Engineクラスの場合、Templateクラスなので、その選択肢は取れません。QuantLibライブラリーの中での基本的な決め事としては、helperクラスはヘッダーファイルの中で宣言し、実装内容は“detail”という名前のnamespaceの中に入れ込んでしまう事にしています。ユーザーの方は紳士協定に(当然淑女協定にも)従って、それに触らないようにして下さい。
仮に、後から、そのようなクラスをどこか別の所で使う必要が出てきた場合(例えば、EuropeanBasketPathPricerを制御変量の PathPricerとして他の PricingEngineで使うような場合)、その内容をヘッダーファイルに移す事もできます。あるいは、既にヘッダーファイルに含まれている場合は、実装内容をメインとなる“QuantLib”のnamespaceに移して、誰にでもアクセス可能なように出来ます。
**********************************************************************************
これまでのすべての説明を聞いた後でも、まだ疑問があるかも知れません。「このPricingEngineクラスはどの程度汎用的なのか?」と。そうですね、私が望んでいたレベル程ではないです。ただ、N個の対象資産の確率過程が必要なものであれば、この Engineを利用でき、かつきちんと動く、という意味では汎用的です。さらに、quanto効果 (訳注:quanto option 対象資産の通貨と、オプションの契約通貨が異なるオプションで、対象資産の変動と為替の変動の両方の確率変動とその相関の影響を受けること)のような、もう少し exoticな商品にも対応可能です。その場合、通常の確率過程モデルを使い、その中で quanto効果を対象資産の価格変動のドリフト項の修正としてモデル化すれば、drift( )あるいは evolve( )メソッドの中でその修正を取り込むことによって、ユーザー自身で自分の確率過程のプログラムコードが書けます (さらに decorateパターンを使えばもっと素晴らしい)。それを、PricingEngineのコードには一切触らず、使う事ができます。
しかし、対象資産以外に確率変動する変数をモデル化したような確率過程 (例えば、マルチアセットのHeston過程)を使う場合には、おそらく問題に直面するでしょう (訳注:Heston Modelは Stochastic Volatilityモデルのひとつで、対象資産の拡散項係数、すなわち Volatility自体も確率変動すると仮定したモデル)。問題は、PathPricerインスタンスのoperator( )関数の中で、N個の対象資産に対し、2N個のPathが渡され、その内、どれが対象資産の価格の Pathでどれが VolatilityのPathなのか区別するすべが無いという事です。では、どのようにして、この2つの Pathをまとめて、正しいバスケットオプションの価格を出せばいいのでしょうか? もちろん、この確率過程モデル用の PathPricerのプログラムコードを作成するというのも方法ですが、もう少し再利用可能なアイデアを提供したいと思います。
まず簡単な方法として、Processクラスと PathPricerクラスで、プログラム上の決め事を同じにする事です。例えば、Processクラスにおいて、最初の N個の Pathは対象商品の価格の Pathとし、残りの M個はその他の確率変数の Pathと決めてしまう事です。そうすると、この Chapterで説明した PathPricerはきちんと動きます。PathPricerの中の loopは、Processの数ではなく、対象資産の数だけ繰り返しを行っているという所を見れば、解ると思います。しかし、この方法では、Processクラスの方で、この決め事を守らなかった場合、そのバグがチェックされないままエラーが発生するリスクを高めます。
考えに浮かんだ別の方法は、Processクラスを別の階層で decorateし、そのクラスでは確率変数として対象資産の価格のみ外に取りだせるようにし、それ以外の確率変数は隠すようにする事です。その decoratorクラスは、Processインスタンスとして、ベースとなる Processオブジェクトと同じ数の確率変数を要求しますが、外にはそれより少ない確率変数しか出さないようにします。このインスタンスは、その隠された確率変数 (その値は、他のメソッドに渡されないでしょう)の動きを監視しなければならないので、その状態変数を保持できるようにしなければなりません。おそらく、その デコレートされたProcessインスタンスの evolve( )メソッドが、対象資産の現在の価格を引数で取り、そのインスタンスに保持されている追加の確率変数を加え、基の Processオブジェクトの evolve( )を呼び出し、追加の確率変数の更新値をメンバー変数に保持して次のメソッドを呼び出す時に使えるようにし、最後に対象資産の価格だけを返すようにしなければなりません。
しかし、私はこの解決方法が好きではありません。このデザインでは、decorateされたメソッドが、予想された順序で呼び出されない限りきちんと動きません。例えば、もしそのメソッドを、全く同じ引数(初期値と確率変数)を使って2回呼び出した場合、隠された状態変数が更新されている為、異なる値が返ってくることになります。この現象を、長ったらしく言えば、「decorateされたプロセスを持つメソッドがreferential transparency (訳注:適切な日本語が見つからないので英文のままにします)を失う事」になります。短く言えば、「インターフェースが嘘」という事です。
より希望の持てる方法としては (フレームワークそのものに若干手を加える必要がありますが)、PathGeneratorクラスに Processクラスの手を借りて、フィルターをかける役割を負わせる事です。もし Processクラスが、何らかのマスク(確率変数の全体の中から、対象資産の価格に該当する部分を示すインデックスのリスト、あるいは bool変数の配列で、[i]番目の値がtrueであれば、[i]番目のPathは対象資産の価格である事を示すもの)を返すメソッドを提供できれば、PathGeneratorクラスはその情報を使って、確率変数の推移を正しくシミュレートし、同時にMultiPathクラスに対象資産の価格の情報のみを書き込むことが出来るでしょう。これらを、過去のバージョンとの互換性を維持したまま取り入れようとすると、そのマスクの情報を選択的に PathGeneratorクラスに渡せるようにしなければなりません。また StochasticProcessベースクラスは、空のマスクを返すメソッドをデフォールトで実装しなければなりません。もし、マスクの情報が渡されなかった場合は、PathGeneratorは、今実装されている通常の動作に戻すようにします。必要であれば、そのメソッドをより汎用的にして、対象資産の価格だけでなく、他の確率変数のマスクにも使えるように出来ます。例えば、Variance Swapでは、対象資産の価格ではなく、Volatilityの方の情報を必要とするでしょう。
最後に、この PricingEngineには、いくつかの前提が組み込まれており、その前提を変える場合は、プログラムコードの書き換えが必要になる点を指摘しておきます。例えば、オプション行使日における割引率は、決まっているものとして取り扱っています。もし、対象資産のみならず、金利についても確率変動する確率過程のモデルのプログラムコードを書こうとすれば、かつ、そうすると Path毎に確率変動に従った金利の水準で割引率を決めなければならないとすれば、その特別な Processクラスの為に、独自の PricingEngineと PathPricerクラスのプログラムコードを用意しなければなりません。幸運なことに、ユーザー自身でプログラムすべきは、その部分だけであり、モンテカルロの全体のフレームワークの中の、他の部分は引き続き使う事ができます。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス