2.Implementing QuantLib の和訳

Chapter-III Term Structures

3.2   Interest Rate Term Structures : 金利の期間構造

YieldTermStructureクラスの設計を始めたのは、そのベースクラスであるTermStructureクラスの設計よりも前でした。実の所、その頃は'金利'についてのTerm StructureをTermStructureというクラス名でオブジェクトライブラリを作り始めていました。なぜなら当時、Term Structureを取り扱ったのは金利しか無く、ライブラリの全体像がまだ見えていませんでした。そのクラスが持つインターフェースは、イールドカーブ全体の期間の中でなら、どんな日付でも、その時点の(ForwardあるいはSpotの)金利を導出したり、割引率を計算したりするメソッドを提供していました。また、具体的にイールドカーブを描く作業を簡単にするような仕組みも用意していました。 

3.2.1 Interface and Rquirements : インターフェースと実装

まずYieldTermStructureクラスの宣言のソースコードを示します。 

Listing 3.3: Partial interface of the YieldTermStructure class.

class YieldTermStructure : public TermStructure {
      public:
        YieldTermStructure(const DayCounter& dc = DayCounter());
        YieldTermStructure(const Date& referenceDate,
                           const Calendar& cal = Calendar(),
                           const DayCounter& dc = DayCounter());
        YieldTermStructure(Natural settlementDays,
                           const Calendar&,
                           const DayCounter& dc = DayCounter());

        InterestRate zeroRate(const Date& d,
                              const DayCounter& dayCounter,
                              Compounding compounding,
                              Frequency frequency = Annual,
                              bool extrapolate = false) const;

        InterestRate zeroRate(Time t,
                              Compounding compounding,
                              Frequency frequency = Annual,
                              bool extrapolate = false) const;

        DiscountFactor discount(const Date& d,
                                bool extrapolate = false) const;
        // same at time t

        InterestRate forwardRate(const Date& d1,
                                 const Date& d2,
                                 const DayCounter& dayCounter,
                                 Compounding compounding,
                                 Frequency frequency = Annual,
                                 bool extrapolate = false) const;
        // same starting from date d and spanning a period p
        // same between times t1 and t2

        // ...more methods
      protected:
        virtual DiscountFactor discountImpl(Time) const = 0;
    };

 コンストラクターは、引数を、ベースクラスであるTermStructureクラスへ渡しているだけであり、それ以外の情報をベースクラスに送っている訳ではありません。その他のメソッドは、金利のTerm Structureに関する情報を返しますが、二通りの方法で対応しています。金利のTerm Structureに関する情報を返すとは、ゼロ金利、フォワード金利、割引率などを(金利については、Appendix Aで説明するInterestRateクラスのインスタンスとして)返すメソッド群を用意しています。二通りの方法とは、これらのメソッドをオーバーロードして、引数として、日付を使う方法と、期間を使う方法を用意しています。 

ご存知のとおり、“ゼロ金利”と“フォワード金利”と“割引率”はそれぞれ密接にリンクしており、その内のどれか一つのカーブの情報があれば他の2つのカーブを導出できます(その計算式はあえて書きませんが、読者の方は十分ご存知かと思います)。この事は、下記のListing3.4の実装を見ればわかる通りです。ここでは“Template Method”パターンが使われており、すべてのパブリックのメソッドは、discountImpl( )という抽象化されたprotectedメソッドを直接あるいは間接的に介在させて実装されています。派生クラスで、このdiscountImple( )を実装するだけで、上記の3つの値を返す事ができます。(訳注:下記例では、パブリックメソッドであるzeroRate( ) とdiscount( )は、間接あるいは直接、discountImpl( )を呼び出して計算をしています。従ってパブリックメソッドがきちんと動作する為には、派生クラスでdiscountImpl( )がきちんと実装されていなければなりません。) 

Listing 3.4: Partial implementation of the YieldTermStructure class. 

InterestRate YieldTermStructure::zeroRate(
                                    const Date& d,
                                    const DayCounter& dayCounter,
                                    Compounding comp,
                                    Frequency freq,
                                    bool extrapolate) const {
        // checks and/or special cases
        Real compound = 1.0/discount(d, extrapolate);
        return InterestRate::impliedRate(compound,
                                         referenceDate(), d,
                                         dayCounter, comp, freq);
    }

    DiscountFactor YieldTermStructure::discount(
                                        const Date& d,
                                        bool extrapolate) const {
        checkRange(d, extrapolate);
        return discountImpl(timeFromReference(d));
    }
3.2.2 ディスカウントカーブ、フォワード金利カーブ、ゼロ金利カーブ

もし派生クラスを作ろうとする人がdiscountImpl( )を実装したくなかったら、どうなるでしょうか? 例えば、もし誰かがゼロ金利のイールドカーブだけを描きたいとしたら?そうした時の為に、(P.G. Wodehouseの小説に出てくる忠実で優秀な執事Jeevesのように、)QuantLibは、それに必要なクラスを2つ用意しています。その2つのクラスとは下記のListing 3.5にある通り、ZeroYieldStructureとForwardRateStructureです。この2つのクラスは、Adapterパターンを使って(これも、デザインパターンに関する有名な4人組の本にある、根幹となるパターンのひとつです。)、ディスカウントカーブをベースにしたYieldTermStructureクラスのインターフェースを、ゼロ金利カーブあるいは瞬間フォワード金利カーブを基にしたインターフェースにそれぞれ変換したものです。 L 

Listing 3.5: Outline of the ZeroYieldStructure and ForwardRateStructure classes. 

class ZeroYieldStructure : public YieldTermStructure {
      public:
        // forwarding constructors, not shown
      protected:
        virtual Rate zeroYieldImpl(Time) const = 0;
        DiscountFactor discountImpl(Time t) const {
            Rate r = zeroYieldImpl(t);
            return std::exp(-r*t);
        }
    };

    class ForwardRateStructure : public YieldTermStructure {
      public:
        // forwarding constructors, not shown
      protected:
        virtual Rate forwardImpl(Time) const = 0;
        virtual Rate zeroYieldImpl(Time t) const {
            // averages forwardImpl between 0 and t
        }
        DiscountFactor discountImpl(Time t) const {
            Rate r = zeroYieldImpl(t);
            return std::exp(-r*t);
        }
    };

ZeroYieldStructureの実装は、単純明快です。(上記のコードでは書かれていませんが)複数用意されているコンストラクターは、引数をベースクラスであるYieldTermStrucutreのコンストラクターに渡しています。Adapterパターンがprotectedの部分で使われています。抽象メソッドzeroYieldImpl( )が宣言され、discountImpl( )メソッドの中で使われています。このようにして、派生クラスの開発者はzeroYieldImpl( )メソッドのみを実装するだけで、きちんと機能するイールドカーブを得ることができます。(当然、ほかのメソッド, (例えばmaxDate( )など)も同様に実装する必要がありますが。) 一点注意してほしいのは、discount factorを計算する式を見てわかる通り(訳注:exp(-rt)の式を使ってゼロレートからDiscount Factorを計算している)、zeroYieldImpl( )が返すゼロ金利は連続複利収益率ベースの年率で返す必要があります。  

 同じように、ForwardRateStructureクラスは、(これも、年率ベースで表示された)瞬間フォワード金利でTerm Structureを描く方法を提供します。具体的には、派生クラスでforwardImpl( )メソッドを実装する事によって、それが可能になります。しかし、その為には、ひとつ工夫が必要でした。与えられた将来の日付Tに対応するDiscount Factorを計算するためには、時間0からTまでの瞬間フォワード金利の平均を計算しなければなりません。それが時間Tにおけるゼロ金利に相当します。このクラスはフォワード金利カーブの形状について何の仮定もおいていないので(訳注:Interpolationの方法が特定されていれば、ある程度、瞬間フォワード金利のカーブを式(何等かの関数)で表現できるが、この段階ではそれが決まっていない)、平均値を出すには計算時間がかかる数値積分を行うしかありません。後から計算を効率的にする方法が見つかった時の為に、その平均計算をzeroYieldImpl( )を仮想関数にして、より早い計算方法が見つかればそれをオーバーライドできるようにしています。この構成に対し反対意見があるかもしれません。ゼロ金利が必要なら、ZeroYieldStructureから持って来ればいいではないか、と。でも、価格モデルでフォワード金利を直接扱うものがあるとすれば、このようにフォワード金利で金利のTerm Structureをもっておくのは、考え方としてはスッキリしています。 

 今説明した2つのAdapterクラス(ZeroYieldStructure、ForwardYieldStructure)とベースクラスであるYieldTermStructureクラスは、Interpolationしたディスカウントカーブ、ゼロ金利カーブ、およびフォワード金利カーブを実装するために使われます。下のListing3.6はInterpolatedZeroCurveクラスのテンプレートです。他の2つのInterpolationされたカーブ(InterpolatedForwardCurve とInterpolatedDiscontCurve)も同様に作れます。 

Listing 3.6: Outline of the InterpolatedZeroCurve class template. 

template <class Interpolator>
    class InterpolatedZeroCurve : public ZeroYieldStructure {
      public:
        // constructor
        InterpolatedZeroCurve(
                      const std::vector<Date>& dates,
                      const std::vector<Rate>& yields,
                      const DayCounter& dayCounter,
                      const Interpolator& interpolator
                                            = Interpolator())
        : ZeroYieldStructure(dates.front(), Calendar(),
                             dayCounter),
          dates_(dates), yields_(yields),
          interpolator_(interpolator) {
            // check that dates are sorted, that there are
            // as many rates as dates, etc.

            // convert dates_ into times_
            
            interpolation_ =
                interpolator_.interpolate(times_.begin(),
                                          times_.end(),
                                          data_.begin());
        }
        Date maxDate() const {
            return dates_.back();
        }
        // other inspectors, not shown
      protected:
        // other constructors, not shown
        Rate zeroYieldImpl(Time t) const {
            return interpolation_(t, true);
        }
        mutable std::vector<Date> dates_;
        mutable std::vector<Time> times_;
        mutable std::vector<Rate> data_;
        mutable Interpolation interpolation_;
        Interpolator interpolator_;
    };

テンプレート引数である“Interpolator”オブジェクトは2つの仕事を行います。ひとつは、Traitsクラスとしての役割です(下記[1]のMyersの文献参照)。まず、どのInterpolationの方法を使うか指定し、かつその為に必要ないくつかの属性を指定します。その属性とは、例えば、いくつのデータポイントが必要になるのか(例えば線形補間なら最低2点のデータポイントが必要)とか、指定されたInterpolationの方法がGlobalかLocalなのかとかです。(すなわち、あるデータポイントのレートを動かした影響が、カーブ全体に及ぶのか、そのデータポイントの近辺だけに留まるのかという事。訳注:線形補間なら原データの摂動の影響は局所的だが、Cubic SplineなどSmoothingを行う方法を使うと、影響がカーブ全体に及ぶ)もう一つは、データポイントの集合が与えられると、それに対応するInterpolationクラスのインスタンスを作成し返してきます。(InterpolationクラスについてはAppendix Aで、ライブラリで提供されているいくつかの方法と、それに必要なTraits(属性)について説明します。) 

publicで宣言されたコンストラクターはカーブを構築するのに必要な情報を引数として取ります。それらは 

  • カーブのNode (Pillar)となる日付の配列
  • それぞれのNodeの日に対応するゼロ金利
  • DayCounter(日数計算方法)オブジェクト
  • Interpolatorオブジェクト(任意)

ほとんどのインターポレーションンにおいて、上の最後の引数は不要(任意)です。インターポレーションを行う際にパラメータが必要な場合だけ、その情報を取り込めばいいからです。コンストラクターはさらにベースクラスのZeroYieldStructureのコンストラクターを呼び出し、引数(日付の配列の先頭=Reference Dateに該当、およびDayCounter(日数計算方法))を渡します。その他の引数はメンバー変数に代入されます。いくつかのデータ整合性チェックのプロセスを経て、日付の配列を年数の配列に変換します。さらにInterpolatorを使ってInterpolationインスタンスを生成し、最後にそのインスタンスをメンバー変数に代入します。 

もうこの段階でカーブを使うことができます。他のメソッドは、1行で記述できるものばかりです。 maxDate( )は日付の配列の最後の日を返します。zeroYieldImpl( )はゼロ金利カーブの補間された値を返します。TermStructureクラスで、既に日付の範囲のチェックが終わっているので、Interpolatorクラスのインスタンスを呼び出すメソッドはtrueの引数を持ちます。この場合は、引数で渡された年数が、与えられた期間の範囲を越えている場合は、イールドカーブはExtrapolation(補外)されます。 

最後に、InterpolatedZeroCurveクラスは、いくつかのprotectedで宣言されたコンストラクターも定義しています。これらのコンストラクターはベースクラスであるZeroYieldStructureクラスと同じ引数を取り、それには任意引数であるInterpolatorインスタンスも含まれます。そしてそれらの引数を対応する親クラスのコンストラクターへ渡します。しかしながら、これらの親クラスはInterpolationを行うことはできません。なぜなら、そもそもゼロ金利のデータを持っていないからです。そもそも、これらのコンストラクターは、InterpolatedZeroCurveの派生クラスの為につくられたものです。派生クラスで、Interpolationに必要なデータを提供し、それと、その他の必要な情報を引数として取得し、Interpolationを行います。(これの例を、この章の残りのセクションで示します。) まったく同じ理由から(訳注:派生クラスで具体的にデータを操作する事を想定している理由から)、ほとんどのメンバー変数はmutableで宣言されています。下記の追加記事(訳注:下記のSymmetry Breakの事)で示した通り、派生クラスにおいて、イールドカーブ構築に使うデータに変更があった場合、インターポレーションをLazilyに行えます。
(訳注:LazyObjectと呼ばれるベースクラスから派生させる事により、原データの変更があっても、直ちに再計算をするのでは無く、必要な時だけ再計算、すなわちイールドカーブの再構築を行えるようにしている事。Lazy ObjectとObserverパターンについては、Appendix Aのデザインパターン参照)

[1] N.C. Myers, Traits: a new and useful template technique. In The C++ Report, June 1995. 

 

< Symmetry Break:対称性の破れ >

読者の方は、(ジョージ オーウェルの「Animal Farm」のように、)3つのTermStructure(Discount Curveと、Zero Coupon Curveと、瞬間Forward Curve)は、お互い平等に取り扱った方がいいのではないかと主張されるかもしれません。YieldTermStructureベースクラスにおいて、ディスカウントファクターをベースにした実装方法は、Discount Curveが他のカーブより優越的な役割を果たしているように見えます。もっと対称的な作り方としては、ベースクラスに3つの抽象メソッド(Publicなメソッドから呼び出されるdiscountImpl( ), zeroYieldImpl( ), およびforwardImpl( ) )を定義し、それらに対応する3つのAdapterを作成する方法もあるかも知れません。その為には、(既存のZeroYieldStructureとForwardYieldStructureクラスに加え)DiscountStructureクラスというのを作る必要があります。 

その論点はごもっともで、最初に設計したYieldTermStructureクラスの作り方は、そのように対称的でした。ディスカントファクターをベースにしたインターフェースへの変更と、その理由については、時間とともに霧の中へ消えてしまいましたが、おそらく様々な金利を表現するオブジェクトとしてInterestRateクラスのインスタンスを使うようになった事が原因かもしれません。仮にzeroYield( )という名のメソッドを作ったとしたら、値をInterestRateインスタンスで返す事になりますが、その場合、金利の支払回数や複利の回数を変換する必要があり、zeroYieldImpl( )の結果(連続複利ベースのゼロ金利)をそのまま直接返す訳にはいかなかったからではないかと思います。 

 

< Twin Classes:双子のクラス >

読者の方は、InterpolatedDiscountCurveやInterpolatedForwardCurveクラスの中で使われるコードは、InterpolatedZeroCurveのコードと、非常に似たものになると推察されるかもしれません。そうすると、次のような疑問がわきます。そのような似たコードは、抽象化して一本のコードにできないのかと。あるいは、ひとつのTemplateクラスにまとめられないのかと。 

それに対する答えは、それぞれYesとNoです。いくつかのコードは既にTemplateクラスに抽象化されています。しかし、それらのカーブは3つの異なる抽象メソッド( discountImpl( )、 forwardImpl( )、 zeroYieldImpl( ) )を実装してやる必要があります。従って、3つの別々のクラスが必要で、それに加えて共通のコードのためのTemplateクラスが必要なのです。 

3.2.3 例:InterpolationされたカーブのBootstrapping

このセクションでは、何でも使えるイールドカーブのテンプレートを構築します。前のセクションで説明したクラスを使って構築しながら、そのイールドカーブテンプレートに、ディスカウントファクター、ゼロ金利、あるいは瞬間フォワード金利を、様々な方法でインターポレーションできる機能を与えます。カーブ上のNodeポイント(訳注:3か月、6か月、1、2,3,4,5年、10年、20年など基準となる日付。Pillarとも呼ぶ)における市場でQuoteされている(かつ動いている)レートから、Bootstrap法により(Discount FactorやZero Coupon Rateを)導出していきます。 

あえて申し上げると、ここに示す例はかなり複雑です。ここで説明するクラステンプレート(PiecewiseYieldCurveクラス)は、いくつかのHelperクラスを利用しており、またTemplateを作る際のちょっとした工夫が含まれています。必要に合わせて、そのすべてを説明するようにします。 

クラステンプレートの実装内容を、下記Listingに示します。 

Listing 3.7: Implementation of the PiecewiseYieldCurve class template. 

template <class Traits, class Interpolator, template <class> class Bootstrap = IterativeBootstrap>
class PiecewiseYieldCurve
    : public Traits::template curve<Interpolator>::type,
      public LazyObject {
  private:
    typedef typename Traits::template curve<Interpolator>::type base_curve;
    typedef PiecewiseYieldCurve<Traits,Interpolator,Bootstrap> this_curve;
    typedef typename Traits::helper helper;
  public:
    typedef Traits traits_type;
    typedef Interpolator interpolator_type;
    PiecewiseYieldCurve(
           Natural settlementDays,
           const Calendar& calendar,
           const std::vector<shared_ptr<helper> >& instruments,
           const DayCounter& dayCounter,
           Real accuracy = 1.0e-12,
           const Interpolator& i = Interpolator());

    // inspectors not shown

    void update();
  private:
    void performCalculations() const;
    DiscountFactor discountImpl(Time) const;
    std::vector<shared_ptr<helper> > instruments_;
    Real accuracy_;

    friend class Bootstrap<this_curve>;
    friend class BootstrapError<this_curve>;
    Bootstrap<this_curve> bootstrap_;
};
template <class T, class I, template <class> class B>
PiecewiseYieldCurve<T,I,B>::PiecewiseYieldCurve(
           Natural settlementDays,
           const Calendar& calendar,
           const std::vector<shared_ptr<helper> >& instruments,
           const DayCounter& dayCounter,
           Real accuracy,
           const I& interpolator)
: base_curve(settlementDays, calendar, dayCounter, i),
  instruments_(instruments), accuracy_(accuracy) {
    bootstrap_.setup(this);
}

template <class T, class I, template <class> class B>
void PiecewiseYieldCurve<T,I,B>::update() {
    base_curve::update();
    LazyObject::update();
}

template <class T, class I, template <class> class B>
void PiecewiseYieldCurve<T,I,B>::performCalculations() const {
    bootstrap_.calculate();
}

template <class T, class I,  template <class> class B>
DiscountFactor PiecewiseYieldCurve<T,I,B>::discountImpl(Time t) const {
    calculate();
    return base_curve::discountImpl(t);
}

このクラスは3種類のテンプレート引数を取ります。最初の2つの引数は、Interpolationの対象データに関する情報と、具体的なインターポレーションの方法です。我々のゴールはこのテンプレートを使って次の様なインスタントを生成することです。 

PiecewiseYieldCurve <Discount, LogLinear>

(ここで指定したテンプレート引数には、別の組合せもあります) 

最初のテンプレート引数(この例ではDiscount)はbootstrap traitsに該当しますが、これについては後で説明します。2番目のテンプレート引数(この例ではLogLinear)はすでに前のセクションで説明したInterpolator(インターポレーションの方法を設定しているクラス)です。3番目のテンプレートパラメータは(この例では宣言の時にデフォールト値を設定しているため省略されている)、見た目はややこしそうですが、Bootstrapの計算アルゴリズムを特定するクラスです。3番目のパラメータは初期設定としてIterativeBootstrapクラス(後で詳しく説明します)が指定されており、ほとんどの場合、この事は忘れてしまって結構です(訳注:大半のBootstrapping において、このデフォールト値の方法を使って良い)。より探究したいプログラマーが、後で自ら開発した別のBootstrapの方法や、ライブラリで提供されている別のBootstrap法に入れ替える事が出来るように、こういう構成にしました。もし、このテンプレートの構文に馴染みがない人の為に説明すると、これはtemplate template parameterです。templateが2回でてきますが、間違いではありません。テンプレートの引数として、特定のデータ型ではなく、具体化される前のクラステンプレート(このケースでは、3番目の、テンプレート引数を1個持つクラステンプレートです)を取っているという意味です。(具体化される前のテンプレートとは、例えばstd::vectorのようなもので、反対に具体化されたクラステンプレートはstd::vector<double>のようなものです。後者は具体的な型を指定していますが、前者はしていません) 

クラスのインターフェースを宣言する前に、もうひとつテンプレートのプログラミング技法を使って、カーブの親クラスを決める必要があります。まずひとつめの親クラスとして、このTermStructureクラスはLazyObjectクラス(Chapter IIを参照)から継承されており、これによりこのTermStructureクラスは必要な時に(原データの更新があった時に)再Bootstrapをかける事ができます。もうひとつ、我々はこのTermStructureクラスを、前のセクションで説明した、インターポレートされたカーブからも継承したいと考えます。(Interpolationされる)原データを何にするかによって(ディスカウントファクターか、ゼロクーポンレートか、または瞬間フォワード金利なのか)、我々は用意されているクラステンプレートの中からそれを選択し、それに適切なInterpolatorクラスを組み合わせて、そのインスタンスを生成します。この方法は、bootstrap traitsの中にそのクラステンプレートを保存することによって成されます。残念ながら、C++は最近までtemplateのtypedef機能はありませんでした(2011年のC++の見直しの時、templateのaliasが加えられました)。従って、そのbootstrap traitsは、内部クラスとしてクラステンプレート“curve”を定義し、そのcurveがテンプレート引数として“Interpolator”クラスを取り、typedefを使って、その具体化されたベースクラスを定義します。このような定義の仕方を、以下のListing3.8に“Discount” のbootstrap traitsの例で示します。(訳注:訳した文章にすると非常に解りにくいですが、原文と下記のコードをよく読んだ方が理解しやすいと思います) 

Listing 3.8: Sketch of the Discount bootstrap traits. 

struct Discount {
        template <class Interpolator>
        struct curve {
            typedef InterpolatedDiscountCurve<Interpolator> type;
        };
        typedef BootstrapHelper<YieldTermStructure> helper;

        // other static methods
    }

上記に説明した仕組みを使って、ようやく選択されたベースクラスを下記例のような表現を使って、参照できます。(Listing 3.7の4行目) 

Traits::template curve::type

(テンプレート構文の詳しい働きについてあまり詳しくない人の為に説明を加えると、上記構文の中のtemplateというKeywordはコンパイラーに対する一種のヒントと見做せます。コンパイラーがこのKeywordを読んだとき、Traitsが何かわかっていません。従ってTraits::curveがクラステンプレートかどうか判断するすべがありません。そのKeywordを加える事によって、残りの構文を処理するために必要な情報を与えています。) 

上記の例 ( PiecewiseYieldCurve<Discount, LogLinear> ) に示したように”Discount”をbootstrap traits、”LogLinear”をInterpolatorの引数にして、具体化した事により、上記のコード(Traits::template curve<Interpolator>::type)は

Discount::curve<LogLinear>::type
のインスタンスを生成したことになり、さらにそれは(望んだように)Discountクラスの定義の中にある
InterpolatedDiscountCurve<LogLinear>
に対応するものとなります。 

最終的にイールドカーブの生成に行く前に少し寄り道をします。長いテンプレートクラスの表現を避ける為に、いくつかのテンプレートクラスをtypedefで定義しています。base_curve、this_curve、helperがそれです。base_curveは(Bootstrap前の原データを提供する)ベースクラスを指し、 this_curveはまさに宣言した自分そのもの(Bootstrap後のカーブデータを保持するクラス)を指し、helperはbootstrap traitsから持ってきたhelperクラスになります。このhelperクラスについては後で詳しく説明しますが、ここで簡単に前説しておくと、このクラスは原商品の市場レートを提供すると同時に、Bootstrapされたイールドカーブを使ってその原商品の価格を再計算する機能を持っています。Bootstrapの目的は、(入力に使われた)原商品の価格と、生成されたイールドカーブで計算されたその商品の価格が一致するように、カーブを何度も微調整していく事です(訳注:Solverを使った最小値問題の解を出す手順)。最後に、さらに2つのtypedefによって定義されたtraits_type とinterpolation_typeにはそれぞれ、テンプレート引数で取り込まれたものが格納され、後で使われる時に引き出されます。 

ついに、実際のインターフェースの説明に入ります。コンストラクターは(上記Listingには1個しか表示していませんが)引数として、 

  • ベースクラスのinterpolated curveのインスタンスを生成するのに必要な引数
  • helperインスタンスの配列
  • Bootstrapアルゴリズムにおける目標精度

を取ります。そして、いくつかのインスペクターメソッド(times( )や data( ) )が親クラスの関数をOverrideする(書き換える)かたちで定義されています。後で説明しますが、これによってイールドカーブは要求されたデータを返す前に、きちんと完成された事を確認します。publicなメソッドの最後はupdate( )です。 

protected メソッドとしては、LazyObjectのインターフェースとして宣言された純粋仮想関数のperformCalculations( )の実装と、(他のインスペクターメソッドと同様に)親クラスのメソッドをOverrideするかたちで実装されるdiscountImpl( )があります。Bootstrapクラステンプレートは、まさに生成されているイールドカーブ(自分自身this_curve)を使ってインスタント化されます。Bootstrapインスタンスは、このカーブ(this_curve)の内部変数、内部メソッドにアクセスできるようにするため、PiecewiseYieldCurveクラスのフレンドとして宣言されています。同様のfriend宣言は、BootstrapErrorクラスでも行われています。このクラスはBootstrapのアルゴリズムの中で使われますが、これについては後で説明します。最後に、Bootstrapクラスのインスタンスと、イールドカーブ目標精度の要求水準、さらにhelper(原商品のレートを扱うクラス)をメンバー変数として保持します。 

ではここから実装を見ていきましょう。コンストラクターの実装に驚きは無いと思います。ベースクラスに必要な引数を渡し、それ以外の引数は自分のメンバー変数に格納しています。そして最後にメンバーとして保持しているBootstrapクラスのインスタンスに自分自身の情報を渡し、あらかじめ行っておくべき計算をさせています。この事については後で説明します。update( )メソッドの実装は、あいまいさを残さないよう、注意が必要です。2つの親クラス(LazyObjectとTermStructure)のいずれもupdate( )メソッドを宣言しているため、コンパイラーにどちらのupdate( )メソッドなのか推定させるのは酷です。従って、両方の親クラスのupdate( )を明示的に呼び出すようにしています。 

performCalculation( )メソッドは、実際の計算をBootstrapのインスタンスに委託しています。最後にdiscountImpl( )メソッドの中味は、なぜこのメソッドがOverrideされないといけなかったかが、一目でわかります。まずcalculate( )を呼び出してBootstrapを先に行ってから、親クラスのdiscountImpl( )が呼び出されています。このやり方は、他のインスペクターとなるメソッドにも当てはまります。しかしながら、zeroYieldImpl( )とforwardImpl( )メソッドは、たとえそれらのカーブがZeroYieldStructure やForwardRateStructureから派生していたとしても、オーバーライドする必要はありません。 前の方のListing3.5をみて解る通り、これらのメソッドはdiscountImpl( )から呼び出されるからで、関数のOverride(書き換え)はこの関数のみで十分です。  

ここでBootstrapHelperクラスのテンプレートを説明する必要があります。このクラスのインターフェースは下記Listing3.9に記述の通りです。 

Listing 3.9: Interface of the BootstrapHelper class template. 

template <class TS>
    class BootstrapHelper : public Observer, public Observable {
      public:
        BootstrapHelper(const Handle<Quote>& quote);
        virtual ~BootstrapHelper() {}

        Real quoteError() const;
        const Handle<Quote>& quote() const;
        virtual Real impliedQuote() const = 0;

        virtual void setTermStructure(TS*);

        virtual Date latestDate() const;

        virtual void update();
      protected:
        Handle<Quote> quote_;
        TS* termStructure_;
        Date latestDate_;
    };

少し前に”Discount”のbootstrap traitsのところで説明した通り、我々はBootstrapHelper<YieldTermStructure>のようなかたちでインスタンスを生成し、カーブの中で使いたいと考えます。便宜上、Libraryはこのクラスに対し(長ったらしいクラス名を簡略化する為)RateHelperという別名を提供しています。 

このクラスのインスタンス(あえて言えば、この派生クラスのインスタンス)は、イールドカーブ上のひとつのNode(Pillar)におけるBootstrapアルゴリズムの補助を行います。あるNodeにおける入力データは原商品の市場レートになります。この情報は、常に変動するため(情報そのものではなく)“Quote”インスタンスに対する、“Handle”のかたちで渡されます。イールドカーブ構築の為の原商品としては、インターバンク預金金利、スワップなどがあり、それぞれ固有の方法で市場金利が呈示されています。従って、それぞれの商品毎に(RateHelperの)派生クラスを用意しなければなりません。 

BootstrapHelperのすべての派生クラスに共通のメソッドは、ベースクラスで実装されています。BootstrapHelperクラスは、ObserverとObservableの2つのベースクラスから派生しています。従ってその両方の機能を持っており、(Observerとして)市場データのクラスに自らを登録し(データの変更があれば通知を受ける)、(Observableとして)自らに登録されたイールドカーブ(インスタンス)に対し、データの変更があれば通知し、再Bootstrapを促します。コンストラクターは、Handle<Quote>(市場データを保持しているQuoteクラスのHandle)を引数として取り、それをメンバー変数に保持すると同時に、(Observerである)自らを(Observableである)Quoteインスタンスに登録します。3種類のメソッドが用意されており、いずれも原商品の価格(利回り)を取扱います。 quote( )メソッドは、市場データを保持しているインスタンスのHandleを返します。抽象メソッド(純粋仮想関数)であるimpliedQuote( )は(訳注:原文ではimpliedValue( )となっているが、上記Listing 3.9をみればimpliedQuote( )の間違いと思われる)、Bootstrap後(あるいはその最中)のイールドカーブを使って計算された(原商品の)レートを返します。そしてquoteError( )メソッドは、市場レートとimpliedQuote( )で計算されたレートの誤差をプラスマイナスの表示付きで返します。 

さらに2種類のメソッドがBootstrapのアルゴリズムの構築の為に用意されています。setTermStructure( )メソッドは、helperインスタンスを、生成するイールドカーブにリンクさせます。 latestDate( )メソッドは、イールドカーブのNodeの中で一番最後の日付を返します。その日付は、イールドカーブからその各Nodeの原商品の金利をBootstrap計算する上で、一番最後のデータポイントとなります。(この一番最後の日付は、必ずしも原商品の中で一番期間が長いものに対応するとは限りません。例えば、価格を計算する対象商品がConstant Maturity Swapであるなら、イールドカーブは、そのスワップの最終期日からさらに何年か先まで(変動金利のインデックスとなっているスワップの期間の最終日まで)伸ばしておく必要があります。) 最後のupdate( )メソッドは、Quoteのインスタンスから送られてきた(市場レートが変化したという)通知を、さらにhelperのObserverインスタンスへ転送するものです。 

(訳注:以上のメソッドの実装内容は上記Listing 3.9には含まれていないので、別途QuantLibのソースコードで確認して下さい。) 

QuantLibではRateHelperクラスから派生するいくつかの具体的なHelperクラスを用意しています。簡潔にするために、実際のコードを載せず、身振り手振りでの説明で済ませることをお許し下さい。それぞれの派生Helperクラスでは、(RateHelperクラスの純粋仮想関数である)それぞれの原商品に対応したimpliedQuote( )(訳注:原文ではimpliedValue( )となっているが間違いと思われる)を実装しています。例えば、DepositRateHelperクラスでは、Bootstrapされたカーブに将来のLIBOR対象期間のForward Rateを聞いて、原商品であるDeposit Rateを予想します。一方SwapRateHelperは、まずSwapオブジェクトのインスタンスを生成し、Bootstrapされたカーブを使って、そのSwapのMark To Marketを行い、そこからFair Valueを計算します。Multi-Curveの方法でデリバティブズの値洗いを行う場合は、BootstrapしたカーブをForwardingカーブとして使い、別のカーブをDiscountingカーブとして使うこともできます。もし興味があるなら、Multi-Curveに関するより詳細な説明は、下記の文献を参照して下さい。 

[2] F. Ametrano and M. Bianchetti, Everything You Always Wanted to Know About Multiple Interest Rate Curve Bootstrapping but Were Afraid to Ask, SSRN working papers series n. 2219548, 2013. 

 

これで、やっとBootstrapのプログラムコードの中味を詳しく見ることができます。Listing 3.10にIterativeBootstrapクラスのインターフェースを示します。このクラスはQuantLibライブラリで提供されており、デフォールトで使われているBootstrapの方法です。 

Listing 3.10: Sketch of the IterativeBootstrap class template. 

template <class Curve>
class IterativeBootstrap {
    typedef typename Curve::traits_type Traits;
    typedef typename Curve::interpolator_type Interpolator;
  public:
    IterativeBootstrap();
    void setup(Curve* ts);
    void calculate() const;
  private:
    Curve* ts_;
};

template <class Curve>
void IterativeBootstrap<Curve>::calculate() const {
    Size n = ts_->instruments_.size();

    // sort rate helpers by maturity
    // check that no two instruments have the same maturity
    // check that no instrument has an invalid quote

    for (Size i=0; i<n; ++i)
        ts_->instruments_[i]->setTermStructure(const_cast<Curve*>(ts_));

    ts_->dates_ = std::vector<Date>(n+1);
    // same for the other data vectors

    ts_->dates_[0] = Traits::initialDate(ts_);
    ts_->times_[0] = ts_->timeFromReference(ts_->dates_[0]);
    ts_->data_[0] = Traits::initialValue(ts_);

    for (Size i=0; i<n; ++i) {
        ts_->dates_[i+1] = ts_->instruments_[i]->latestDate();
        ts_->times_[i+1] = ts_->timeFromReference(ts_->dates_[i+1]);
     }

    Brent solver;

    for (Size iteration = 0; ; ++iteration) {
      for (Size i=1; i<n+1; ++i) {
          if (iteration == 0)   {
              // extend interpolation a point at a time
              ts_->interpolation_ = ts_->interpolator_.interpolate(
                          	            	ts_->times_.begin(),
	                                      ts_->times_.begin()+i+1,
             	                         ts_->data_.begin());
              ts_->interpolation_.update();
          }

          Rate guess;
          // estimate guess by using the value at the previous iteration,
          // by extrapolating, or by asking the traits

          // bracket the solution
          Real min = Traits::minValueAfter(i, ts_->data_);
          Real max = Traits::maxValueAfter(i, ts_->data_);

          BootstrapError<Curve> error(ts_, instrument, i);
          ts_->data_[i] = solver.solve(error, ts_->accuracy_, guess, min, max);
      }

      if (!Interpolator::global)
          break;      // no need for convergence loop

      // check convergence and break if tolerance is reached; bail out
      // if tolerance wasn't reached in the given number of iterations
    }
}

便利なように、curveインスタンスからtraitsタイプとinterpolatorタイプを抜き出し、typedefを使って型名を作っています。コンストラクターとsetup( )メソッドは、特に興味をそそるようなものではありません。コンストラクターは、メンバーとして持つTermStructureインスタンスへのポインターをNullに初期化しているだけです。 setup( )は、まず、引数として取り込まれたcurveインスタンスのポインターをメンバー変数に保持し、次にcurveをBootstrapするのに十分な数のhelperを持っているかどうかチェックし、最後にそのカーブをObserverとして(Observableである)helperインスタンスに登録します(訳注:以上の実装内容は上記Listing 3.10に含まれていないので、ソースコードで実際に確認して下さい)。Bootstrapのアルゴリズムは、calculate( )メソッドで実装されています。ここで紹介しているバージョンでは、詳細な説明を避け、若干端折った部分がありますが、もし詳細な内容を知りたいなら、QuantLib Libraryにあるソースコードを読んで下さい(訳注:但し100行以上あります)。 

calculate( )は最初に(いくつかのデータチェックを行った後)、すべてのhelper(訳注:上記コードの中ではinstruments_[ ] 配列がそれに該当)に、(Bootstrappingしようとしている)今のTermStructureのインスタンスをセットします。このプロセスは、(setup( )メソッドとは異なり)calculate( )の中で、計算の都度行っています。理由は、同じhelperインスタンスを他のイールドカーブでも使えるようにする為です。(注:当然ながら、マルチスレッドの動作環境では、競合が起こるため、同じhelperの集合を他のカーブに渡すのは安全ではありません。改めて申し上げれば、QuantLibのコードの多くは、マルチスレッドに対して安全ではなく、これについては議論のある所です) 次に、各データの配列を初期化します。配列の最初のNodeの日付と(カーブ上の)値は、引き渡されたtraitsインスタンスから取ってきます。金利のTerm Structureでは、最初のNodeの日付は、curveのreference dateに該当します。最初のNode(カーブ上の)値は、Bootstrapされるカーブの種類によって変わってきます。ディスカウントカーブでは、1になり、ゼロ金利カーブやフォワード金利カーブにおいては、ゼロとかダミーの値を入れておきます(このダミー値はいずれにしてもBootstrapのプロセスの中で書き換えられていきます)。日付配列の残りの要素は、各helperインスタンスの持つ最終期日を順次代入していきます。 times_のデータ(訳注:各Nodeまでの年数)はcurveクラスの中で実装された関数timeFromReference( ) を使って計算されます。 

ここで、1次元Solverのインスタンスを生成します。Solverは各Node ポイントでbootstrapするのに使われます(Solverの詳細についてはAppendix A参照)。bootstrapの計算は、2段階のネストのFor Loopアルゴリズムになっています。ネストの内側のループ計算(ここでは正規のBootstrapと呼んでおきましょう)は各NodeのBootstrapを順番に行っており、外側のループ計算は、それを何回かやり直す為のものです。Bootstrap計算そのものを再帰計算で何度もやり直すのは、(Cubic Splineのように)Non-localなInterpolationの方法(訳注:高次のSpline関数の係数を求めるため、線で繋ぐ2点のNodeだけでなく、すべてのNodeポイントのデータを使って連立方程式を立て、その解を計算する方法)の場合、あるNodeの値をBootstrapで計算すると、カーブ全体がその影響を受けるため、すでにBootstrap済のNodeの値を(誤差が一定の許容範囲に収まるまで)再Bootstrapする必要があるからです。 

すでに述べた通り、内側のForループは、カーブの期間の最も短いNodeから始め、順番に期間の長いNodeへ進んでいきます。最初の内側のループ計算を行う際(上記コードの中のif (iteration==0)の部分)、InterpolationはNodeを1つずつ進んで行きます(訳注:この部分は、QuantLibの原コードを見ると、すこし違ったコーディングがされていますが、趣旨は同じです)。それが最後まで終わると、一応すべてのNodeのデータが得られます。 2回目以降のIterationは、そのデータ全部を使って(すなわち、それを初期値として一次の連立方程式の解を求めるアルゴリズムを走らせ、)各Nodeポイントにおける市場レートを導出します。一番目の再帰計算では一旦“解の推定値(guess)”をbootstrap traitsから取ってくる事も可能ですし、カーブを線形補外して一旦guessとして使う事も可能です。2回目以降の再帰計算では手前で行った計算結果を新たなguessとして使います。各Nodeの最大値と最小値も、既に計算された各Nodeの値を使って、Traitsクラスから提供されます。例えば、ゼロ金利カーブやフォワード金利カーブのBootstrapを行うTraitsでは、最小値を0で設定しており、マイナス金利を発生させないようにしています。またディスカウントカーブのTraitsでは、カーブの中で増加部分が発生しないようにして(各Nodeの最大値を一つ前のNodeの値に設定することによって)、同じくマイナス金利を発生させないようにしています。(訳注:2010年代に入り、Sfr、Euroや円金利でマイナス金利が常態化してきており、この制約は見直す必要あります。QuantLibライブラリを見ると、マクロ変数としてQL_NEGATIVE_RATESを定義しており、マイナス金利を許容する場合は、上記のチェックをはずすようになっています) 

連立方程式の解を出すアルゴリズムの為に必要な最後の要素は、解が0になる関数です(訳注:最小値問題)。それは、BootstrapErrorクラステンプレートで提供されています(下記Listing3.11参照)。このクラスはhelperのquoteError( )メソッドを、関数オブジェクトインターフェースにより取り込んでいます。このクラスのコンストラクターは、引数として、生成されるイールドカーブのポインター、各Nodeに対応するhelper、およびNodeのインデックス番号を取り、それらをメンバー変数に代入しています。 operator( )メソッドは、このクラスのインスタンスを生成して関数として使えるようにしています。具体的には、まずNodeの推定値を更新し、それに従ってカーブデータの修正を行い、最後にquote error(値の誤差)を返します。 

Listing 3.11: Interface of the BootstrapError class template. 

template <class Curve>
    class BootstrapError {
        typedef typename Curve::traits_type Traits;
      public:
        BootstrapError(
            const Curve* curve,
            const shared_ptr<typename Traits::helper>& helper,
            Size segment);
        Real operator()(Rate guess) const {
            Traits::updateGuess(curve_->data_, guess, segment_);
            curve_->interpolation_.update();
            return helper_->quoteError();
        }
      private:
        const Curve* curve_;
        const shared_ptr<typename Traits::helper> helper_;
        const Size segment_;
    };

これで、すべての準備は整いました。内側のBootstrapのFor ループでは、BootstrapErrorクラスのインスタンスが生成され、それがsolverインスタンスに渡されます。 solverは、そのNodeの値を誤差ゼロで返します。すなわち、implied quote(Bootstrap後で再計算された市場レート)が、市場レートと許容範囲の誤差内で一致しているという事です。(原)イールドカーブのデータは、(Bootstrapにより返されたデータにより)更新され、またloopによる再計算が開始されます。 

すべてのNodeのBootstrapが終われば、外側のLoopが、再計算が必要かチェックを行います。local interpolationであれば、そこで終わりです。Non-local なinterpolationであれば、誤差のチェックを行い、それが一定値以下になるまで再びLoopでの再計算が行われます。 

以上で、Bootstrapアルゴリズムの説明を終了します。これ以上、読者の忍耐力を試すつもりはありませんので、このExampleによる説明もここで終わります。PiecewiseYieldCurveクラスのサンプルコードはQuantLibの提供するサンプル例の“Swap-valuation”で使われていますので、参考にして下さい。 

 

< A Friend in Need:必要な時の友 >

(C++に精通の読者は)PiecewiseYieldCurveクラスの宣言を見て、「ちょっと待てよ」とおっしゃるかもしれません。(メンバークラスのBootstrapとBootstrapErrorクラスをPiecewiseYieldCurveクラスの)friendとして宣言するのは、問題を起こすのでは? その通りです。 friendクラスとして宣言すると、encapsulationを壊す可能性があり、クラス間の整合性を厳密にとっていく必要が出てきます。 

しかし、別の方法を取った場合どうなるでしょうか? Bootstrapクラスはカーブのデータに対し、書込みを行う必要があります。仮にfriendクラスとして宣言せずに、カーブデータに対するアクセス権を与えるとしたら、それには3つの方法があります。ひとつめは、(カーブのデータを)Bootstrapインスタンスに渡してしまう方法です。しかし、これは2つのクラスのデータ整合性を取るために同様に厳格なプログラミングが必要になります(また、カーブクラスの内部変数を変更しようとすれば、Bootstrapクラスのプログラム内容の変更も必要になります)。2番目は、カーブクラスの持つ内部変数データをpublicなインターフェースで解放する方法です。しかし、これはencapsulationを壊す、より大きなリスクをもたらします(Bootstrapの過程でカーブデータを書き換える必要がある事を思い出して下さい)。最後に、カーブのデータを別のクラスに持たせて、Privateな継承でアクセスをコントロールする方法です。Release1.0の段階では、Friendの宣言が最初の2つの選択肢より優れ、プログラムもシンプルになると考えていました。3番目のオプションがおそらくベストだと思いますが、今後のReleaseで導入を検討していきたいと思います。 

3.2.4 プログラム例:イールドカーブにz-スプレッドを追加:

このセクションのプログラム例は(前のセクションよりもかなり簡単ですが)、あるイールドカーブにスプレッドを加え、別のイールドカーブを生成する方法を説明します。ここでは、すでにあるリスクフリーのイールドカーブを、修正してクレジットカーブを生成してみます。リスク相当部分をz-spread、即ちゼロ金利カーブに加えられる固定スプレッドとして表現します。デザインパターンになじみの深い人にとっての話題としては、ここでは「デコレーターパターン」(Decorator Pattern)を使っています。即ち、当初のオブジェクトに対し、いくつかの動作を加える一方、残りの動作は当初のインスタンスに任せるという形で、当初のオブジェクトを包み込む技法です。 

Listing 3.12: Implementation of the ZeroSpreadedTermStructure class. 

class ZeroSpreadedTermStructure : public ZeroYieldStructure {
    public:
      ZeroSpreadedTermStructure(
                        const Handle<YieldTermStructure>& h,
                        const Handle<Quote>& spread);
      : originalCurve_(h), spread_(spread) {
          registerWith(originalCurve_);
          registerWith(spread_);
      }
      const Date& referenceDate() const {
          return originalCurve_->referenceDate();
      }
      DayCounter dayCounter() const {
          return originalCurve_->dayCounter();
      }
      Calendar calendar() const {
          return originalCurve_->calendar();
      }
      Natural settlementDays() const {
          return originalCurve_->settlementDays();
      }
      Date maxDate() const {
          return originalCurve_->maxDate();
      }
    protected:
      Rate zeroYieldImpl(Time t) const {
          InterestRate zeroRate = originalCurve_->zeroRate(t, Continuous,
                                       NoFrequency, true);
          return zeroRate + spread_->value();
      }
    private:
      Handle<YieldTermStructure> originalCurve_;
      Handle<Quote> spread_;
  };

上記Listing3.12では、ZeroSpreadedTermStructureクラスの実装を示しています。すでに述べた通り、ゼロ金利のイールドカーブをベースに構築されています。したがって、ZeroYieldStrucutreを親クラスとして継承し、(親クラスの仮想関数である)zeroYieldImpl( )メソッドをここで実装しています。当然の事ながら、コンストラクターは、修正元となるゼロ金利カーブ(のHandle)と、そこに足される固定スプレッド(のHandle)を引数としてとっています。データソースを変更できるようにする為、いずれの引数もHandleとして取り込まれています。引数は、メンバー変数に格納され、かつ(それらをObservableとして)ここで生成されるイールドカーブをObserverとして登録します。ベースクラスから継承したupdate( )メソッドが、Observableクラスから飛んできたデータ更新の通知を処理します。 

このクラスのコンストラクターでは、ベースクラスのコンストラクターを明示的に呼び出していない点に注意して下さい。Chapter 3.1での説明を思い出して欲しいのですが、これは即ち、ここで生成されるイールドカーブのインスタンスは、ベースクラスとなるTermStructureクラスの仕組みの中で使われるデータを一切持たないという事です。従って、このクラスでは、reference Dateの計算についても、自分自身でそれを用意する必要があります。正統なDecorator Patternでは、それを(自身で用意するのではなく)、包含されるオブジェクトのメソッドに委譲されます。このクラスの中のreferenceDate( ), dayCounter( ), calendar( ),settlementDays, maxDate( )の各メソッドは、親クラスとなるRisk Freeのカーブが持つそれぞれ対応するメソッドに委譲しています。 

最後に、このクラス独自の動作(メソッド)、即ち固定スプレッドを加えるという動作を、実装しています。この動作はzeroYieldImpl( )メソッドの中で行われています。すなわち、特定の時点のRisk Free金利(当然ながら連続複利利率で表示されたもの)を、ベースとなるゼロ金利カーブから取得し、それに固定スプレッドを加え、その結果の値を新たなゼロ金利として返します。ZeroYieldStructureクラスでとられたAdapter Patternのメカニズムが、残りの全部の動作を行い、望んだRisky curveを生成してくれます。 

 

<ライセンス表示>

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

目次

Page Top に戻る

// // //