2. "Implementing QuantLib"の和訳

Chapter-IV Cash Flows and Coupons

4.3 Cash-Flow Analysis: キャッシュフローの分析

Couponキャッシュフローの Leg が生成できたら、それを分析したいと思うのが自然なステップになります。QuantLibライブラリは、npv (現在価値) や basis-point sensitivity (金利1bpの動きに対するNPVの感応度), yield (キャッシュフローの利回り), duration (平均残存期間) といった、業界で一般的に使われている計算機能をいくつか提供しています。これらの関数は、CashFlowsクラス内の static メソッドとして定義されているので、例えば‘npv’といったようなQuantLibのメインのnamespaceの中で汎用的に使われる名前と競合する事はありません。同じように競合を避けるには、CashFlowsクラスの内部 namespace の中だけで free function として定義する事も可能でした。 

それぞれの関数自体は、いずれも実務でよく使われる計算式をもとに実装されていますが、プログラムコード的にはあまり興味を引く題材ではありません。ほとんどの関数は、キャッシュフローの配列に対して単純なLoopを使って計算されています。下記 Listing 4.13 に示す npv計算の関数が、その一例です。  

Listing 4.13: Sketch of the CashFlows::npv method 

    Real CashFlows::npv(const Leg& leg,
                        const YieldTermStructure& discountCurve,
                        Date settlementDate) {
        ...checks...

        Real totalNPV = 0.0;
        for (Size i=0; i<leg.size(); ++i) {
            if (!leg[i]->hasOccurred(settlementDate))
                totalNPV += leg[i]->amount() *
                            discountCurve.discount(leg[i]->date());
        }
        return totalNPV;
    } 

しかしそれ以外の関数では、npv( )とは異なり、CashFlowクラスの汎用的なインターフェースでは提供されていない情報にアクセスする必要があったり、(同じ Leg中 で)異なるタイプのキャッシュフローを区別する必要があったりする場合があります。そのような関数の中のひとつに、キャッシュフロー Leg の basis-point sensitivity(金利感応度) を計算するものがあります。この関数は、一定の利率で利息を発生させる Coupon とそうでないキャッシュフローを区別する必要があります。なぜなら、前者のみが金利リスクを発生させるからです。(訳注:キャッシュフローに、クーポン部分と元本償還部分が含まれていた場合、金利リスクに晒されるのは、クーポンキャッシュフローのみ) その区分をした後に、この関数は Couponクラスのインターフェースを使って計算に必要な情報を取り込む必要があります。 

このような区分け動作は、各キャッシュフローに対し dynamic_pointer_cast を行い、うまく行った時だけ計算結果にそのキャッシュフローの項を足していくという方法でも出来そうです。(訳注: Couponの派生クラスのインスタンスであれば、型変換は成功し、計算アルゴリズムが実行されるが、それ以外の CashFlowクラスの派生クラスのインスタンスでは、失敗し計算が行われないような仕組み) しかし casting(型変換)はプログラミングの世界では、眉をひそめられるものとして受け止められており、特に何種類ものキャッシュフローを区別するためにif文を何層にも積み重ねる場合はそうです。 

そこで我々は、明確な castを行うのではなく、Acyclic Visitorパターンを使って bps( )メソッドを実装しました (注: 詳細はAppendix Aをご覧下さい)。注意しておきますが、この方法が絶対という訳ではありません。cast(型変換)を使った場合、プログラムコードが汚くなり、より不明確にしてしまいますが、一方で、Visitorパターンは最も批判の多いデザインパターンで、使い方は簡単ではないという事です。それぞれの関数によって、型変換がいいか、Visitorパターンがいいか選択の余地があります。私のアドバイスは (次の段落で、自分自身でそれを無視しますが)、できるだけ単純な方を選びなさいという事です。 

bps( )メソッドの場合は、cast(型変換)を使うので十分であったであろうと思われ、もしそうした場合には、プログラムコードは以下の様になるかと思います。 

   for (Size i=0; i<leg.size(); ++i) {
       if (shared_ptr<Coupon> c =
                        dynamic_pointer_cast<Coupon>(leg[i]) {
            // do something with c
        }
    } 

このコードでも、オブジェクト指向の原則を大きく踏み外していませんし、Visitorパターンを使う場合に比べて、かなりシンプルです。それでも、我々が Visitorパターンを使った1つの理由は、bps( )メソッドを、より複雑な関数を実装する際の例として使ってみたという事です。複雑な関数で型変換を使うと、プログラムコードに非常に多くの足場をつくらなければなりません。(注: 別の理由は、かっこよくみせるという動機が入っていたかも知れません。この点についてはこれまでも何度かやってしまいました。今だったら、単純な方を選ぶでしょうが、コード例のためにVisitor Patternを使ったという理由は、それなりに意味があり、現時点で実装を変える必要はないかと思います。) 

bps( )メソッドのプログラムコードを Listing 4.14 に示します。ここでは、Acyclic Visitorパターンの仕組みについては簡単に済ませ(詳細は Appendix A で説明します)、これを使うための手順を説明するだけにします。 

Listing 4.14: Sketch of the CashFlows::bps method 

    namespace {

        const Spread basisPoint_ = 1.0e-4;

        class BPSCalculator : public AcyclicVisitor,
                              public Visitor<CashFlow>,
                              public Visitor<Coupon> {
          public:
            BPSCalculator(const YieldTermStructure& discountCurve)
            : discountCurve_(discountCurve), result_(0.0) {}
            void visit(Coupon& c) {
                result_ += c.nominal() *
                           c.accrualPeriod() *
                           basisPoint_ *
                           discountCurve_.discount(c.date());
            }
            void visit(CashFlow&) {}
            Real result() const {
                return result_;
            }
          private:
            const YieldTermStructure& discountCurve_;
            Real result_;
        };

    }

    Real CashFlows::bps(const Leg& leg,
                        const YieldTermStructure& discountCurve,
                        Date settlementDate) {
        ...checks...

        BPSCalculator calc(discountCurve);
        for (Size i=0; i<leg.size(); ++i) {
            if (!leg[i]->hasOccurred(settlementDate))
                leg[i]->accept(calc);
        }
        return calc.result();
    } 

我々がここで設計した Visitorクラス (BPSCalculatorクラス) は、複数のベースクラスから派生しています。その内のひとつは AcyclicVisitorクラスで、CashFlow::accept( AcyclicVisitor&) メソッドの引数の型と整合させるために必要なものです。他のベースクラスは、Visitorテンプレートクラスを(テンプレートを指定して)具体化したもので、計算対象となるキャッシュフローが、Couponクラスなのか、あるいは ashFlowクラスなのかを特定する為のものです。その為、このクラスはそれぞれの templateクラスに合わせてvisit( )メソッドをOverloadしています。Couponクラス用に Overloadされた visit( )メソッドは、Couponクラスから派生するすべてのキャッシュフローに対応し、Couponクラスのインターフェースを使って計算式を実行し、結果を result_ に足しあげていきます。CashFlowクラス用に Overload された visit( )メソッドは、ここでは何もしません。このメソッドは、Couponクラス以外のキャッシュフロー用です。(訳注:CashFlowが元本の場合を想定していると推察され、その場合は金利の Basis Point Sensitivity を計算しない。)。 

このようにしておけば、Visitorクラスは、CashFlowクラスの中でプログラムされたパターンに従って動作します。bps( )メソッドは、npv( )メソッドと同じように、複数のキャッシュフローの配列に対して Loop 計算するようコードされています。Loop がスタートする前に、まず Visitor のインスタンスを生成します。次に、そのインスタンスを引数として、配列中の各 Couponインスタンスの accept( )メソッドが呼び出されます。Coupon毎に、それに対応する Visitorテンプレートクラスの visit( )メソッドが呼ばれ、計算結果を足しあげていきます。Couponクラス以外の CashFlowインスタンスが呼ばれた場合は、何もしません。最後に、計算結果が Visitor から渡され、それを返します。 

既に申し上げた通り、このコードは単純ではありません。型変換を使ったプログラムコードの方が、おそらく半分位の長さで済むでしょう。しかし、Visitorクラスを実装するのに必要な枠組みは、どれも同じようなものです。様々なタイプのキャッシュフローを区別して取り扱う必要がある関数については、この例を参考にでき、最終的にはより効率の良いS/N比(Signal-Noise Ratio) を得られるでしょう。 

4.3.1 具体例:固定金利債券

ここで使う例では、キャッシュフローに関する計算機能を、Pricing Engineの枠組みに沿って説明したいと思います。実例として、‘FixedRateBond (固定金利債)’ を選びました。しかし、“後から見ればよく見える (hindsight is 20/20)”の例えの通り、ほとんどのプログラムコードは、(固定金利債の)ベースクラスである Bondクラスの中に押し込めます (ほとんどの計算は、汎用的であり、大半の債券で使えると判明したので。) FixedRateBondクラスのような派生クラスでは、そのクラス特有のキャッシュフローを生成する為のコードが追加されているだけです。 

ユーザーが、Bondのインスタンスを使って、Clean Price (訳注:経過利息を除いた債券価格)、Dirty Price (経過利息を含めた債券価格)、経過利息、利回り、その他トレーダーが喜ぶような計算値を出すことを期待するのは自然なことです。従ってBondクラスにはそのような計算値を返すメソッドが必要です。そういったものを取り込んだプログラムコードの一部を Listing 4.15 に示します。QuantLibライブラリのソースコードを参照されれば、すべてのプログラムコードを見ることができます。 

Listing 4.15: Partial interface of the Bond class

    class Bond : public Instrument {
      public:
        class arguments;
        class results;
        class engine;

        Bond(...some data...);

        bool isExpired() const;
        bool isTradable(Date d = Date()) const;
        Date settlementDate(Date d = Date()) const;
        Real notional(Date d = Date()) const;

        const Leg& cashflows() const;
        ...other inspector...

        Real cleanPrice() const;
        Real dirtyPrice() const;
        Real accruedAmount(Date d = Date()) const;
        Real settlementValue() const;

        Rate yield(const DayCounter& dc,
                   Compounding comp,
                   Frequency freq,
                   Real accuracy = 1.0e-8,
                   Size maxEvaluations = 100) const;

        ...other methods...

        Real cleanPrice(Rate yield,
                        const DayCounter& dc,
                        Compounding comp,
                        Frequency freq,
                        Date settlementDate = Date()) const;

        Rate yield(Real cleanPrice,
                   const DayCounter& dc,
                   Compounding comp,
                   Frequency freq,
                   Date settlementDate = Date(),
                   Real accuracy = 1.0e-8,
                   Size maxEvaluations = 100) const;
      protected:
        void setupExpired() const;
        void setupArguments(PricingEngine::arguments*) const;
        void fetchResults(const PricingEngine::results*) const;

        Leg cashflows_;
        ...other data members...

        mutable Real settlementValue_;

    }; 

もし、すべての計算メソッドを PricingEngine に委託してしまったら、この例は長いけどあまり興味を引かない例になってしまうでしょう。しかし、そうではありません。債券の settlement value の計算など、いくつかの計算は選択した価格モデルに依存するので、Pricing Engineで計算するのが適当でしょうが、その他の、価格モデルに依存しない計算は、Bondクラスの中で計算でき、Pricing Engineでの重複を避ける事ができます。 

その結果、Bond::resultsクラス (とそれに対応するBondクラス内のmutableなメンバー変数) が保有するデータの数は非常に少なくなっています(訳注:Bond::resultsクラスは、PricingEngineの計算結果を格納する為のオブジェクトであり、PricingEngineで計算する部分が少なければ、このオブジェクトがカバーするデータも少なくなる)。実際のところ、settlement value (将来のキャッシュフローの合計を決済日の時点まで割引いた時価) のみです。(注:NPVは、将来のキャッシュフローを、reference date (通常は時価を計算する当日)まで割引いた時価)。settlementValue( )メソッドと、いまから説明するその他のメソッドの実装内容を Listing 4.16 に示しますが、settlementValue( )メソッドは、Pricing Engineに依存する大半のメソッドと同様、まず計算を起動し、Pricing Engineが自分のメンバー変数に計算結果を代入したかどうかチェックし、その計算結果を返します。 

Listing 4.16: Partial implementation of the Bond class 

    Real Bond::settlementValue() const {
        calculate();
        QL_REQUIRE(settlementValue_ != Null<Real>(),
                   "settlement value not provided");
        return settlementValue_;
    }

    Date Bond::settlementDate(Date d) const {
        if (d == Date())
            d = Settings::instance().evaluationDate();
        Date settlement =
            calendar_.advance(d, settlementDays_, Days);
        return std::max(settlement, issueDate_);
    }

    Real Bond::dirtyPrice() const {
        Real currentNotional = notional(settlementDate());
        if (currentNotional == 0.0)
            return 0.0;
        else
            return settlementValue()*100.0/currentNotional;
    }

    Real Bond::cleanPrice() const {
        return dirtyPrice() - accruedAmount(settlementDate());
    }

    Rate Bond::yield(const DayCounter& dc,
                     Compounding comp,
                     Frequency freq,
                     Real accuracy,
                     Size maxEvaluations) const {
        Real currentNotional = notional(settlementDate());
        if (currentNotional == 0.0)
            return 0.0;

        return CashFlows::yield(cashflows(), dirtyPrice(),
                                dc, comp, freq, false,
                                settlementDate(), settlementDate(),
                                accuracy, maxIterations);
    } 

その他の計算メソッドは、複数のタイプに分けられますが、それぞれのタイプについて上記 Listing に例をあげています。最初のタイプは静的データを返すものです。単純なインスペクター関数という訳ではありませんが、返す値はすでに存在する値であり、価格モデルに依存するものではありません。上記コードの中では settlementDate( )メソッドがそれに該当しますが、与えられた Evaluation Date (価格評価日) に対応する通常の決済日を返します。仮に引数として価格評価日が与えられなかった場合は、グローバル変数として設定されている Evaluation Date を基準に、決済営業日数分だけ先日付の日を導出し、その日とその Bondの発行日とを比較して遅い方の日付を返します。この Listing には載せていませんが、もうひとつ同じタイプの notional( )メソッドがあり、引数として渡された日における元本額 (Amortizationがある場合は、発行時よりは少なくなっている可能性がある)を返します。 

二つ目のタイプは、債券の clean price や dirty price を返すメソッドです。当然、それらの値は、価格モデルに依存するので、原則として Pricing Engine を使って計算されます。しかし、実際には、どのようなモデルを使ったかに関わらず、settlement value をもとに計算できます。上記 Listing に載せている例では、dirtyPrice( )メソッドが該当し、まず settlement value を取ってきて、それを現在のみなし元本で割って%単位に換算します。仮に、現在のみなし元本が null値だった場合は(分母に使われるので割り算が出来ない)、債券が償還された事を意味し、dirty price も null値にします。cleanPrice( )メソッドも、同様に Listing に例示していますが、まず dirty price を取ってきて、そこから経過利息相当額を引いて算出します。最後に yield( )メソッドですが、dirty price、キャッシュフローの配列、決済日、及びその他の必要な引数を取り、CashFlowsクラスのメソッドを呼び出し、そこで計算された債券利回りの値を返します。 

三つ目のタイプは、二つ目のタイプに似ていますが、他で計算された計算結果を返します。違いは、計算に使うパラメータは、Pricing Engine からではなく、関数を呼び出したメソッドから渡されます。そのような例は、Listing 4.15 の最後の方にある cleanPrice( )や yield( )メソッドを Overload したものです。前者は利回りを引数として取り、clean priceを計算して返します。後者は、その逆の計算を行います。これらのメソッドも実際の計算を CashFlowsクラスのメソッドに投げています。 

実際に使用可能な Bondクラスのインスタンスを作る最後のステップは、Pricing Engine を与える事です。Bondクラスで使える簡単な Pricing Engineの例を Listing 4.17 に示します。まずコンストラクターは、Discount Curveを引数として取り、メンバー変数に代入します。calculate( )メソッドは、その代入された Curveインスタンスと Bond のキャッシュフローの配列 (Leg) を CashFlows::npv( )メソッドに渡します。その時、現在価値に割引く日として、決済日が渡された場合は、settlement value が計算され、Curveの reference date(Evaluation Date) が渡された場合は、NPVが計算されます。 

Listing 4.17: Sketch of the DiscountingBondEngine class 

    class DiscountingBondEngine : public Bond::engine {
      public:
        DiscountingBondEngine(
              const Handle<YieldTermStructure>& discountCurve);
        void calculate() const {
            QL_REQUIRE(!discountCurve_.empty(),
                       "discounting term structure handle is empty");

            results_.settlementValue =
                CashFlows::npv(arguments_.cashflows,
                               **discountCurve_,
                               false,
                               arguments_.settlementDate);

            // same for results_.value, but discounting the cash flows
            // to the reference date of the discount curve.
        }
      private:
        Handle<YieldTermStructure> discountCurve_;
    };

最後に残ったのは、Bondクラスのインスタンスを生成するのに必要なデータを与える事です。この項の例として使っている FixedRateBond についてみていきましょう。Listing 4.18 をご覧ください。このクラスがすべき事は、それ程多くありません。コンストラクターは、キャッシュフローの配列のインスタンスを生成するのに必要なパラメータを引数として取ります。いくつかのインスペクター関数を除いては、(ベースクラスである) Bondクラスのメソッドが計算の大半をカバーしているので、その他のメソッドは必要ありません。 

Listing 4.18: Implementation of the FixedRateBond class 

    class FixedRateBond : public Bond {
      public:
        FixedRateBond(Natural settlementDays,
                      const Calendar& calendar,
                      Real faceAmount,
                      const Date& startDate,
                      const Date& maturityDate,
                      const Period& tenor,
                      const std::vector<Rate>& coupons,
                      const DayCounter& accrualDayCounter,
                      BusinessDayConvention accrualConvention =
                                                           Following,
                      BusinessDayConvention paymentConvention =
                                                           Following,
                      Real redemption = 100.0,
                      const Date& issueDate = Date(),
                      const Date& firstDate = Date(),
                      const Date& nextToLastDate = Date(),
                      DateGeneration::Rule rule =
                                            DateGeneration::Backward)
        : Bond(...needed arguments...) {

            Schedule schedule = MakeSchedule()
                                .from(startDate).to(maturityDate)
                                .withTenor(tenor)
                                .withCalendar(calendar)
                                .withConvention(accrualConvention)
                                .withRule(rule)
                                .withFirstDate(firstDate)
                                .withNextToLastDate(nextToLastDate);

            cashflows_ = FixedRateLeg(schedule)
                         .withNotionals(faceAmount)
                         .withCouponRates(coupons, accrualDayCounter)
                         .withPaymentAdjustment(paymentConvention);

            shared_ptr<CashFlow> redemption(
                new SimpleCashFlow(faceAmount*redemption,
                                   maturityDate));
            cashflows.push_back(redemption);
        }
    };

さて、Craig Ferguson (訳注:CBSの The Late Late Show の司会者) の言葉を借りると、「今晩のショーで何を学びましたか?」 ここでの具体例では、Instrumentクラス (ここで はBondクラス) で使う、PricingEngine に依存しない一般的な計算のプログラムコードを示しました。考え方は妥当だと思いますが、(そうでなければ、計算方法を各PricingEngineに移植しなければなりません)、これによる不都合な部分を一点述べなければなりません。この例で示した設計方法では (実際にQuantLibライブラリに実装されている方法になりますが)、PricingEngine の外で計算された計算結果については cashing mechanism (訳注:再計算が必要になるまで計算結果を一時保管しておく仕組み) を使う事ができません。このメカニズムについてはすでにいくつかの項で説明しました。その結果、例えば、現時点の元本額や settlement value の計算を、dirty priceを計算する度に、何度も何度も計算しなければなりません。 

これから逃れる方法は二通りありましたが、いずれも十分満足のいくものではありませんでした。一つ目の方法は、これらの計算メソッドをすべて performCalculation( )メソッドにまとめてしまう事です。例えば、次のようなプログラムコードになります。 

    void Bond::performCalculations() const {
        Instrument::performCalculation();
        Real currentNotional = ...;
        dirtyPrice_ = settlementValue_*100.0/currentNotional;
        cleanPrice_ = ...;
        yield_ = ...;
        // whatever else needs to be calculated.
    }

しかし、この方法では、すべての計算が実行されてしまいます。その中には (再帰計算が必要になるため) 計算時間の遅い yield の計算も含まれています。これでは、債券価格だけを求めたいという場合には不便です。(注:すべての計算結果を求めるというのは、PricingEngine にとっても問題があります。もし、パフォーマンスが主要な課題であれば、一部の計算結果だけに対応する lightバージョンの PricingEngine を別途作成する方法も検討可能かも知れません) 

二つ目の方法は、各メソッドに何等かの caching mechanism (訳注:計算結果を一時的に保存し、計算アルゴリズムが依存する Observable にデータ変更が無い場合は、計算するメソッドが呼び出されても計算を実効せず、その値を使うようにする) を加える事です。これにより、不必要な再計算は避けられますが、瞬く間にプログラムコードが煩雑になってくるでしょう。各メソッドに、それぞれ別の Observerオブジェクトを保持させ、それをすべて対応する Observableオブジェクトに登録し、そこから(再計算の必要性の)通知を受けていたかどうかメソッドが走った都度チェックする必要があります。もし本当に計算速度のパフォーマンス要求が高いようであれば、私なら、メソッドを選んでこの方法を取るでしょう。但し、アプリケーション全体の性能をチェックしてからですが。 

しかし、全体としてみれば、現時点の妥協は十分受け入れ可能でしょう。何度も再計算をする負担は、プログラムコードの単純化(その結果としてメンテナンスしやすさ)とバランスが取れているでしょう。もし caching のニーズが強い場合は、今あるプログラムコードを修正して対応可能です。 

 

<ライセンス表示>

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

目次

Page Top に戻る

// // //