2. "Implementing QuantLib"の和訳

Chapter-IV Cash Flows and Coupons

4.2 Interest-Rate Copupons

4.2.3 具体例:LIBORクーポン

変動金利クーポンの最も典型的な例は、LIBORインデックスにより決定される変動金利クーポンです。そのようなクーポンの最低限必要な実装例について、Listing 4.6 に示します。 

Listing 4.6: Minimal implementation of the IborCoupon class 

class IborCoupon : public FloatingRateCoupon {
      public:
        IborCoupon(const Date& paymentDate,
                   const Real nominal,
                   const Date& startDate,
                   const Date& endDate,
                   const Natural fixingDays,
                   const boost::shared_ptr<IborIndex>& index,
                   const Real gearing = 1.0,
                   const Spread spread = 0.0,
                   const Date& refPeriodStart = Date(),
                   const Date& refPeriodEnd = Date(),
                   const DayCounter& dayCounter = DayCounter(),
                   bool isInArrears = false)
        : FloatingRateCoupon(paymentDate, nominal,
                             startDate, endDate,
                             fixingDays, index, gearing, spread,
                             refPeriodStart, refPeriodEnd,
                             dayCounter, inArrears) {}
    };

このクラスで必要なメンバー関数はコンストラクターのみです。このコンストラクターは、FloatingRateCouponクラスと同じ引数を取りますが、金利インデックスのインスタンスについてはこのクラス専用のそれを渡します。すなわち、汎用的な InterestRateIndex ではなく IborIndex のインスタンスへのポインターを渡しています。(注:IBOR(Inter-Bank-Offer-Rate)という名前は、EURIBOR や LIBOR や TIBOR といった類似性のある金利インデックスを総称するものとして使っています。) すべての引数は、単純に親クラスのコンストラクターにそのまま渡されます。金利 Index のインスタンスについては、特に何の操作もする必要はありません。派生クラスに対する Shared Pointer は、ベースクラスに対するポインターに内生的に upcast (ポインターが示すオブジェクトの型が、ベースクラスで使われている型に変換される事) されるからです。 

当然ながら、実際の金利計算は Pricer によって行われます。Listing 4.7 に、いくつかある Pricer の内、BlackIborCouponPricerクラスの実装内容の一部を示します。Blackの名前は、Convexity Adjustmentが必要な場合や、クーポン fixing に Cap や Floor が付いている場合に Blackモデルを使ってそれらの計算を行っている所から来ています。 

Listing 4.7: Partial implementation of the BlackIborCouponPricer class 

class BlackIborCouponPricer : public FloatingRateCouponPricer {
      public:
        BlackIborCouponPricer(
            const Handle<OptionletVolatilityStructure>& v =
                           Handle<OptionletVolatilityStructure>())
        : capletVol_(v) {
            registerWith(capletVol_);
        }
        void initialize(const FloatingRateCoupon& coupon) {
            coupon_ = dynamic_cast<const IborCoupon*>(&coupon);
            gearing_ = coupon_->gearing();
            spread_ = coupon_->spread();
        }
        Rate swapletRate() const {
            return gearing_ * (adjustedFixing() + spread_);
        }
        // other methods, not shown
      protected:
        virtual Rate adjustedFixing(
                              Rate fixing = Null<Rate>()) const {

            if (fixing == Null<Rate>())
                fixing = coupon_->indexFixing();

            Real adjustement = 0.0;
            if (!coupon_->isInArrears()) {
                adjustement = 0.0;
            } else {
                QL_REQUIRE(!capletVolatility().empty(),
                           "missing optionlet volatility");
                adjustment = // formula implementation, not shown
            }

            return fixing + adjustement;
        }
        const IborCoupon* coupon_;
        Real gearing_;
        Spread spread_;
        Handle<OptionletVolatilityStructure> capletVol_;
    };

コンストラクターは、LIBOR金利のボラティリティの Term Structure (期間構造) オブジェクトに対する Handle を引数として取り、メンバー変数に保持し、かつ自分自身をそのオブジェクトに対する Observer として登録します。仮に、変動金利クーポンが後決めでない場合や、Cap や Floor が付いていない場合は、このボラティリティの Term Structure は使われることはありません。その場合、Handle は空のままです。(コンストラクターからの呼び出しを省略する事も可能です) 

 initialize( )メソッドは、引数として取った Couponクラスのインスタンスが型適合しているかどうか、IborCouponクラスに Downcast (派生クラスへの型変換) することによってチェックします。Downcast がうまくいけば、いくつかの事前計算を行います。ここでは、概要を知っていただくために、クーポンの倍率とスプレッドの情報をメンバー変数に保存しているコードをお見せしています。これを見ていただければ、他のメソッドをより正確にプログラミングする助けになると思います。swapletRate( )メソッドは、adjustedFixing( )メソッドで計算された‘調整後金利’に単純に倍率を掛け、スプレッドを足しています。金利の fixing のタイミングがクーポン期間の始めに行われるのであれば、調整は不要で、金利インデックスの fixing をそのまま使うだけです。金利の fixing ーポン期間の最後に行われる場合 (すなわち in Arrears の場合) は、Convexity Adjustment が計算され、fixing された金利に加えられます。ここでは、Convexity Adjustment の計算の実装内容は示しませんが、Caplet ボラティリティのデータチェックの部分だけ示しています。capletRate( )メソッドなど、他の金利を返すメソッドの実装内容については、ここでは省略します。必要があれば、QuantLib Libraryのプログラムコードを直接読んで確認して下さい。 

以上が、明解で非常に短い例で (何でもバラバラに分解するのが好きな子供向きではないかも知れませんが)、単純な変動金利キャッシュフローを持つ変動金利債の価格計算で使われる一般的な実務慣行です。プログラムコードを自分で書き換えたく無い人にとってはこれで十分かと思います。もちろん、モデルLibraryは、この方法を取るか取らないか選択肢を持つべきであり、その場合は、少なくとも2つの方法が用意されています。 

ひとつの可能な方法は (Listing 4.8に示します)、Pricer を使わずに、rate( )メソッドを Overrideする方法です。ここでは、まずプリプロセッサーを使ったコンパイル時のフラッグ設定で Parクーポン (訳注:イールドカーブから市場実勢のIndex Rateを導出したレート) が使用可能かどうかチェックし、もしそうなら、ここで実装された Par金利を使って計算値が返されます。そうでないなら、ベースクラスの rate( )メソッド(そこでは Pricer に実際の計算を依存していますが)をそのまま使います。 

Listing 4.8: Extension of the IborCoupon class to support par coupons 

class IborCoupon : public FloatingRateCoupon {
      public:
        // constructor as before

        Rate rate() const {

            #ifndef QL_USE_INDEXED_COUPON
            if (!isInArrears()) {
                Rate parRate = // calculation, not shown
                return gearing()*(parRate + spread());
            }
            #endif
            
            return FloatingRateCoupon::rate();
        }
    };

この方法は、そのままで動きますが、いくつか欠点もあります。この方法を使っても、Couponクラスに Pricerを設定する事もできますが、目に見える効果はありません。一方で、金利決定の後決め方式と先決め方式の間で、同じ日に金利インデックスの決定がなされた場合、異なった fixing金利を使ってしまう可能性があります。その結果、両者の同一性が失われ、最悪間違ったヘッジ比率を計算してしまう可能性があります。 

より良い方法は、Par-Coupon Pricer を別途外生的に実装してしまう方法かもしれません。下記 Listing 4.9で示す通り、もしその Pricerを作るなら、BlackIborCouponPricerクラスから派生させて実装する事が出来ます。この派生クラスは、adjustedFixing( )メソッドを Override する必要があるだけです。金利の fixingレートを求める為に、保持された 金利Index オブジェクト を使うのでは無く、リスクフリーのイールドカーブを持ってきて、そこからPar金利を計算します (訳注:イールドカーブから、市場実勢のLIBOR金利を導出すること)。その方が、Couponクラスの rate( )メソッドを Override する方法よりは解りやすいでしょうし、上記のような欠点を回避でき、なおかつ実行時に Pricerの選択が可能になります。 

Listing 4.9: Sketch of the ParCouponPricer class 

class ParCouponPricer : public BlackIborCouponPricer {
      public:
        // constructor, not shown

      protected:
        virtual Rate adjustedFixing(
                              Rate fixing = Null<Rate>()) const {

            if (fixing == Null<Rate>()) {
                Handle<YieldTermStructure> riskFreeCurve =
                    index->termStructure();
                fixing = // calculation of par rate, not shown
            }

            return BlackIborCouponPricer::adjustedFixing(fixing);
        }
    };

しかし、この方法でもまだ1点問題があります。convexityAdjustment( )メソッドは、イールドカーブを使った fixing金利と、金利Index の fixing金利との差を返しますが、この差は Convexity の影響によるものではありません (訳注:メソッドの名前と、実際の計算内容が違う)。 同じ問題は、Couponクラスの rate( )メソッドを Overrideする場合にも発生しています。残念ながら、どのように修正すべきか、少なくとも私自身には、(いくつかの可能な選択肢はありますが) これといった明確なアイデアがありません。ひとつの方法は、convexityAdjustment( )メソッドを Overrideして、Par金利を取り込んで、Nullの調整値を返すことです。しかし、この方法は金利後決めのケースではうまくいきません。もうひとつの方法は、メソッドの名前をより汎用的な意味をもつ adjustment( )に変えてしまうことですが、こうすると、逆に何を意味しているか解りにくくなってしまいます。もっとも良い方法と思われるのが、Convexity Adjustmentの計算をPricerにまかせてしまう方法です。これだと、前回の項で adjustedFixing( )メソッドの説明の際に指摘した脆弱性の問題の解決策になるかも知れません。 

現時点の実装方法はどうかと言えば、若干問題ありでしょう。次のAsideの項をお読みください。 

< Aside:約束違反 >

現時点のIborCouponクラスの実装方法は、indexFixing( )メソッドを Override して (リスクフリーのイールドカーブから) Par金利を取ってきてそれを返すものです。ある意味、それで事足りています。メソッドは正しい値を返しますし、必要のない Convexity Adjustment の値は Null で返します (後者については、他の実装方法では、そうなっていないので、それが今の方法を選択したひとつの理由です。)
 しかし、やはり実装方法としては間違っています。なぜならメソッドの名前の通りの内容になっていないからです。今の実装方法では、もはや金利インデックスの fixing金利を返していません。今後のリリースで、プログラムコードを修正すべきであり、クラスのメソッドがその宣言通りの意味を持たせるべきです。  

4.2.4 具体例: Cap・Floor付きのクーポン

Cap や Floor は、様々な変動金利クーポンにしばしば付いている条件です。従って、我々のフレームワークの中でも、そのような条件を、出来る限り汎用的な方法で、様々な FloatingRateCoupon の派生クラスで加えたいと思っています。 

そういった要求を満たすには、Decoratorパターンを使うのがふさわしいでしょう。それを取り込んだ CappedFlooredCouponクラスの実装内容を下記 Listing 4.10 に示します。 

Listing 4.10: Implementation of the CappedFlooredCoupon class 

class CappedFlooredCoupon : public FloatingRateCoupon {
      public:
        CappedFlooredCoupon(
                  const shared_ptr<FloatingRateCoupon>& underlying,
                  Rate cap = Null<Rate>(),
                  Rate floor = Null<Rate>());
        Rate rate() const;
        Rate convexityAdjustment() const;

        bool isCapped() const { return isCapped_; }
        bool isFloored() const { return isFloored_; }
        Rate cap() const;
        Rate floor() const;
        Rate effectiveCap() const;
        Rate effectiveFloor() const;

        virtual void accept(AcyclicVisitor&);

        void setPricer(
                const shared_ptr<FloatingRateCouponPricer>& pricer);
    protected:
        shared_ptr<FloatingRateCoupon> underlying_;
        bool isCapped_, isFloored_;
        Rate cap_, floor_;
    };
  CappedFlooredCoupon::CappedFlooredCoupon(
            const boost::shared_ptr<FloatingRateCoupon>& underlying,
            Rate cap, Rate floor)
    : FloatingRateCoupon(underlying->date(),
                         underlying->nominal(),
                         ...other coupon parameters...),
      underlying_(underlying),
      isCapped_(false), isFloored_(false) {

        if (gearing_ > 0) {
            if (cap != Null<Rate>()) {
                isCapped_ = true;
                cap_ = cap;
            }
            if (floor != Null<Rate>()) {
                isFloored_ = true;
                floor_ = floor;
            }
        } else {
            if (cap != Null<Rate>()) {
                isFloored_ = true;
                floor_ = cap;
            }
            if (floor != Null<Rate>()) {
                isCapped_ = true;
                cap_ = floor;
            }
        }
        registerWith(underlying);
    }
    Rate CappedFlooredCoupon::rate() const {
        Rate swapletRate = underlying_->rate();
        Rate floorletRate = isFloored_ ?
            underlying_->pricer()->floorletRate(effectiveFloor()) :
            0.0;
        Rate capletRate = isCapped_ ?
            underlying_->pricer()->capletRate(effectiveCap());
            0.0;
        return swapletRate + floorletRate - capletRate;
    }
     Rate CappedFlooredCoupon::convexityAdjustment() const {
       return underlying_->convexityAdjustment();
    }
    Rate CappedFlooredCoupon::cap() const {
        if ( (gearing_ > 0) && isCapped_)
                return cap_;
        if ( (gearing_ < 0) && isFloored_)
            return floor_;
        return Null<Rate>();
    }

    Rate CappedFlooredCoupon::floor() const {
        if ( (gearing_ > 0) && isFloored_)
            return floor_;
        if ( (gearing_ < 0) && isCapped_)
            return cap_;
        return Null<Rate>();
    }
    Rate CappedFlooredCoupon::effectiveCap() const {
        if (isCapped_)
            return (cap_ - spread())/gearing();
        else
            return Null<Rate>();
    }

    Rate CappedFlooredCoupon::effectiveFloor() const {
        if (isFloored_)
            return (floor_ - spread())/gearing();
        else
            return Null<Rate>();
    }
   void CappedFlooredCoupon::setPricer(
               const shared_ptr<FloatingRateCouponPricer>& pricer) {
        FloatingRateCoupon::setPricer(pricer);
        underlying_->setPricer(pricer);
    } 

この後すぐに見ていきますが、このクラスは Decoratorパターンの典型的な様式に従っています。すなわち、ベースクラスである FloatingRateCouponインスタンスに対するポインターを保持した上で、必要な追加動作(メソッド)を実装したり、あるいは保持したポインターを使ってベースクラスのメソッドを使ったりします。しかし、C++では、他の言語と違って、Decoratorパターンを実装する為の他の方法も用意されている点に注意下さい。すなわち、テンプレートと派生クラスを一緒に使って、次のようなコードを書くことも可能なのです。 

    template <class CouponType>
    class CappedFloored : public CouponType; 

このクラスTemplate を使えば、例えば、CappedFloored<IborCoupon> や、CappedFloored <CmsCoupon> といったインスタンスを生成し、これらのクラスの中で必要な追加動作を実装したり、ベースクラスで用意されているメソッドを呼び出したりすることが可能です。現段階で、今の実装方法から、このクラスTemplate を使う方法に変更する強い理由は見当たりません。しかし、興味を引く問題なので、これから CappedFlooredCouponクラスを説明する中で、仮にクラスTemplate方式を使った場合の、良い点、悪い点などの相違点も説明していきます。 

まずコンストラクターを見ると、当然ですが、引数として、これから Decorate しようと する FloatingRateCouponインスタンス (へのポインターを) を取り、メンバー変数に保持しています。 

一方、驚く点は、このコンストラクターは、引数として渡された Couponクラスのインスタンスからデータを取り出し、それをベースクラスである FloatingRateCouponクラスのコンストラクターに渡して、自らのメンバー変数として保持していることです。この方法は、通常の Decoratorパターンの実装方法とは、少し離れています。通常なら、下記コードのように、渡された Couponインスタンスのインターフェースを使って、そのインスタンスのメンバーデータを使うようにするでしょう。 

Date CappedFlooredCoupon::startDate() const {
        return underlying_->startDate();
    } 

今の実装方法は、あえてこのようにせず、(Decorateする為に作られたオブジェクトが)自分自身で(Decorateの対象となる)Coupon情報を保持して、それを使っています。例えば、startDate( )メソッドは、コピーされたメンバー変数をそのまま返しているだけです。その結果、いくつかの情報は2重に保持している事になり、最適とは言えません。しかし、一方で、こうする事によって、ポインターを使った何件ものメソッドの呼び出しを避けることができます。もし別の方法を取るとすれば、Couponクラスのメソッドをすべて仮想関数として宣言し、すべてOverrideする方法です。 

仮に、クラスTemplate から派生させる方法を使ったとしたら、プログラムのデザインはもう少し簡潔になったかも知れません。その場合、Decorator の対象となる Couponオブジェクトは Decorator すなわち自分自身であり、情報を2重に保持するといった状態は避けられます。しかし、その場合でも、コンストラクターは、情報をコピーせざるを得ないのです。すなわち、次のようのコードを書く必要があり、 

template <class CouponType>
    CappedFlooredCoupon(const CouponType& underlying,
                        Rate cap, Rate floor)
    : CouponType(underlying), ...

これはより望ましい方法とは言えません。すなわち、コンストラクターは、(Decoratorの) 対象となる Couponオブジェクトのインスタンスを生成するのに必要な引数を取るため、別途それを外生的に構築してから引数として渡し、データをコピーし、使用後は消去しなければならないのです。これはひとつには、C++ に内包するいわゆる Forwarding Problem と呼ばれている理由によるもので (下記[1]参照)、もうひとつは、Couponクラスは相当な数のパラメータを必要とするので CappedFlooredCouponオブジェクト におそらく1ダース程度のテンプレートコンストラクターを用意する必要がある為です。いずれの問題も、我々が古いタイプのコンパイラーを使っているため 2003年バージョンの C++ に依存しているからであり、2011年版の C++ であれば、Perfect Forwarding (下記[2]参照) と Variadic Templates (下記[3]参照) によって、それぞれこの問題は解決できるかも知れません。従って、将来的には、このデザインを見直すかもしれません。 

残りのコンストラクターの引数は、Cap金利と Floor金利です。もしいずれか一方が不要であれば、デフォールト設定の Null<Rate>( ) が引数として渡されます。コンストラクターに渡された Cap あるいは Floor金利は、いずれもメンバー変数に保持されますが、その時 2ステップほど操作をしています。最初の操作は、Cap/Floor金利以外に、それぞれについて Bool変数を保持し Cap や Floor が付いているかどうかのフラッグになります。この方法は、QuantLib Library の他の箇所で行っている方法とは若干異なります。他の箇所では、データの値を保存するのみで Null<Rate>( ) を使ってデータが使われているかどうかチェックしています。それとは別の Bool変数を保持するのは二度手間のように見えますが、Null値を魔法の数字のように使うのを避けています。boost::optional のような型を使う方が良かったかも知れません。この型は Null値を保持することになりますが、意図が言葉上より明確です。実際の所、Nullクラスを消して boost::optional に変更したいのですが、現段階ではそれに依存するコードが膨大にあるため躊躇しています。 

もう一つの操作は、Cap金利は最大金利として保持されているとは限らないという事です。対象金利インデックスに対する‘倍率’(gearing) の値がプラスの場合は問題ないのですが、マイナスの場合は floor_ のメンバー変数に保持されます。これは、倍率がマイナスの場合、変動金利インデックスにつく Cap は実質的に Floor になっている為です。(訳注: リバースFloaterの場合 “固定金利 - 倍率 × 変動金利インデックス” というクーポン計算式になるが、この場合、倍率の前の符号がマイナスになっている。計算後のクーポンは通常 0パーセントが Floor となるので変動金利につく Cap は実質的に Floor金利になる) しかし、この方法は、コンストラクターとインスペクター関数にとって複雑で紛らわしい操作を要求します。Cap と Floor は引数をそのままそれぞれのメンバー変数に保持する方が良かったかも知れません。 

rate( ) が、実際に引数で取った Couponインスタンスのメソッドの内、まさにDecorateされる部分です。 我々の方法は、クーポンレート (訳注:コード中の swapletRate に該当) に Floorlet金利 (クーポン期間に渡って、Floorletの価格を与える金利) を加え、Caplet金利を引くことによって調整するようになっています。この2種類の金利の計算は対象となる Couponクラスの Pricer に委託されていますが、引数として実効Cap金利と実効Floor金利を必要とします。この後でみていきますが、これらの実効CAP金利や実効Floor金利は対象金利インデックスに適用になるものであり、Couponレートそのもの適用になるものではありません。 

次のメソッドは convexityAdjustment( )メソッドです。このメソッドは、単に対応する対象 Couponオブジェクトのメソッドに動作を投げて、帰ってきた計算結果をそのまま返しているだけです (偶々ですが、Decoratorパターンとして Template を使った継承の方法を使ったとすれば、このような動作を何のコストも払わずに取り込めました)。これは、クーポンに Cap や Floor金利が適用になれば Couponレートが変更されて、さらなる Convexity調整が必要になりそうなので、少し変に見えます。しかし実際には、Cap や Floor金利を勘案しても convexityAdjustment( )メソッドの意味を変えるわけではなく、それはもともと、Couponレートではなく対象金利インデックスの fixingレートを調整するためのものだからです。 

cap( )と floor( )メソッドは保持しているCap金利とFloor金利を、‘倍率’gearingの符号がマイナスの場合は Cap と Floor を入れ替えて (コンストラクターが入れ替えたものを基に戻すため)、それぞれ返します。すでに述べたように、Cap金利と Floor金利を保持する時に、コンストラクターが入れ替えの操作を行わないように改良すれば、この操作は不要になります。もし必要なら、この入れ替えの動作は、effectiveCap( )と effectiveFloor( )メソッドの中で実行してもいいかも知れません。今の実装では(これらのメソッドは入れ替えが済んだものとして)、クーポン倍率とスプレッド計算の為だけに、Cap金利と Floor金利を調整して、対象金利インデックスに適用になる Cap金利と Floor金利を返しています。 

最後に setPricer( )メソッドについてです。すべての整合性を取るため、引数として渡された Pricerオブジェクトは、対象Couponクラスのインスタンスと、自分自身 (CappedFlooredCouponクラス) のインスタンスの両方にセットされます。若干問題がありそうに見えます。もし Decoratorパターンを素直に使っていれば、あるいはクラスTemplateからの派生クラスを使っていれば、Pricerの保持が重複している部分と、必要以上の動作を行っている部分は、不要であったかもしれません。しかし、これまでにプログラム開発者としてもっとひどい罪を犯してきたものとしては、このメソッドの中だけに起こっているこの程度の重複はお許し頂きたいと思います。 

これで、CappedFlooredCouponクラスは完成です。具体例を使ってまとめると、このクラスから派生させて Cap付きあるいは Floor付きの Couponクラスのインスタンスを簡単に生成する事が可能です。そのような派生クラスを Listing 4.11 に示しますが、これは Cap付きあるいは Floor付きの LIBORインデックスによる変動金利クーポンをベースクラスから派生させたものです。このクラスはコンストラクターを定義しているだけです。そこでは、すでに生成された Couponクラスのインスタンスを引数として取るのではなく、対象Couponクラスの生成に必要な引数を取り込み、それを使ってそのインスタンスを自分で生成し (訳注:プログラム中のnew IborCoupon(…)の部分)、それをベースクラスのコンストラクターに渡しています。この方法により、Couponクラスを(外生的に)生成するための面倒な作業を無くしています。仮に、CappedFlooredCouponクラスをクラスTemplateの形で設計していたとしても、この実装内容は全く同じになります。 

Listing 4.11: Implementation of the CappedFlooredIborCoupon class 

class CappedFlooredIborCoupon : public CappedFlooredCoupon {
      public:
        CappedFlooredIborCoupon(
                const Date& paymentDate,
                const Real nominal,
                const Date& startDate,
                const Date& endDate,
                ...other IBOR-coupon arguments...
                const Rate cap = Null<Rate>(),
                const Rate floor = Null<Rate>(),
                ...remaining arguments...)
        : CappedFlooredCoupon(
            shared_ptr<FloatingRateCoupon>(new IborCoupon(...)),
            cap, floor) {}
    }; 

最後に、ありうる別の実装方法についてです。この具体例で説明したクラスは、Cap と Floor の両方とも Null となるケースにも適用できますが、その場合、このクラスは2つの Bool変数と、どちらが使われているのかを追跡するロジックをコード化する必要があります。もし Cap と Floor それぞれに Decorator を作れば、この作業は避けることが出来るかもしれません。それぞれの Decorator が別々に使われることになり、使われない方のデータの為に Null の変数を保持する必要なくなります。しかし、同じような構文を持つ2つの Decorator を構築することは、ある程度のプログラムコードの重複は避けられないでしょう。また一方の Decorator が Cap に対応するものなのか、Floor に対応するものなのかを示す Flag とそれを管理するためのロジックの構築も必要になるでしょう。もし読者の方が、その方法による実装を試したいのなら歓迎します。今の実装方法との比較についてどうなるのか、教えて頂けることを歓迎します。 

 

[1] P. Dimov, H.E. Hinnant and D. Abrahams, The Forwarding Problem: Arguments. C++ Standards Committee Paper N1385, 2002. 

[2] H.E. Hinnant, B. Stroustrup and B. Kozicki, A Brief Introduction to Rvalue References. C++ Standards Committee Paper N2027, 2006. 

[3] D. Gregor, A Brief Introduction to Variadic Templates. C++ Standards Committee Paper N2087, 2006. 

4.2.5 キャッシュフロー配列の生成

Couponキャッシュフローが一本しかないケースは稀です。普通は、Couponは連続したキャッシュフローの塊として存在し、それが故に、モデルライブラリーの設計者の頭痛の種になります。もちろん、Coupon支払いのスケジュールに従って、Couponの Leg(配列) を構築するような機能を提供したいと思っていましたが、そうする為に、いくつかのインターフェース上の問題に直面しました。 

最初に、我々はそのような機能を、一番手っ取り早い方法でプログラムしてみました。すなわち、必要なパラメータをすべて取り込み (極めて単純なケースを除いては、相当な数になります)、対応する Leg を構築し、そのインスタンスを返すという、自己完結する関数です。しかし、出来上がったプログラムコードは、クライアントコード (訳注:Libraryを使う為にユーザーが独自に作ったプログラムコード) から呼び出すには使いにくいものでした。例えば、変動金利クーポンの Leg を構築するためには、ユーザーは次のようなプログラムコードを書かなければなりません。 

    vector<shared_ptr<CashFlow> > leg =
        IborLeg(schedule, index,
                std::vector<Real>(1, 100.0),
                std::vector<Real>(1, 1.0),
                std::vector<Spread>(1, 0.004),
                std::vector<Rate>(1, 0.01),
                std::vector<Real>(),
                index->dayCounter(),
                true); 

上記のコードは2つの問題を抱えています。ひとつは、引数の数が多い関数全般に言える問題点で、関数の宣言のプログラムコードまで戻って見ない限り、どのような引数を取ろうとしているのか判らないという事です (上記例では、みなし元本が 100、gearing(倍率)が 1 、スプレッドが 40bp、Floor金利が 1%、金利決定が後決め、といった情報を渡していますが、上のコードからそれが簡単に推察できるでしょうか?)。この問題は、次のコードのように引数に名前をつければ若干和らげることができます。 

    std::vector<Real> notionals(1, 100.0);
    std::vector<Real> unit_gearings(1, 1.0);
    std::vector<Real> no_caps;
    bool inArrears = true; 

しかし、こうすると関数を呼び出す前に各引数の定義を行わなければならず、プログラムコードが冗長になります。さらに、いくつかの引数は特定のデフォールト値を持ちますが、もし引数のひとつで、デフォールト値から値を変えたい場合、その前にくるすべての引数も、改めて値を特定しなければなりません (訳注:デフォールト値が使えなくなるので関数呼び出し時に値を与えないといけなくなる)。例えば、上記例で、金利の後決めを指定した場合、(その前に来る引数である)日数計算方法 (DayCounter) やすべての Cap金利に null値の配列を、引数として渡す必要があります。 

ふたつ目の問題は、我々のドメイン特有のものです。Coupon日付はもとより、他の引数も Coupon毎に異なるケースがあります。例えば、みなし元本は、Amortization (元本が一定期間ごとに逓減していく) スケジュールが付いているような条件では、Coupon毎に減少していきます。また Cap/Floorコリドーの条件が (CapやFloorの金利が一定ではなく)、フォワード金利に沿うような形で決まっているものもあります。Couponキャッシュフローを構築する関数は、できるだけ汎用的にするために、そのような引数はすべて配列で取り込む必要があります。しかし乍ら、そのような引数が、仮に一定であったとしても、コードが冗長になってしまいますが、配列を生成せざるを得ません。しかも、そのようなケースが大半にもかかわらず。(注:関数をオーバーロードして、引数を配列で取る場合と、単一の数値で取る場合の両方に対応できるように出来ますが、そういった類の引数が N 個あったとすれば、オーバーロード関数の数は 2N 個になます。引数が増えれば、あっという間にその数は管理できないほど大きな数になってしまいます。) 

あるひとつの方法が、両方の問題を解決します。それは、C++ の世界ではずいぶん昔から知られていた慣用句で、“Named Parameter Idiom”と呼ばれるもので (注:1998年刊行の“C++ FAQs” の中で、すでに常用されている慣用句として紹介されています。下記[1]参照)、Martin Fowlerが主導する dynamic-language派の中では “Fluent Interface” と呼ばれていた人気の高い手法でした(下記[2]参照)。考え方は極めて単純です。関数を使わずに、オブジェクトを使って Couponの配列を構築しようとするものです。関数のように、呼び出しの際にすべての引数を一度に渡そうとするのではなく、引数ごとに解りやすい名前のついたメソッドで数値を渡していくものです。この方法を使うと、下記のコードのように、すでに紹介した関数による呼び出しよりも、より少ないプログラムコードで、かつ意味が明瞭な方法で同じ動作が行えます。 

   vector<shared_ptr<CashFlow> > leg =
        IborLeg(schedule, index)
        .withNotionals(100.0)
        .withSpreads(0.004)
        .withFloors(0.01)
        .inArrears(); 

 IborLegクラスの実装内容の一部を下記 Listing 4.12 に示します。 

Listing 4.12: Partial implementation of the IborLeg class 

    class IborLeg {
      public:
        IborLeg(const Schedule& schedule,
                const shared_ptr<IborIndex>& index);
        IborLeg& withNotionals(Real notional);
        IborLeg& withNotionals(const vector<Real>& notionals);
        IborLeg& withPaymentDayCounter(const DayCounter&);
        IborLeg& withPaymentAdjustment(BusinessDayConvention);
        IborLeg& withFixingDays(Natural fixingDays);
        IborLeg& withFixingDays(const vector<Natural>& fixingDays);
        IborLeg& withGearings(Real gearing);
        IborLeg& withGearings(const vector<Real>& gearings);
        IborLeg& withSpreads(Spread spread);
        IborLeg& withSpreads(const vector<Spread>& spreads);
        IborLeg& withCaps(Rate cap);
        IborLeg& withCaps(const vector<Rate>& caps);
        IborLeg& withFloors(Rate floor);
        IborLeg& withFloors(const vector<Rate>& floors);
        IborLeg& inArrears(bool flag = true);
        IborLeg& withZeroPayments(bool flag = true);
        operator Leg() const;
      private:
        Schedule schedule_;
        shared_ptr<IborIndex> index_;
        vector<Real> notionals_;
        DayCounter paymentDayCounter_;
        BusinessDayConvention paymentAdjustment_;
        vector<Natural> fixingDays_;
        vector<Real> gearings_;
        vector<Spread> spreads_;
        vector<Rate> caps_, floors_;
        bool inArrears_, zeroPayments_;
    }; 
    
    IborLeg::IborLeg(const Schedule& schedule,
                     const shared_ptr<IborIndex>& index)
    : schedule_(schedule), index_(index),
      paymentDayCounter_(index->dayCounter()),
      paymentAdjustment_(index->businessDayConvention()),
      inArrears_(false), zeroPayments_(false) {}

    IborLeg& IborLeg::withNotionals(Real notional) {
        notionals_ = vector<Real>(1,notional);
        return *this;
    }

    IborLeg& IborLeg::withNotionals(const vector<Real>& notionals) {
        notionals_ = notionals;
        return *this;
    }

    IborLeg& IborLeg::withPaymentDayCounter(
                                     const DayCounter& dayCounter) {
        paymentDayCounter_ = dayCounter;
        return *this;
    }

    IborLeg::operator Leg() const {

        Leg cashflows = ...

        if (caps_.empty() && floors_.empty() && !inArrears_) {
            shared_ptr<FloatingRateCouponPricer> pricer(
                                        new BlackIborCouponPricer);
            setCouponPricer(cashflows, pricer);
       }

        return cashflows;
    }

コンストラクターは、デフォールト値を与える意味があまりなく、同じような型の他の引数と混同する余地が少ない引数を2つ取るのみです。それは、Couponキャッシュフローの Schedule(支払い日の配列) と LIBOR金利インデックス になります。その他のメンバー変数は一旦、固定されたデフォールト値か、引数として渡された値を基に設定されます。(例えば、日数計算方法は、引数として取られた金利インデックス用に設定された DayCounterオブジェクトを使います。) 

それ以外の引数は、setter(パラメータを設定するメソッド)関数を使って、引き渡されます。各 setter関数は、その意味が明確な関数名が付けられ、引数として単一の値をとる場合と配列を取る場合に分けてオーバーロードされています。(注: N 個の引数があれば、2N 個のオーバーロード関数が必要になりますが、それでもこの方法の方が他の選択肢よりも管理しやすいと思います。) 各 setter は IborLegインスタンスへの参照を返す事によって、並べて実行できるようにしています。すべてのパラメータがセットされたら、Leg( ) オペレーターが変換演算をスタートし、実際の Couponインスタンスの配列を生成します。(それ自体はそれほど興味を引く話題ではありません。IborLegオブジェクトに保持された日付や配列の中身を使って、Couponインスタンスの配列を生成します。) 

最後に、一点追加で注意点を述べたいと思います。本件の実装ではなく、Boostライブラリの開発者グループが同じ問題を解決する別の方法を提供してくれました。一見不可能に見える事をやり遂げて、Boost Parameter Library を完成させました。それを使うと、以下のプログラムコードのように、C++ で名前付きパラメータを使うことが可能にしました。 

   vector<shared_ptr<CashFlow> > leg =
        IborLeg(schedule, index,
                _notional = 100.0,
                _spread = 0.004,
                _floor = 0.01,
                _inArrears = true); 

この手法を QuantLib の中で使う誘惑にかられましたが、現状の Named Parameter Idiom をベースにした実装もきちんと機能しており、開発者にとっても負担が少ないものです。ここでは、Boost の解決策が本当に素晴らしいものである事を、紹介したかっただけです。 

 

[1] M.P. Cline, G. Lomow and M. Girou, C++ FAQs, 2nd edition. Addison-Wesley, 1998. 

[2] M. Fowler, Fluent Interface. 2005. 

[3] M. Fowler, K. Beck, J. Brant, W. Opdyke and D. Roberts, Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999. 

4.2.6 他の種類のCouponと今後の開発

当然ながら、他のタイプの Coupon配列を、ここで説明したような形で実装することも可能です。ひとつの例として、その他の金利(インデックス) にリンクする Couponキャッシュフローに拡張可能です。QuantLibライブラリでは、CMS (Constant Maturity Swap) インデックスにリンクする Couponキャッシュフローのような、同じスタイルの実装方法が使える他の金利インデックス Leg のオブジェクトモデルを提供しています。それぞれ独自の Pricer を使っている所が異なるのみです。まだ実装に至っていませんが、デザインを追加することによって、例えばひとつの Monte Carloシミュレーションで、連続する Couponキャッシュフローを生成するような事も出来るかもしれませんので、読者の方に試して頂きたいものです。(注:ヒント;配列メンバーの各 Couponオブジェクトの Pricer が一本の Monte Carloシミュレーションオブジェクトへのポインターを共有し、各Couponオブジェクトを示す整数インデックスを使ってシミュレーション結果から正しい Pay offを取り出すようなデザイン) 

別の例として、金利インデックス以外で決まる Couponキャッシュフローにも同じオブジェクトモデルが使えます。例えばインフレ率に連動する Couponキャッシュフローについては、既に実装済みです。もちろん、このことは、他の種類のインデックスで生成される Couponキャッシュフローについても応用可能かという質問を投げかけています。私としては、クラスのデザインを変える前に、そのような実例が出てくるのをもう少し待ちたいと思います。 

 

<ライセンス表示>

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

目次

Page Top に戻る

// // //