2.Implementing QuantLib の和訳

Chapter-III Term Structures

3.1 The TermStructure Class

 現在のTerm Structuresベースクラスは、そのデザインを後から決めた典型的な例です。(オブジェクト指向プログラミングの開発では)様々な検討を重ねた後、ベースクラスの定義にたどり着く事もあります。このModel Libraryの開発を始めた当初、(Term Structureのオブジェクトモデルをどの様に作るか)まだ思い至っていませんでした。最初は、必要に応じてTerm Structureのクラスをいくつも作っていきました。しかし数年後、我々も少し賢くなり、それまで作ったいくつものTerm Structureの構成を見直した上で、そこから共通の性質を抽象化し、抽出しました。それによって出来たのがこの章で説明する今のTermStructureクラスです。 

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

抽象化されたTermStructureベースクラスのインターフェースを以下のListing3.1に示しますが、このクラスには3つの基本的な役割(このクラスが提供すべき関数やメソッド)があります。 

Listing3.1 TermStructureクラスのインターフェース 

class TermStructure : public virtual Observer, public virtual Observable, public Extrapolator {
      public:
        TermStructure(const DayCounter& dc = DayCounter());
        TermStructure(const Date& referenceDate,
                      const Calendar& calendar = Calendar(),
                      const DayCounter& dc = DayCounter());
        TermStructure(Natural settlementDays,
                      const Calendar&,
                      const DayCounter& dc = DayCounter());
        virtual ~TermStructure();

        virtual DayCounter dayCounter() const;
        virtual Date maxDate() const = 0;
        virtual Time maxTime() const;
        virtual const Date& referenceDate() const;
        virtual Calendar calendar() const;
        virtual Natural settlementDays() const;
        Time timeFromReference(const Date& date) const;

        void update();
      protected:
        void checkRange(const Date&, bool extrapolate) const;
        void checkRange(Time, bool extrapolate) const;

        bool moving_;
    };

 まず、(このクラスがやるべき)最初の役割はReference Date(参照日、すなわちTerm Structureのスタート日)の情報をきちんと捉えること、すなわち、将来の日の基準となるスタートの日を決める事です。(注:この事は、すべてのTerm Structureに備わっている必須のものという訳ではないですが、ここではとりあえずこのように置いておきます)。VolatilityのTerm Structureでは、このReference Dateは、殆どの場合“本日”になります。金利のTerm Structureについても、おおむね“本日”としていいと思いますが、トレーディング担当部署によっては、すべての取引をSpot Start(訳注:取引慣行として、取引日に対して一定数の営業日の後に契約をスタートさせる日)としている場合、例えば金利スワップで言えば取引日の2営業日後を契約スタート日としている場合は、そのSpot DateをReference Dateとする選択肢もあります。その為、我々の作るTerm Structureクラスでは、必要があれば、こういった取扱いが出来るよう柔軟にしなければなりません。また、このReference Dateを外生的に指定することもあり得ます(たとえばReference Dateを含め将来の日付とそれに対応するDiscount Factorの配列が外から与えられるような場合)。最後に、Reference Dateを基準にした日数計算をすべて他のクラスにまかせてしまう事も想定されます。その様なケースを、この後の例で示します。いずれにしても、これらすべてのケースでReference Dateの取り込みはreferenceDate( )メソッドを使って行われます。これに関連するcalendar( ) やsettlementDays( )メソッドはそれぞれ、(日数計算に使われる)カレンダーの情報や、(本日からTerm Structureスタート日までの)実日数の情報を返します。(”settlement”は、通常、金融商品の決済日の意味をあらわすので、Term Structureで使う用語としてはふさわしく無いかもしれませんが、あしからず) 

 2番目の役割は、あたりまえの事ですが、Reference Dateから、ある将来の日付までの日数を年数に換算することです。すなわちReference Dateを t=0 年とし、Term StructureにおけるPillarとなる日付のシリーズを年数の実数値に換算する事です。そのような年数の値は、イールドカーブを使った数理モデルや、単にイールドカーブ上の値の変換(例えばZero Coupon RateからDiscount Factorへの変換)に使われたりします。この年数の計算はtimeFromReference( )メソッドを使って提供されています。 

3番目の役割は、これもあたりまえの事ですが、ある日付が、そのTerm Structureが想定している期間の範囲に入っているかどうかチェックする事です。Term Structureクラスは、その範囲の最後の日を特定する方法を、派生クラスに委託しています。すなわちmaxDate( )メソッドを実装する事で、それを特定します。またmaxTime( )メソッドがそれを補完し、オーバーロード関数であるcheckRange( )メソッドが、実際に範囲確認のチェックを行います。このクラスにはminDate( )メソッドは必要ありません。なぜならReference Dateがそれに該当するからです。 

3.1.2 Implementation 実装

TermStructureクラスの実装 

Listing 3.2: Implementation of the TermStructure class. 

TermStructure::TermStructure(const DayCounter& dc)
   : moving_(false), updated_(true),
     settlementDays_(Null<Natural>()), dayCounter_(dc) {}

   TermStructure::TermStructure(const Date& referenceDate,
                                const Calendar& calendar,
                                const DayCounter& dc)
   : moving_(false), referenceDate_(referenceDate),
     updated_(true), settlementDays_(Null<Natural>()),
     calendar_(calendar), dayCounter_(dc) {}

   TermStructure::TermStructure(Natural settlementDays,
                                const Calendar& calendar,
                                const DayCounter& dc)
   : moving_(true), updated_(false), settlementDays_(settlementDays),
     calendar_(calendar), dayCounter_(dc) {
       registerWith(Settings::instance().evaluationDate());
   }

   DayCounter TermStructure::dayCounter() const {
       return dayCounter_;
   }

   Time TermStructure::maxTime() const {
       return timeFromReference(maxDate());
   }

   const Date& TermStructure::referenceDate() const  {
       if (!updated_) {
           Date today = Settings::instance().evaluationDate();
           referenceDate_ = calendar().advance(today,settlementDays_,Days);
           updated_ = true;
       }
       return referenceDate_;
   }

   Calendar TermStructure::calendar() const {
       return calendar_;
   }

   Natural TermStructure::settlementDays() const {
       return settlementDays_;
   }

   Time TermStructure::timeFromReference(const Date& d) const {
       return dayCounter().yearFraction(referenceDate(),d);
   }

   void TermStructure::update() {
       if (moving_)
           updated_ = false;
       notifyObservers();
   }

   void TermStructure::checkRange(const Date& d,
                                  bool extrapolate) const {
       checkRange(timeFromReference(d),extrapolate);
   }

   void TermStructure::checkRange(Time t,
                                  bool extrapolate) const {
       QL_REQUIRE(t >= 0.0, "negative time (" << t >> ") given");
       QL_REQUIRE(extrapolate || allowsExtrapolation() || t <= maxTime(),
                  "time (" << t << ") is past max curve time (" << maxTime() << ")");
   }

(3.1.1.で説明した)最初の役割、すなわちReference Dateの情報の取り込みは、Term Structureクラスのインスタンスが生成される際に行われます。(上記Term Structureの実装をみて解るとおり)Reference Dateをどのように決めるかによって、それぞれ別のコンストラクターが呼び出されます。(注:このセクションを読む前に、Appendix Aにざっと目を通して、日数計算などで使われるクラス群の説明を読んで頂ければと思います。) それらすべてのコンストラクターにおいて、boolean値をもつ2種類のデータメンバーがそれぞれ初期化されます。ひとつはmoving_と呼ばれるメンバー変数で、trueならReference Dateを日付が変わる毎に更新することが可能になり、falseならReference Dateを固定します。2つ目はupdated_と呼ばれるメンバー変数で、referenceDate_に入っている日付の情報が、直近のものなのか、(日付が変わって)変更が必要になっているのかを示すものです。 

この実装では、3種類のコンストラクターが用意されています。ひとつ目は、引数としてDayCounterクラスのインスタンスを取り込みますが、referenceDate( )メソッドに必要な引数は取り込んでいません。(DayCounterは日数計算に必要なオブジェクトクラスで、詳しくは後で説明します。)従って、このベースクラスではReference Dateを計算できないので、派生クラスでこのコンストラクターを呼び出す場合、仮想関数であるreferenceDate( )メソッドをオーバーライドして実装してやる必要があります。コンストラクターが、moving_ をfalseに、 update_ を trueに設定しているので、ベースクラスでReference Dateの設定を出来ないようにしています。 

ふたつ目のコンストラクターは、引数に(Reference Dateとなる)日付、および任意引数としてカレンダークラスと日数計算クラスのインスタンスを呼び込んでいます。このコンストラクターが使われた場合は、reference dataは引数として取り込んだ日付に固定されるものと見做されます。引数の日付でメンバー変数であるreferenceDate_を初期化します。またmoving_ はfalse、 updated_ はtrueで初期化されます。  

3番目のコンストラクターは、‘日数’とカレンダーを引数として取ります。この場合、Reference Dateは本日の日付にその日数分だけ営業日を加えた日に設定されます。引数のカレンダーが営業日を決めるカレンダーになります。引数を対応するメンバー変数に代入するだけでなく、他のメンバー変数であるmoving_ はtrueに、updated_は(この段階では未だなんの計算も行われていないので)falseに設定されます。しかしこれだけでは終わりません。TermStructureオブジェクトは、日付が動いた場合は、その通知を受け、Reference Dateを更新する必要があります。(後でAppendix Aで説明しますが)Settingsクラスが、現在のEffective Date(訳注:この場合は価格計算の基準日)など、プログラム全体で必要とする基本情報を保持しているので、この(Observable)クラスに対しTermStructureクラスをObserverとして登録します。Settingsオブジェクト内のデータ(Effective Date)が変更になった場合、その事がObserverであるTerm Structureに通知され、update( )メソッドが実行されます。そのメソッドは、Reference Dateが変更になればupdated_変数をfalseに設定し、また同時にTermStructureオブジェクトに登録されているObserverオブジェクトにアップデートされた事を通知します。(訳注:TermStructureクラスは、Observerであると同時に、Observableにもなっている。すなわち両方のクラスから派生している。ObserverとObservableの関係性と動作は、Appendixのデザインパターンの所を参照) 

ごく簡単な動作しか行わないcalendar( )メソッドのようなinspector 関数の実装以外では、TermStructureクラスの最初の役割を実装していく作業は、referenceDate( )メソッドの実装で終了します。仮に、Reference Dateを改めて計算する必要がある場合(日付が変更になった場合)は、(Settingsオブジェクトから)Effective Dateの情報を取り出し、それに一定の営業日数を加え、それをreferenceDate_メンバー変数に代入し、それを(関数の呼び出し元に)返します。 

2番目の役割(Reference Dateから将来のPillarとなるべき日付までの日数を実数値で年数に変換すること)の実装は、もっと簡単です。なぜなら、その仕事はDayCounterクラスのインスタンスに任せてしまっているからです。そのようなDayCounter インスタンスは、TermStructureのコンストラクターの引数として取り込まれ、メンバー変数であるdayCounter_に代入されます。ある日付までの年数の計算はtimeFromReference( )メソッドが行っています。そのメソッドはReference Dateと渡された日付との差の日数を計算し、それを年数に換算する動作を、DayCounterインスタンスに委託しています。そのメソッドの中身で注目してほしい点は、DayCounterとReference Dateの情報を、メンバー変数から情報を取るのではなく、メソッドを呼び出して取得している事です。こうする必要がある理由は、既に述べたとおり、referenceDate( )メソッドは派生クラスでOverrideされる可能性があり、その場合referenceDate_のメンバ変数を全く使わないケースがあり得るからです。DayCounterインスタンスについても同様です。 

一部の読者は、このプログラミング方法に反対されるかもしれません。なぜなら、Kent Beckが使った言葉で表現すると、臭いコード(訳注:Bugを起こしやすいプログラム)だからです。この方法だと、TermStructureインスタンスが保持するdayCounter_ とreferenceDate_メンバー変数の情報と、メソッドで呼び出される情報が違ってくる可能性あります。これについては、私も悩みました。TermStructureクラスの古いバージョンでは、dayCounter( )メソッドを純粋仮想関数で宣言して、DayCounterのメンバー変数を(Term Structureベースクラスの中に)置きませんでした。しかし、Reference Dateについては、呼び出した情報をメンバー変数にCacheしておく必要があったため、必要悪としてメンバー変数を置きました。割れ窓理論のセオリー通り、ひとつの(必要)悪を認めた為、悪い習慣がDayCounter、Calendar、Settlement Days(といったメンバー変数を持たせる方向)へと広がっていった訳です。(様々なTerm Structureの派生クラスをプログラムする中で、結局すべての派生クラスでこれらの情報が必要と分かった為、ベースクラスでこれらの情報を持たせる方法に至った訳です) 

個別のTermStructureのインスタンスに、それぞれ、どのDayCounterオブジェクトが必要になるでしょうか?幸いなことに、実はあまり気にする必要はありません。もし、日付の情報だけを使ってTermStructureオブジェクトを操作する場合、(すなわち、Term Structureのコンストラクターに日付を渡してそれを構築し、その日付を使ってTermStructureからレートを取りだす場合)日数計算方法が何であっても同じ期間を示すので、Day Counterがきちんと動作する限りにおいて、問題ありません。DayCounterがきちんと動作するとは、異なる日付でも日数が同じであれば同じ実数値の年数を返すとか、(Day1+Day2)+(Day2+Day3)=Day1+Day3が成り立つこと。そのような動作をするDayCounterとしては、Actual / 360とActual / 365-fixedなどが該当します。同様に引数として、実数値の年数を直接渡す場合は、DayCounterを使う必要はありません。 

最後に、3番目の役割の実装内容について説明します。(将来の)有効な日付の範囲を定義する仕事は、派生クラスに委託され、そこでmaxDate( )関数を実装する必要があります。(ベースクラスではそのメソッドは純粋仮想関数として宣言されています。)その日に対応する実数値の年数は、maxTime( )メソッドで計算されますが、関数の動作としては単純にtimeFromReference( )メソッドを使ってReference Dateからmax Dateまでの年数を計算しています。このメソッドも派生クラスでOverrideできます。最後に2種類のcheckRange( )メソッドが用意されており、引数で取込んだ‘日付’あるいは‘年数’が、有効な範囲に入っているかチェックし、そうでない場合はException(例外処理)に飛ぶようになっています。引数に‘日付’を取り込む方のメソッドは、まずその日付を年数に変換し、もう一方の‘年数’を引数とするcheckRange( )メソッドを呼び出して同じ動作を行います。このチェック機能は、Term Structureの有効範囲を越えてExtrapolation(補外)の要求がある場合には、はずされます。こうするには、引数にboolean変数を使って指示するか、Term Structureクラスの派生元になるExtrapolationクラスで用意された機能を使います。Extrapolationは最長日を越えて使う場合のみ適用があり、手前のReference Dateをマイナスの方向にExtrapolationする事はできません。 

 

< Evaluation Dateに関する工夫 >

もしコンストラクターがEvaluation Dateの情報を設定しない場合は、Settingsクラスが持つ’本日’を基準にReference Dateを決めるデフォールト設定になっています。残念ながら、この場合は、エラーを起こす原因になり得ます。なぜなら‘本日’は夜の12時に(ObservableからObserverに連絡することなく)、自動的に変わってしまいます。もし日付を越えてバッチを走らせる場合は、(テレビドラマの)Heroという番組のHiro Nakamuraのように時間を止める超能力を使うしかありません。その方法は、Evaluation Dateを外生的に本日の日付として与えれば、オーバーナイトバッチをする際にも、日付を固定しておく事が可能です。 

知っておくと便利なもう一つの工夫は、すべてのTermStructureのEvaluation Dateを1日だけ進ませて、その他のデータはすべて同じにしたまま、ポートフォリオの価値を再計算すると、前日からのセータ(時間経過による時価の変動幅)の値が計算できます。 

 

<ライセンス表示>

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

目次

Page Top に戻る

// // //