金融 > Implementing_QuantLib日本語訳 > 2章

Implementing QuantLib日本語訳 2章


これは、Luigi Ballabio氏が書かれているImplementing QuantLibの翻訳です。氏の許可を得て、翻訳・公開しています。
日本語訳の情報は、こちらを参照してください(日本語訳に関する質問等もここのフォームにお願いします)。
The content of this page is licensed under Creative Commons.



2. 金融商品とプライシングエンジン

多くの問題に、多くの解決法

金融ライブラリは金融商品を価格付けする方法を提供しなければならないという声明は、間違いなく自明の理です。しかしながら、それは全体の問題のほんの一部であり、金融ライブラリは新しい価格付け機能性を追加することでそれを拡張するという方法も開発者に提供しなければなりません。ありうる拡張は2種類あり、ライブラリはどちらの方法も許可しなければいけません。

ひとつは、新しい金融商品を追加することが可能でなければならないことであり、もうひとつは既存商品の価格付けの新しい方法を追加することが可能であるということです。両方とも解決法が満足しなければならない多くの要求、もしくは模範的な用語で言うと圧力があります。この章では、そのような要求を詳しく述べ、QuantLibがそれらを満足させる設計を説明します。

2.1 Instrument(商品)クラス

私たちの領域では、金融商品はそれ自体で概念になります。これだけの理由で、自尊心のあるオブジェクト指向プログラマは、特定の商品が派生される基底クラスとしてそれをプログラムするでしょう。

QuantLibでそのようなクラスを定義するのは当然の決定です。このことは多くの他の利点になります。最も明白であるのは、結果としてユーザが一様に商品を管理できるということです。これは実際の金融の現場でよく行われます。たとえば、ある人がポートフォリオ(商品の集合)全体の価値に興味があるかもしれません。結果を得る自然な方法は、商品を循環させその値を収集することです。C++ではこれは、基底クラスへのポインタのコンテナで商品を保管し、共通のインターフェイスを通じて順番にそれらを検査するということになります。

2.1.1 インターフェイスと要件

経験する最初の設計決定では、私たちは新たに初めて使用するInstrumentクラスの詳細を始めました。第一の問題は、パブリックのインターフェイスの発見であり、それはすべての金融商品に共通のメソッドを含まなければなりませんでした。しかしながら、トレードされる資産の多様性(もっとも単純からもっともエキゾチックまで変動する)は次のことを示唆します。商品(たとえばエクイティオプション)の与えられたクラスに特有のいかなるメソッドも他の種類の商品(たとえば金利スワップ)に対しては意味をなさないようでしょう。。したがってほんのわずかのメソッドが、一般的であるとしてInstrumentインターフェイスに属することができるくらいに選び抜かれました。私たちは自分自身を、現在価値(もしかすると関連の誤差推定も一緒に)を返すものと商品が満期を迎えたかどうかを示すものへ制約しました。結果としてのインターフェイスはリスト2.1に示されます。

  • リスト2.1: Instrumentクラスの前置きのインターフェイス
class Instrument {
    public:
        virtual ~Instrument();
        virtual Real NPV() const = 0;
        virtual Real errorEstimate() const = 0;
    virtual bool isExpired() const = 0;
};

良いしきたりの通り、メソッドは純粋仮想として定義されました。しかし、ガーシュウィンののポギーとベスでスポーティン・ライフが指摘したようにそれほど必要ではありません。基底クラスでコード化されるいくつかの振る舞いがあるかもしれません。これがその場合なのか調べるために、私たちは一般的な金融商品から予想されるべきことを分析し、一般的な方法でそれが実装できるか確認しなければなりませんでした。二つのそのような要求が違う時間に発見され、それらの実装はライブラリの開発中に変化しました。私は現在の形でここにそれらを示します。

一つは、与えられた金融商品は、継承の方法をとることなく必要なく異なる方法(たとえば、一つ以上の解析式もしくは数値的方法)で価格付けされるかもしれないということです。この時点で、パターンをよく知っている読者は「Strategyパターン」を考えるでしょう。確かにその通りです。私は2.2節をその実装へ捧げます。

二つ目の要求は、金融商品の値はマーケットデータに依存するという観察からきました。そのようなデータは時間での自然の変数によるために、商品の値は次々に変化します。可変性の他の原因はいかなる単一のマーケットデータが異なる源から提供されるということです。私たちは、金融商品がこれらの源にリンクするようにしたかったため、異なる呼び出しに応じてそのメソッドは最新の値にアクセスし、それに応じて結果を再計算するべきでした。また私たちは、透過的に源の間を変更し、商品が単にデータ値の変化であるとしてこれを扱うようにしたかったです。

しかしながら、これに伴い効率性の潜在的損失がおこります。たとえば私たちは、コンテナに商品を保管することで、時間内にポートフォリオの値をモニターし、定期的にその値を投票し、そして結果を加えることができたかもしれません。解説してきた実装ではこれは、その入力が変わっていない商品に対してもさえ再計算を誘発するでしょう。したがって私たちは、商品のメソッドにキャッシュ機構を追加することを決めました。直前の結果が保存され、入力のいずれかが変化した際にのみ再計算される仕組みです。

2.1.2 実装

商品の値のキャッシュと再計算を管理するコードは、二つのデザインパターンを用いて一般的な金融商品のために書かれました。

入力のいずれかが変化した時、商品はObserverパターンの方法で通知されます。パターン自身は付録Aで簡単に述べられています。私は登場人物の特徴をここで述べます。

明らかなように商品は観察者の役割を演じ、一方で入力データは観察対象の役割を演じます。変化が通知された後で新しい値にアクセスするために、入力を表現するオブジェクトへの参照を観察者が保持する必要があります。これはある種のスマートポインタを提案するかもしれませんが、ポインタの振る舞いは完全に問題を記述するのに十分ではありません。すでに説明したように、データ供給者からの値は時間で変化するという事実だけではなく、私たちは異なるデータ供給者へスイッチしたいと思うかもしれません。(スマート)ポインタを保持することは指し示されたオブジェクトの現在値へアクセスすることを認めますが、観察者にとってプライベートであるポインタのコピーは異なったオブジェクトを指し示すことができませんでした。したがって私たちが必要なものは、ポインタへのスマートなポインタの等価物です。この特徴はQuantLibではクラステンプレートとして実装され、Handleの名前を与えられています。詳細は付録Aで与えられますが、この議論への関連は、与えられたHandleのコピーはオブジェクトへのリンクを共有するという事実です。リンクが他のオブジェクトを示されたとき、すべてのコピーが通知されそれらのハンドラーが新しいポインティにアクセスすることを可能にします。さらにHandleは指し示されたオブジェクトからのどんな通知でもその観察者へ転送します。

もう一つの問題は、継承先のクラスがいかなる特殊な計算を実装するようにしながら、キャッシュされた結果を保持し再計算するためのコードを抽象化することでした。これはTemplate Methodパターンの方法でなされました。以前のバージョンのQuantLibでは、その機能性はInstrumentクラスそれ自体に含まれていました。後にそれは抽出され今ではライブラリのほかの部分でも再利用されている他のクラスへコード化されました(それは驚きに値しませんが、LazyObjectと呼ばれています)。クラスの概要はリスト2.2で示されます。

  • リスト2.2: LazyObjectクラスの概要
class LazyObject : public virtual Observer ,
                   public virtual Observable {
    protected :
        mutable bool calculated_ ;
        virtual void performCalculations () const = 0;
    public :
        void update () { calculated_ = false ; }
        virtual void calculate () const {
        if ( ! calculated_ ) {
            calculated_ = true ;
            try {
                performCalculations () ;
            } catch ( ... ) {
                calculated_ = false ;
                throw ;
            }
        }
    }
};

コードは十分に単純です。論理型のデータメンバーcalculated_が定義され、それは結果がまだ有効であるかのトラックを保持します。updateメソッド(これはObserverインターフェイスを実装し観察対象からの通知により呼ばれます)は、その論理型が偽であると設定し従って以前の結果を無効化します。

calculateメソッドはTemplate Methodパターンの方法で実装されます。4人のギャングたち(GoF)の本で説明されたように、アルゴリズムの不変の部分(この場合キャッシュされた結果の管理)は基底クラスで実装され、可変の部分(ここでは実際の計算)はperformCalculataionsと名付けた仮想関数(これは基底クラスのメソッドの主要部で呼ばれます)へ任せます。その結果、派生クラスはキャッシング(関連するコードは基底クラスに投入されます)に気を配らずに特定の計算だけを実装するだけになります。

キャッシングの論理は単純です。現在の結果がもはや有効でないなら、私たちは派生クラスが必要となる計算を行い、そして新しい結果が最新であるとフラグを立てるようにします。現在の結果が有効であるなら、私たちは何も行いません。

しかしながら、実装はそれほど単純ではありません。私たちはなぜtryブロックが前もってcalculated_を設定し、ハンドラが例外を投げる前に変更を元に戻さなければいけなかったのかと、あなたが考えるのはもっともでしょう。 つまり、私たちはアルゴリズムの本体をより単純に書くことができたと言うことです。たとえば以下は一見等価な実装に見えます。

if ( ! calculated_ ) {
    performCalculations();
    calculated_ = true;
}

上のように書かなかった理由は、performCalculationsが再帰的に呼ばれること(たとえば、怠惰なオブジェクトが怠けてブートストラップさせる金利の期間構造であるとき)が起こる場合があるためです。もしcalculated_が真に設定されなければ、if条件節はまだ有効であり、performCalculationsは再び呼ばれ無限の再起を引き起こすでしょう。そのようなフラグが真であると設定することは、これが起こるのを防ぎます。しかしながら、例外が投げられたならそれを偽であると復元するように注意を払います。備えつかられたエラーハンドラがキャッチできるようにそれから例外が再び投げられます。

さらに少しのメソッドが、ユーザが結果の再計算を避けるもしくは強制することを可能にするLazyObjectで与えられます。それらはここでは議論しません。私は興味のある読者にマスター・オビ=ワン・ケノービがよく与えた助言を繰り返します。「ソースを読むのだ、ルーク。」

InstrumentクラスはLazyObjectから派生します。リスト2.1でまとめたインターフェイスを実装するために、それは金融商品に特有なコードで計算法を飾り付けます。結果としてのメソッドは、他の少しの支援コードとともにリスト2.3で示されます。

  • リスト2.3:Instrumentクラスの引用
class Instrument : public LazyObject {
    protected :
        mutable Real NPV_;
    public :
        Real NPV() const {
            calculate();
            return NPV_;
        }
        void calculate() const {
            if ( isExpired () ) {
                setupExpired();
                calculated_ = true;
            } else {
                LazyObject::calculate();
            }
        }
        virtual void setupExpired() const {
            NPV_ = 0.0;
        }
};

またしても、追加されたコードは商品特有の計算を派生クラスへ委託するTemplate Methodパターンに追随します。クラスは、計算結果を保持するNPV_データメンバを定義します。派生クラスは特有の結果を保持するほかのデータメンバを宣言することができます。計算メソッドの主要部は、商品が満期後のものかをチェックする仮想のisExpiredメソッドを呼びます。もしその場合は、setupExpiredと名付けられた他の仮想関数(これは結果に意味のある値を与える責任を持ちます。デフォルトの実装はNPV_へ0をセットし、また派生クラスで呼ぶことができます)を呼びます。calculated_フラグはそれから真に設定されます。もし商品が満期でない場合、LazyObjectのcalculateメソッドが代わりに呼ばれ、それは今度は必要に応じてperformCalculationsを呼びます。これは後のメソッドに契約を義務付けます、つまり、派生クラスでの実装は計算結果を(他の商品特有のデータメンバと同様)NPV_へセットすることが要求されます。最終的にNPVメソッドはcalculateが結果を返す前に呼ばれることを保証します。

2.1.3 例:金利スワップ

私は特定の金融商品が説明した容易さに基づいてどのように実装できるかを示すことでこの説を終わりにします。

選択した商品は金利スワップです。利用者が確かに知っているように、定期的なキャッシュフローの交換が存在する約定です。その商品の正味の現在価値は、キャッシュフロー額が支払い受取りかに基づいて、割り引いたキャッシュフローを加算または減算することで計算されます。

驚くことではないですが、スワップはInstrumentから派生する新しいクラスとして実装されます。その概要はリスト2.4で示されます。データメンバーとして計算に必要なオブジェクトを含みます。つまり第1と第2レグとそれらの額を割り引くために使われる金利の期間構造、そして追加の結果を保持する2つの変数です。さらにInstrumentインターフェイスとスワップ特有の結果を返すものを実装するメソッドを定義します。Swapとその関連するクラス図は図2.1(オリジナルのpdf参照)で示されます。

  • リスト2.4:Swapクラスの部分的なインターフェイス
class Swap : public Instrument {
    public:
        Swap(const vector<shared_ptr<CashFlow> >& firstLeg,
             const vector<shared_ptr<CashFlow> >& secondLeg,
             const Handle<YieldTermStructure>& termStructure);
        bool isExpired() const;
        Real firstLegBPS() const;
        Real secondLegBPS() const;
    protected :
        // メソッド
        void setupExpired() const;
        void performCalculations() const;
        // データメンバー
        vector<shared_ptr<CashFlow> > firstLeg_, secondLeg_;
        Handle<YieldTermStructure> termStructure_;
        mutable Real firstLegBPS_, secondLegBPS_;
};

この節の残りで、私はクラスの実装をレビューします。関連するメソッドはリスト2.5で示されます。

  • リスト2.5:Swapクラスの部分的な実装
Swap::Swap(const vector<shared_ptr<CashFlow> >& firstLeg,
           const vector<shared_ptr<CashFlow> >& secondLeg,
           const Handle<YieldTermStructure>& termStructure)
: firstLeg_(firstLeg), secondLeg_(secondLeg),
  termStructure_(termStructure) {
    registerWith (termStructure_);
    vector<shared_ptr<CashFlow> >::iterator i;
    for ( i = firstLeg_.begin(); i != firstLeg_.end (); ++i )
        registerWith( *i );
    for ( i = secondLeg_.begin() ; i != secondLeg_.end(); ++i )
        registerWith( *i );
}
 
bool Swap::isExpired() const {
    Date settlement = termStructure_−>referenceDate();
    vector<shared_ptr<CashFlow> >::const_iterator i ;
    for ( i = firstLeg_.begin(); i != firstLeg_.end(); ++i )
        if ( !( *i )>hasOccurred( settlement ) )
            return false;
    for ( i = secondLeg_.begin(); i != secondLeg_.end(); ++i )
        if ( !( *i )>hasOccurred( settlement ) )
            return false;
    return true;
}
 
void Swap::setupExpired() const {
    Instrument::setupExpired();
    firstLegBPS_= secondLegBPS_ = 0.0;
}
 
void Swap::performCalculations() const {
    NPV_ = − Cashflows::npv( firstLeg_, **termStructure_ )
           + Cashflows::npv( secondLeg_, **termStructure_ );
    errorEstimate_ = Null<Real>();
    firstLegBPS_ = −Cashflows::bps( firstLeg_, **termStructure_ );
    secondLegBPS_ = Cashflows::bps( secondLeg_, **termStructure_ );
}
 
Real Swap::firstLegBPS() const {
    calculate();
    return firstLegBPS_;
}
 
Real Swap::secondLegBPS() const {
    calculate ();
    return secondLegBPS_;
}

クラスをInstrumentフレームワークに合わせることは3ステップで終了し、3番目は派生されたクラスに応じて任意です。最初のステップはクラスのコンストラクタで行われ、それは交換されるキャッシュフローの2つの列と額を割り引くために使われる金利の期間構造を引数として取ります(そして対応するデータメンバーへコピーされます)。そのステップ自体は、キャッシュフローと期間構造の両方の観察者としてスワップを登録するためにあります。以前に説明したように、これにより変化が起こるたびに観察対象がスワップへ通知し、再計算を引き起こすことを可能にします。

2つめのステップは、要求されたインターフェイスの実装です。isExpiredメソッドのロジックは、十分に単純です。その主要部は、保持されたキャッシュフローの支払日をチェックしながらループします。まだ起こっていない支払を見つけるとすぐに、スワップは満期を迎えていないと報告します。何も見つからないなら、商品は満期を迎えたとなります。この場合、setupExpiredメソッドが呼ばれます。その実装は基底クラスのそれを呼び、従ってInstrumentから継承されたデータメンバーを引き受けます。それからスワップ特有の結果に0をセットします。

最後の要求されたメソッドは、performCalculationsです。計算はCashflorクラスからの2つの外部関数を呼ぶことで行われます。最初の呼び出しつまりnpvは、上で延べたアルゴリズムの率直な翻訳です。将来のキャッシュフローの割り引かれた額を足しながらキャッシュフロー列をまわします。私たちはNPV_変数に2つのレグからの結果の差をセットします。2番目の呼び出しbpsは、キャッシュフロー列のベーシスポイントの感受性(BPS)を計算します。私たちはそれをレグにつき1回呼び、結果を対応するデータメンバーへ保存します。結果は数値誤差を生まないために、errorEstimate_変数はNull<Real>() (無効な数を指し示す番人の数値として使われる特殊な浮動小数点数)にセットされます。

3番目と最後のステップは、この場合のように、もしクラスが追加の結果を定義するなら行われます。それは、保持された結果を返す前に計算が(なまけて)行われることを保証する、対応したメソッド(ここれは、firstLegBPSとsecondLegBPS)を書くことにあります。

実装はこれで完了です。Instrumentクラスの上で書くことで、Swapクラスはそのコードから利を得ます。したがって、登録呼び出しを除きSwap内に関連コードを書かないにもかかわらず、入力からの通知に従い自動的に結果をキャッシュし再計算するでしょう。

2.1.4 さらなる開発

あなたは、前の例とInstrumentクラス全般の私の扱いに関する欠点に気づいたかもしれません。 一般的にもかかわらず、私たちが実装したSwapクラスは、2つのレグが異なる通貨で支払われる金利スワップを扱うことができません。 もしユーザが同じ通貨でない2つの商品の価値を足し合わせようとしたら、同様の問題は起こるでしょう。ユーザは2つを加算する前に、1つの価値をもう1つの通貨へ手動で変換しなければならないでしょう。

そのような問題は、この実装の1つの弱点です。私たちは、商品やキャッシュフローの価値を表現するためにReal型(つまり単純な浮動小数点数)を使いました。 したがってその結果は、現実世界で付属される通貨情報を持っていません。

もしそのような結果をMoneyクラスの方法で表現するなら、その弱点は取り除かれます。 そのようなクラスのインスタンスは、通貨情報を含んでおり、さらにユーザの設定により、加算や減算の際、自動的に共通通貨へ変換を行うことができます。

そのような開発は、私たちのto-doリストにあります。しかしながらかなり大きな変更でありために、多くの方法に基づいたコードの大部分に影響を与えます。 したがって、リリース1.0では現れません。1.xシリーズである形で導入されるかもしれませんし、将来のリリース2.0の何時かまで待つかもしれません(このために固唾を飲まないでください。数年かかるでしょう)。

別の(より微妙な)弱点は、Swapクラスは抽象化を表現する2つの要素間を明示的に区別することを欠いていることです。 すなわち、約定(キャッシュフローの詳細)を説明したデータと、商品を価格付けるために使われるマーケットデータ(現在のディスカウントカーブ)の明確な分離がありません。

その解決法は、商品に最初の種類のデータ(つまりタームシートに現れるもの)のみを保持し、マーケットデータを別所に確保することです。 これを行う方法は次の節の主題です。

2.2 プライシングエンジン

私たちは前の節で述べた2番目の要求に移ります。 いかなる与えられた商品に対し、唯一のプライシング手法が存在するということは常に当てはまるわけではありません。さらにある人は異なる理由のために複数の方法を使いたいかもしれません。 古典的な教科書の例、ヨーロピアン株式オプションは、私の言い分を述べるのを助けます。 市場価格からのインプライドボラティリティを検索するために解析的なブラック・ショールズ公式の方法を使って、キャリブレートしそれをよりエキゾチックオプションで使うために確率ボラティリティモデルの方法を使って、解析解と比較し有限差分実装を確認するために有限差分スキームの方法を使って、もしくはヨーロピアンオプションをより複雑なものの制御変量として使うためにモンテカルロモデルの方法を使って、このように同一人物が価格付けを行いたいかもしれません。

したがって、私たちは単一の商品が異なる方法で価格付けされることを可能にしたいです。 これが単一の商品型に対して異なるクラスを使うことを強制するような、performCalculationsメソッドの異なる実装を与えることはもちろん望ましくありません。 私たちの例では、基底のEuropianOptionクラスからAnalyticEuropeanOption、McEuropeanOptionと他が派生されるとして結末をつけます。 これは少なくとも2通りで間違いです。概念レベルでは、ヨーロピアンオプションがヨーロピアンオプションであることはヨーロピアンオプションであるとガートルード・スタインが言ったように、単一のものが必要とされる時に異なる実体を導入すべきです。 有用性のレベルでは、実行時にプライシング手法を切り替えることを不可能にします。

解決法は、Strategyパターンを使うこと、つまりおこなわれる計算をカプセル化したオブジェクトを商品がを取るようにします。 私たちはそのようなオブジェクトをプライシングエンジンと呼びました。 与えられた商品は、多数の利用可能なエンジンのいずれか1つを取り、選択されたエンジンに必要な引数を渡し、商品価格と他の要求される量を計算させ、結果を持ってくることができます。したがって、performCalculations()メソッドは、大まかに以下のように実装されます。

void SomeInstrument::performCalculations() const {
    NPV_ = engine_−>calculate (arg1, arg2 , . ., argN);
}

ここで私たちは、仮想のcalculateメソッドはengineインターフェイスで定義され具体的なエンジンで実装されると仮定します。

残念ながら上記のアプローチは、まだ残念な点が残ります。 理想的に私たちは、ディスパッチするコードを単に一回だけ、つまりInstrumentクラスで実装したいです。 しかしながらその書かれたコードは、エンジンが引数の特定の数と型を受け入れることになるため、十分にジェネリックではありません。 商品クラスはおそらく数と型が幅広く異なるデータメンバを持つために、これは明らかに受容できません。 返却される結果に対してもそれは同様です。例えば、利スワップは固定レートと変動スプレッドに対するフェアバリューを返すかもしれませんし、よくあるヨーロピアンオプションは何個かのグリークスを返すかもしれません. 上で概説したものとしての、メソッドを通じて引数をエンジンへ渡すインターフェイスは、したがって2つの望ましくない結果となります。 一つは、異なる商品に対するプライシングエンジンは、異なるインターフェイスを持つだろう点で、それは私たちが単一の基底クラスを定義することを妨げます。 もう一つは、エンジンを呼ぶためのコードは、それぞれの商品クラスで複写されるだろう点です。 このように狂気があります。

私たちが選択した解決法は、引数と結果がいみじくも引数と結果と呼ばれる不透明な構造体の方法で渡されそしてエンジンから受け取ります。 これらから派生しそして商品特有のデータを持つそれらを引数として渡す二つの構造体は、どんなプライシングエンジンでも保持されます。商品は、エンジンと情報を交換するためにそのようなデータを書き込みそして読み込みます。

リスト2.6は、結果としてのPricingEngineクラスのインターフェイスと内部引数と結果クラスさらに手助けGenericEngineクラステンプレートを示します。 後者はPricingEngineインタフェースの大部分を実装し、calculateメソッドの実装のみを特定のエンジンの開発者へ残します。 データのためのドロップボックスとして彼らの使用を楽にするメソッドが引数と結果クラスへ与えられたと見れます。 入力データが書かれた後にarguments::validateがその値が有効範囲におさまっているかを確認するために呼ばれ、エンジンが計算を開始する前にresults::resetが前回の結果を空にするために呼ばれます。

  • リスト2.6: PricingEngineと関連するクラスのインターフェイス
class PricingEngine : public Observable {
    public:
        class arguments;
        class results;
        virtual ~PricingEngine() {}
        virtual arguments* getArguments() const = 0;
        virtual const results* getResults() const = 0;
        virtual void reset() const = 0;
        virtual void calculate() const = 0;
    };
 
class PricingEngine::arguments {
    public:
        virtual ~arguments() {}
        virtual void validate() const = 0;
};
 
class PricingEngine::results {
    public:
        virtual ~results() {}
        virtual void reset() = 0;
} ;
 
// ArgumentsTypeはartumentsからの派生されなければならない。
// ResultTypeはresultsからである。
template<class ArgumentsType, class ResultsType>
class GenericEngine : public PricingEngine {
    public:
        PricingEngine::arguments* getArguments() const {
            return &arguments_;
        }
        const PricingEngine::results* getResults() const {
            return &results_;
        }
        void reset() const { results_.reset(); }
    protected:
        mutable ArgumentsType arguments_;
        mutable ResultsType results_;
};

新しいクラスを用意したので、私たちは一般的なperfomCalculationメソッドを書く課題を終えることがここでできます。 すでに述べたStrategyパターンに加え、私たちは与えられた商品が足りないビットを満たすTemplate Methodパターンを使います。 結果としての実装はリスト2.7で示されます。 内部クラスInstrument::resultが定義されたことを述べておきます。そのようなクラスはPricingEngine::resultsを継承し、どんな商品に対しても提供されなければならない結果を保持します。

  • リスト2.7: Instrumentクラスの抜粋
class Instrument : public LazyObject {
    public:
        class results;
        virtual void performCalculations() const {
            QL_REQUIRE(engine_ , "null pricing engine");
            engine_->reset();
            setupArguments(engine_->getArguments());
            engine_->getArguments()->validate();
            engine_->calculate();
            fetchResults(engine_->getResults());
        }
        virtual void setupArguments(
                        PricingEngine::arguments *) const {
            QL_FAIL ("setupArguments() not implemented");
        }
        virtual void fetchResults(
                        const PricingEngine::results* r) const {
            const Instrument : : results* results =
                dynamic_cast<const Value*>(r);
            QL_ENSURE (results != 0, "no results returned");
            NPV_ = results->value;
            errorEstimate_ = results->errorEstimate;
        }
        template <class T> T result (const string& tag) const;
    protected :
        boost::shared_ptr<PricingEngine> engine_;
};
 
class Instrument::results
        : public virtual PricingEngine : : results {
    public:
        Value() {reset();}
        void reset() {
            value = errorEstimate = Null<Real>();
        }
        Real value;
        Real errorEstimate;
};

performCalculationに関しては、実際の仕事は多くの協働するクラス(商品、プライシングエンジンさらに引数と結果クラス)の間で分割されていることが分かります。 そのような(次の段落で説明される)協力の原動力は、図2.2(オリジナルのpdf参照)で示されるUMLシーケンス図の助けにより最もよく理解されるかもしれません。それらのクラス(とありうる現実の商品)の静的な関係は図2.3(オリジナルのpdf参照)で示されます。

商品のNPVメソッドの呼び出しは、(もし商品が満了しておらず、関係する数量が計算される必要があるなら)最終的にperformCalculationsメソッドの呼び出しを引き起こします。 ここは、商品とプライシングエンジンの相互作用が起こる箇所です。 まず最初に、商品はエンジンが利用可能か確認し、その場合でないなら計算を終了します。 もしエンジンが発見されたら、商品はそれに対して自分自身をリセットするように促します。メッセージは、resetメソッドの用法で商品特有の結果構造体へ転送され、実行の後に、構造体は新しい結果を書き込む準備のできたクリーンな状態になります。

この時点で、Template Methodパターンが舞台に入ります。 商品がプライシングエンジンに、引数構造体を要求し、それは引数へのポインタとして返されます。 ポインタはそれから、商品のsetupArgumentsメソッドへ渡され、それはパターンで可変の部分として動作します。特定の商品に依存して、そのようなメソッドは、渡された引数が正しい型か確認し、データメンバに正しい値を満たすことに取り組みます。 最終的に、引数は、validateメソッドを呼ぶことにより新しく書かれた値に対していかなる必要な確認を行うことを要求されます。

ステージはここで、Strategyパターンの準備が整います。引数セット、選択されたエンジンは、calculateメソッドで実装された、特有の計算を行うように要求されます。 そのような処理の間、エンジンは引数構造体から必要とする入力を読み込み、対応する出力を結果構造体へ書き込むでしょう。

エンジンが仕事を終えた後、制御はInstrumentインスタンスへ戻り、そしてTemplete Methodパターンは展開を続けます。 ここで、呼ばれたメソッドfetchResultsがエンジンに結果を要求し、それらを収容されたデータへのアクセスを得るためダウンキャストし、ししてそのような値を自身のデータメンバへコピーしなければいけません。 Instrumentクラスは、すべての商品に共通の結果を取り込むデフォルトの実装を定義しており、派生クラスはそれを特有の結果を読むように拡張するかもしれません。

2.2.1 例:プレーンバニラオプション

解説した手段が、ある特有の商品を実装するためにどのように使われるかを示す例がここで必要になります。 しかしながら、この目的はライブラリ作者に衝突します。つまり、コードを再利用し、既存のものから新しいクラスを作ることを簡単にするために多くの商品に共通な抽象概念を探すということです。 たとえば、QuantLibにはプレーンバニラオプション(つまりヨーロピアン、アメリカン、バミューダン行使のいずれかの単純なコールとプットの株式オプション)を実装するクラスが存在していますが、そのようなクラスは実際はクラス階層の末端の葉です。 商品クラスをルートに持つようにするため、そのような階層は最初Optionクラスで特殊化し、その後、単一の原資産のオプションを一般化したOneAssetOptionクラスを特殊化し、私たちが興味のあるVanillaOptionクラスを最終的に定義するまで、さらに1、2クラス通り抜けました。

この増殖には良い理由があります。たとえば、OneAssetOptionクラスでのコードは、アジアンオプションで自然に再利用できますが、すべての種類のバスケットオプションを実装する際Optionクラスのコードは再利用に役立ちます。 残念ながらこれは、プレーンオプションを価格付けするコードが、説明した継承の連鎖のすべてのメンバ中に分布するということになり、極端にわかりやすい例には役立ちません。 したがって、私は妥協しなければなりませんでした。 この節では、私はライブラリのクラスと同様の実装ですが、商品クラスから直接継承し、統合されたVannilaOptionクラスを説明します。中間クラスで実装されていたすべてのコードは、インライン、つまり継承ではなくあたかも例題クラスで実装されたかのように示されます。

(以下翻訳中)

最終更新:2010年12月06日 22:25