2.Implementing QuantLib の和訳

Chapter-II Financial Instruments and Pricing Engines

2.1 The Instrument Class

われわれ(金融モデルのプログラマー)の世界では、Financial Instrumentsは、それ自体ですでに基本概念です。従って、自らをオブジェクト指向プログラマーと自認している人なら、まずこれをBase Classとして構成し、そこから様々な種類の金融商品の派生クラスを定義していくでしょう。 

その考え方をもとにすれば、例えば(そのBase Classが持つべき機能として)次のようなプログラムコードを書きたくなるのではないでしょうか 

 for (i = portfolio.begin(); i != portfolio.end(); ++i) 
	       totalNPV += i->NPV( );		

(訳注:ポートフォリオ配列の中に、どのような金融商品の派生クラスオブジェクトが含まれていても、ベースクラスで定義された共通のメソッドNPV( )で価格が返ってくるような仕組みにしておけば、様々な商品のポートフォリオの総額を計算する場合、上記のような簡単なコードで記述できるという事) 

このように関数を定義すれば、具体的な商品のタイプを気にする必要ありません。しかし、このプログラムコードではNPV( )のメソッドにどのような引数を渡せばよいのか解らないし、さらにどのようなメソッドを呼び出すのかも解りません。上記の一見無害に見えるCodeについても、一旦引いて、(このBase Classが持つべき)インターフェースについてよく考えてみる必要があります。 

2.1.1 Interface and Requirements: インターフェースと要件

金融市場では、-非常に単純な商品から非常に複雑な商品に至る-広範な商品が取引されており、ある特定の商品(例えば株式オプション)のクラスに特有のメソッドが、他の商品(例えば金利スワップ)では意味をなさないという事は十分起こり得ます。従ってInstrumentsベースクラスで定義すべき、すべての金融商品に共通のメソッドの数は極めて限られました。我々は、その共通するメソッドを、現在価値を返すメソッド(場合によっては推定誤差の値も含めて)と、その金融商品が満期を過ぎているかどうかを返すメソッドだけにしました。商品によって、(そのメソッドが)どのような引数を取るべきかについて、(最近改変されたC++11のvariadic templatesのような技術を使っても)、ベースクラスでは特定できない為、そのメソッドは引数を取らない形で宣言しています。従って、メソッドに必要な入力項目は、金融商品ごとに、保存されなければなりません。その結果、Instrumentクラスのインターフェースは下記Listing2.1のようになっています。 

Listing 2.1: Preliminary interface of the Instrument class. 

  class Instrument {
      public:
        virtual ~Instrument();
        virtual Real NPV() const = 0;
        virtual Real errorEstimate() const = 0;
        virtual bool isExpired() const = 0;
    };

プログラミングの慣行に従って、当初、各メソッドはすべて純粋仮想関数にしました。ただ、これは(ガーシュインのオペラPorgy and Bessに登場する麻薬密売人のSportin’ Lifeが指摘したように)必ずそうしなければならないというものではありません。ベースクラスで具体的に定義・実装した方が良いようなプログラムの動作があるかもしれません。本当にそうなのか調べるために、一般的な金融商品に期待される共通の動作が何であるのか、またそれを実行させる為のプログラムで共通するところはあるのか、よく分析してみる必要がありました。その結果、QuantLibの開発過程のなかで、2つの必要事項が見つかり、開発期間中に実装の方法を変更しました。ここでは、それらの現時点での実装内容を説明します。 

ひとつは、特定の金融商品について、価格計算の方法が複数存在し(例えば、解析解で価格計算をする方法と、数値解でそれを行う方法)、その実装を継承の仕組みを使わずにできないだろうかということです。読者の方はこれを聞いて、Strategy Patternを使えばいいのではと思われるかもしれません。その通りです。本チャプターのSection2.2で、その実装方法を説明します。 

もうひとつは、金融商品の価格は、市場データに依存しているという事実からくるものです。市場データは、本質的に時間の経過によって変化するものです。従って、その商品の価格も時間の経過によって変化していきます。また、金融商品の価格変化は、市場データを提供するソースが複数存在しうるという点からも発生します。我々は、ひとつの金融商品が複数のデータソースとリンクできるようにする必要があると考えました。その場合、価格計算のメソッドを呼び出した時は、その都度、最新データを使って金融商品の価格を再計算させるような仕組みが必要と考えました。また、複数のデータソースを任意に選択できるような仕組みも必要と考えました。そして、データソースを変更した場合は、その金融商品にとっては、市場データのアップデートと同じ取扱い(都度価格の再計算を行う)とすべきとも考えました。 

効率性の低下も関心事でした。例えば、多くの金融商品をひとまとめにして、定期的に時価を確認して、ポートフォリオの全体の価値を適宜モニターするとします。その際、単純な仕組みでは、時価の変更が無かった商品についても、すべて価格の再計算を行ってしまう可能性がありました。従って、Instrumentクラスのメソッドの中に、時価を一時保管する仕組みを取り入れました。時価の再計算は、市場データの更新があった場合のみ行い、それ以外の場合は、一時保管されている値を使うような仕組みです。 

2.1.2 Implementation: 実装

どの金融商品についても共通な、価格を一時保管したり、再計算したりする仕組みを司るプログラムコードは、2種類のデザインパターンを使って書かれています。(デザインパターンについては(下記 [1] の文献を参照) 

入力データに変更があった場合、InstrumentクラスはObserverパターンを使って、その変更の通知を受ける仕組みにしています。Observerパターンについては、Appendix Aを参照下さい。(但し、Appendixを読んだからといって、下記 [1] にある「Gang of Four(4人組)」の(有名な)本を読まない理由にしないでください。) とりあえず、ここではどのようにそれが使われているか解説します。 

(金融商品と市場データとの関係から)明らかなように、InstrumentクラスはObserverの役割を持ち、(Instrumentが価格計算で使う)市場データはObservableの役割をもっています。入力データ(訳注:市場データ)が更新された場合、それを(ObserverであるInstrumentに)通知されるような仕組みを構築する為には、Observerが、その入力データを保管しているオブジェクト(Observable)への‘参照’を保持しておく必要があります。このような仕組みにするには、何らかのSmart Pointer(訳注:メモリーリークを防ぐため、ポインターの指し示すメモリー領域の管理を自動的に行ってくれるポインター)が必要になると気づかれるでしょう。しかし、ポインターの仕組みだけでは、我々が必要としているメカニズムにとって十分ではありません。すでに説明した通り、入力データの変更は、市場価格が動く以外に、データソースの変更からも発生します。(訳注:例えば、同じ銘柄の株が複数の取引所で取引されている場合、データソースは一本だけではない)
 (Smart)ポインターを1つしか持たない場合、それが参照するオブジェクトの直近のデータのみにしかアクセスできません。我々が必要とするのは、ポインターを保持するポインターです。この仕組みは、QuantLibの中では、Handleという名前のクラス・テンプレートを使って実装されました。これについても、詳細はAppendix Aを参照下さい。ここで議論となっている点と関連する部分で言えば、ある特定のHandleの複数のコピーは、ひとつのオブジェクトに対するリンクを共有しているという点です。仮に、ポインターが別のオブジェクトを指すようになった場合、Handleのすべてのコピーにそれが通知され、それらのHandleを保持しているオブジェクト(訳注:Instruments)に対し、新しいポイント先(のデータ)を通知する仕組みになっています。さらに、Handleは、そこが指し示しているオブジェクト(訳注:市場データを保持するオブジェクト)から、それを監視するすべてのObserver(訳注:市場データをモニターしているInstruments)に対し、データ変更の通知を転送する仕組みを持っています。 

そして、市場データの役割を持ち、Handleクラスの中で保持できる、様々な種類のObservableクラスを実装しました。その中で、最も基本的なクラスがQuoteクラスで、市場価格を一個だけ保持する機能を持っています。金融商品の価格計算に必要な市場データ(Observable)は、その他にも、もっと複雑な構造を持つイールドカーブやボラティリティのTermStructureクラスなどがあります。(それらのクラスも、最終的には一個しかデータを持たないQuoteクラスのインスタンスに行きつきます。イールドカーブオブジェクトはBootstrappingを行うため、預金金利やスワップレートを保持するQuoteクラスのインスタンスを必要とします) 

Instrumentベースクラスが持つもう1つの課題は、具体的な価格計算のアルゴリズムは派生クラスで実装するとして、ベースクラスにおいて価格の再計算を行った後の値を一旦保持しておくメカニズムを、どのように抽象化するか、という事です。このメカニズムは、Template Methodパターンを使って導入しました(このパターンについても下記 [1] の本を参照)。QuantLibの初期の頃のバージョンでは、この仕組みをInstrumentクラスそのものに、含めていました。その後のバージョンで設計を変更し、その仕組みを抜き出して別のクラスに持たせることとしまた。そのクラスはLazyObjectという名前で呼ばれており、ライブラリの中で、他の用途にも使われています。このクラスの概要は、下記Listing 2.2に示します。 

Listing 2.2: Outline of the LazyObject class. 

    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;
                }
            }
        }
    }; 

プログラムコードは極めて単純です。bool型のメンバー変数 calculated_は、価格の計算結果が“直近のものかどうか”のフラッグを保持します。ここにあるupdate( )メソッドは、(ベースクラスである)Observerのメソッドを実装したものであり、Observableクラスから(入力データが更新されたという)通知が届いた場合に、メンバー変数calculated_をfalseにセットし、すでにある価格計算結果を、古いものとして扱います。 

calculate( )メソッドは、Template Methodパターンを使って実装されています。下記 [1] の4人組の本にある通り、メソッドの内容のうち、すべての派生クラスに渡って共通な動作はベースクラスで実装され(このケースでは一時保存された計算結果のフラッグの取扱い)、派生クラス毎に実装すべき部分については、仮想関数(このケースではperformCalculations( ) )に任されます。その仮想関数は、ベースクラスのメソッドの中で呼び出されます。従って、派生クラスでは、具体的な計算方法を(performCalculations( )の実装の中で)特定すればよいだけで、計算結果の一時保存について考える必要がありません。派生クラスの実装部分は、ベースクラスのメソッドの中に(コンパイルの際に)組み込まれます。 

計算結果の一時保存のロジックは単純です。もし、現在保存されている計算結果データがすでに古くなってしまった場合は(calculated_ フラッグがfalseにセットされている)、派生クラスにおいて価格の再計算を行い、calculated_フラッグを再びtrueに戻します。もし現在保存されているデータが最新分であれば何もしません。 

ロジックは簡単でも、実装はそれほど単純ではありません。まず、bool型であるcalculated_のフラッグ変更の手続きを、なぜtryブロックの手前で行う必要があったのでしょうか?また、catchブロックの中で、なぜcalculated_フラッグの設定を元に戻す操作を行ってからエラー処理へ飛ぶのでしょうか? もしかしたら、以下のようなプログラムコードであっても、同じ効果を持たせる事ができたのではないか、と考えられませんか? 

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

こうしなかった理由は、performCalculations( )メソッドが再帰的にcalculate( )メソッドを呼び出す場合が想定されるからです(例えばLazyObjectがイールドTermStructureで、Bootstrappingを行うような場合)。もしcalculated_をtrueにセットしなかった場合、上記構文中のif条件は継続してチェックされ、performCalculations( )が再び呼びだされ、無限ループに陥ってしまいます。(performCalculations( )が実行される前に)このフラッグをtrueにセットする事により、そういった事が防げます。しかし、(仮にperformCalculations( )の実行時に)エラーにより例外処理に飛ぶ場合は、その前に、このフラッグをfalseに戻しておく必要があります。その後、さらに再例外処理に飛び、インストールされているエラー処理Handlerにプロセスが移動します。 

LazyObjectには、さらにいくつかのメソッドが用意されており、ユーザーに再計算を行わせないようにしたり、逆に強制したりできるようになっています。これについてはここでは説明しません。興味がある方は、かの有名なObi-Wan Kenobiのアドバイスに従って下さい―“Read the Source, Luke”。 

InstrumentクラスはLazyObjectから派生しています。上で説明したLazyObjectのインターフェース(ここではcalculate( )メソッド)を、Instrumentクラス用にデコレート(訳注:Decorator Patternを使って、ベースクラスの仮想関数calculate( )に、派生クラスで機能を追加している)しています。その内容は、若干の追加のコードも含めて、下記Listing 2.3のとおりです。 

Listing 2.3: Excerpt of the Instrument class. 

    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パターンに従って、派生クラスに、そのInstrumentに固有な計算アルゴリズムの実装を委任できるようにしています。(訳注:このクラスの段階でperformCalculations()を実装しておらず、さらに派生するクラスでの実装を前提にしている。)このクラスは、価格計算結果を一時保存するメンバー変数、NPV_を持っています。派生クラスではそこで必要なメンバー変数を追加で宣言する事ができます。 (注:Instrumentクラスでは、ここでは書いていませんが、その他にerrorEstimate_変数が定義されており、NPV_に関する論点がそのまま適用できます)。  

calculate( )メソッドの本体は、仮想関数であるisExpired( )を呼び出して、その商品の期限が到来しているかどうかをチェックしています。かりに期限が到来しているのであれば、さらに仮想関数setupExpired( )を呼び出し、NPV_を適切な値にセットします。デフォールトの設定はNPV_を0に設定しますが、派生クラスで別の適切な値とする事も可能です。その後calculated_フラッグをtrueに設定します。もし、その商品がまだ満期を迎えていないのであれば、ベースクラスであるLazyObjectのcalculate( )メソッドが呼び出され、そのメソッドはさらにperformCalculations( )を呼びだします。このような仕組みをとると、performCalculations( )に一定の作業を必ずさせる必要があります。すなわちNPV_に計算結果を、(メンバー変数に)書き込む事です。(その他派生クラスに特有のメンバー変数への値の代入も必要になります。)最後に商品の価格(NPV)を返すNPV( )メソッドですが、NPV_の値を返す前に、calculate( )メソッドを呼び出して、その値をまず計算するようにしています。 

< const にすべきか否か? >

変数NPV_が、なぜmutableで宣言されているかの説明に興味を持たれるかも知れません。なぜなら、データの一時保存やlazy calculationを実装する際には、この問題が常に発生するからです。問題の要点は、NPV( )メソッドは、論理的にはconstメソッドであり、商品の価格計算は、データを変更しないという事です。従って、Libraryのユーザーは、そのようなメソッドをconstなインスタンスから呼び出しても、意図せざるデータ変更が為される事はないという事を期待してかまいません。その代わり、NPV( )メソッドがconstで宣言されているという事は、calculate( )や performCalculations( )などのメソッドもconstで宣言しなければなりません。しかし、我々が目的とするところの、価格計算をlazilyに行ったり、計算結果を一時保存したりする機能を実装しようとすると、そういったメソッドの本体中にデータ書き換えが可能な変数を必要とします。データを一時保存する為のメンバー変数をmutableとして宣言することによりそういった問題が解決されます。こうする事によって、我々開発者やのちの派生クラスの実装を行う開発者にとって、両方の目的、すなわちNPV( )メソッドのconst化と、メンバー変数のlazyな役割を担わせる事、が達せられます。

また、C++ 11では、constメソッドをマルチスレッドでも安全性を持たせる必要がある点を注意しなければなりません。すなわち、二つのスレッドが同時にconstなメンバーメソッドを呼び出す場合、競合しないようにしなければなりません(詳しくは 下記 [2] のSutterの著書を参照)。新しいC++のスタンダードに準拠するために、mutableとして宣言されたメンバー変数をmutexで保護しなければなりません。いずれ、デザインの変更が必要になってくると思われます。 

 

[1] E. Gamma, R. Helm, R. Johnson and J.Vlissides, Design Patterns: Element of Reusable Object-Oriented Software. Addison-Wesley, 1995. 

[2] H. Sutter, You don’t know const and mutable. In Sutter’s Mill, 2013.

<ライセンス表示>

QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。   ライセンス

目次

Page Top に戻る

// // //