2. "Implementing QuantLib"の和訳

Chapter-V Parameterized Models and Calibration

5.1 Calibration Helper クラス

(前ページで登場した2つのクラスの内)まず CalibrationHelper クラスの説明から始めたいと思います。なぜなら、このクラスは間接的にしか“モデルクラス”(訳注:もう一方の CalibratedModel クラス。こちらは Helperクラスに依存するので、説明は後にする)に依存していないからです(実際の所、このクラスは“モデルクラス”のインターフェースを全く使っていません)。このクラスの実装内容を、下記 Listing 5.1に示します。 

Listing 5.1: Implementation of the CalibrationHelper class.

    class CalibrationHelper : public LazyObject {
      public:
        enum CalibrationErrorType {
                  RelativePriceError, PriceError, ImpliedVolError};

        CalibrationHelper(
            const Handle<Quote>& volatility,
            const Handle<YieldTermStructure>& termStructure,
            CalibrationErrorType calibrationErrorType
                                             = RelativePriceError);

        void performCalculations() const {
            marketValue_ = blackPrice(volatility_->value());
        }
        virtual Real blackPrice(Volatility volatility) const = 0;
        Real marketValue() const {
            calculate(); return marketValue_;
        }

        virtual Real modelValue() const = 0;
        virtual Real calibrationError();
        void setPricingEngine(
                      const shared_ptr<PricingEngine>& engine) {
            engine_ = engine;
        }

        Volatility impliedVolatility(Real targetValue,
                                     Real accuracy,
                                     Size maxEvaluations,
                                     Volatility minVol,
                                     Volatility maxVol) const;
        virtual void addTimesTo(list<Time>& times) const = 0;

      protected:
        mutable Real marketValue_;
        Handle<Quote> volatility_;
        Handle<YieldTermStructure> termStructure_;
        shared_ptr<PricingEngine> engine_;
      private:
        class ImpliedVolatilityHelper;
        const CalibrationErrorType calibrationErrorType_;
    };

    class CalibrationHelper::ImpliedVolatilityHelper {
      public:
        ImpliedVolatilityHelper(const CalibrationHelper& helper,
                                Real value);
        Real operator()(Volatility x) const {
            return value_ - helper_.blackPrice(x);
        }
        ...
    };

    Volatility CalibrationHelper::impliedVolatility(
            Real targetValue, Real accuracy, Size maxEvaluations,
            Volatility minVol, Volatility maxVol) const {
        ImpliedVolatilityHelper f(*this,targetValue);
        Brent solver;
        solver.setMaxEvaluations(maxEvaluations);
        return solver.solve(f,accuracy,volatility_->value(),
                            minVol,maxVol);
    }

    Real CalibrationHelper::calibrationError() {
        Real error;
        switch (calibrationErrorType_) {
          case RelativePriceError:
            error = fabs(marketValue()-modelValue())/marketValue();
            break;
          case PriceError:
            error = marketValue() - modelValue();
            break;
          case ImpliedVolError: {
              const Real modelPrice = modelValue();
              // check for bounds, not shown
              Volatility implied = this->impliedVolatility(
                              modelPrice, 1e-12, 5000, 0.001, 10);
              error = implied - volatility_->value();
            }
            break;
          default:
            QL_FAIL("unknown Calibration Error Type");
        }
        return error;
    }

 このクラスの役割は、Chapter III で説明した BootstrapHelperクラスのそれと似通っています(クラス名そのものがヒントになりませんか?)。市場価格を持つ、単一の“商品”をモデル化したもので ( TermStructureモデルの中の個別のNodeと言ってもいいかもしれません)、その商品の価格を(価格)モデルに従って計算する機能と、その価格が“市場価格”からどのくらい離れているかをチェックする機能を提供しています。実際は、価格計算機能だけにとどまりませんが、その事については後で少しだけ触れます。 

CalibrationHelperクラスは、LazyObjectクラスから派生しています (LazyObjectについては既に説明済でご存知かと思います)。そうしたのは、このクラスは若干の事前計算を必要とする可能性があるという理由からです。Optimization(最適化)を行う際の Target Value(目的値)が市場から直接手に入らない可能性があります (例えば、市場での Quote(価格呈示)がオプション‘価格’ではなく、その Implied Volatilityであるような場合です)。市場で Quote されている Implied Volatility からオプション‘価格’を計算するのは、カリブレーションの前の段階で一回だけ行えば十分であり、市場の Quote に変動があった場合にのみ Lazily に再計算を行えば十分です。(訳注:LazyObjectの Lazy の意味は怠慢という意味ではなく、市場価格が変更になった場合に、すぐにそれに対応して再計算を起動させるのではなく、実際に価格計算が必要になった場合にのみそれを行うといった意味。市場価格が頻繁に変動しても、その都度価格を再計算すると、コンピューターシステムに対する負担が大きくなるため。)。 

 コンストラクターは、3つの引数を取ります。それぞれ、やや汎用性の低い性質のパラメータですが(訳注:従って、ベースクラスではなく派生クラスで特定しても良かったかもしれない)、特に文句を言うほどでもありません。一つ目は、市場の Volatilityデータに対する Handle (訳注:ポインターへのポインター。Appendix A参照)です。それは、Calibrationの対象となるオプションの市場価格が、どのようなモデルを使っていたとしても、(価格ではなく) Volatilityで呈示されているという前提です。 

二つ目の引数は、金利の Term Structure への Handle です。これが計算の時に必ず必要になるという前提です。実際にその通りでしょうが、これは派生クラスでしか使われないので、このベースクラスではなく、私としては、他の計算に必要な引数とともに、派生クラスのコンストラクターで宣言する方が良かったかと思います。 

 最後の引数は、Calibrationエラー(訳注:市場価格と価格モデルでの計算結果との差)をどのように設定するかを指定しています。Enumerationで指定された3種類の選択肢から選びます。3種類とは、①市場価格とモデル価格との相対価格での乖離、②市場価格とモデル価格の絶対値での乖離、③それに市場で呈示の Volatilityと、価格モデルで計算されたオプション価格が内包する (Black)Volatility (訳注:金利の拡散過程が対数正規分布を取ると仮定したモデル(Black Model)の拡散係数)との差の絶対値です。このような、選択肢があるケースでは Strategy Patternを使うことも出来ましたが、それによる複雑化に対して、汎用性のメリットが上回るか確信がなく、enumで対応することにしました。少なくとも、この3つのケース以外にエラーをとらえる方法を思いつきませんでした。 

 コンストラクターの実装内容は、簡潔にするため、ここでは示していません。しかし、実際にやっている事は、通常のコンストラクターの操作と同じで、引数を該当するメンバー変数に保存することと、(Observerである)自分自身を、値が変動する可能性のあるもの(Observable)へ登録する事です。 

既に述べたように、LazyObjectの仕組みは、市場価格に変動があった場合に(それを使ったメソッドが起動された時にのみ)機能します。performCalculations( )メソッドが起動された場合、市場で Quote されている Volatility のレベルに変動があった場合にのみそれをオプション価格に変換して、それをメンバー変数に保持します。実際の計算アルゴリズムは、個別の商品によって異なります。従って、計算は純粋仮想関数である blackPrice( )メソッドに委託されています。メソッド名から明らかなように、市場で呈示されている Volatility のレベルは Black Model(訳注:上記Black Volatilityの注釈参照)を使って計算されているはず、という前提です。最後に、marketValue( )メソッドが、計算結果を返します。 

今説明したこの3つの(似たような目的を持つ)メソッドについては、すべて別個に必要となります。同じような機能のメソッドが3つあると、たしかに煩わしいと思います。performCalculations( )メソッドは、LazyObjectのインターフェースを利用するために不可欠です。では blackPrice( ) と marketValue( ) はどうでしょうか?この2つを一緒にできなかったでしょうか?残念ながらNoです。Inspector関数(メンバー変数の値を返す関数)である marketValue( )メソッドについても、Lazyにしたいので、performCalculations( )を呼ぶかたちにしなければなりません。一方、blackPrice( )メソッドは、performCalculations( )から呼び出される形なので、一緒にできないのです。(これらのメソッドは、blackPrice( )メソッドが Volatility を引数として取っているため、インターフェースも異なっています。但しそれ自体は、保存されている Volatility をデフォールト値として与える事で、対応可能だったかもしれません。) 

つぎの一連のメソッドは、Calibration の対象となる“価格モデル”を使った計算を司ります。純粋仮想関数であり、従って派生クラスで実装される modelValue( )メソッドは、その商品の価格を‘価格モデル’に従って計算し、それを返します。calibrationError( )メソッドは、後でもっと詳しく説明しますが、市場価格とモデル価格との差を、何らかの形で返します。setPricingEngine( )は、引数として渡されたモデルのポインターをメンバー変数に取り込みます。 

ここでの考え方は、メンバー変数として保持される PricingEngine は、モデルへのポインターを持ち、それを使ってこの Helperクラスが対応している商品の価格計算を行い、その計算結果を返します。この考え方を直接的に実現するものとして、QuantLibライブラリの中にあるすべての CalibrationHelper クラスは、modelValue( )メソッドを持っており、そのHelperが対応する商品に、PricingEngineを設定し、それを使ってNPV(Net Present Value)を計算します。 

 実際のところ、各実装方法は非常に似通っているので、共通の実装をベースクラスで提供してもよかったかも知れません。ベースクラスのメンバー変数の中にInstrumentインスタンスへのポインターを加えれば、modelValue( )メソッドの実装方法は、おそらく次のようになったでしょう。 

    void CalibrationHelper::modelValue() const {
        instrument_->setPricingEngine(engine_);
        return instrument_->NPV();
    }   

さらにもう少し手を加えれば、PricingEngine の設定の動作を、modelValue( )を計算する都度ではなく Calibration の最初の時点で、一度で済ませられたかも知れません。しかし、そうした場合のダウンサイドリスクは、メソッドの計算結果が、それが呼ばれた順番に依存していたかも知れないという事です。例えば、marketValue( )を呼び出した後にmodelValue( )を呼び出した場合、先にmarketValue( )メソッドが Instrumentインスタンスに Black Model の PricingEngine を設定してしまい、(その後に、それと知らずに)modelValue( )メソッドが(本来意図していた自分の価格モデルではなく)Blackモデルによる価格を返してしまう可能性があります。マーフィーの法則によれば、うまく行ったとしても仕返しが付いてくる、ですね。 

 setPricingEngine( )メソッドについての最後の注意点ですが、最初に私がそのコードを見たとき、バグがあるように見えました。この Helperクラスは、自らを Observerとして、設定される PricingEngine (Observableになる)に登録すべきだと思いました。しかし、実際にはその必要は無く、PricingEngineは価格モデルの Calibration の為だけに使われており、LazyObjectの仕組みを使って、市場価格の再計算に使われるべきではありません。 

 最後の2つのメソッドは、Calibrationを行う際の補助機能を提供しています。impliedVolatility( )メソッドは、一次元ソルバーを使って、blackPrice( )メソッドの逆関数の動作を行います。すなわち、Blackモデルで計算されたオプション価格から、それにフィットする Volatility(Implied Volatility)を逆算するものです。その実装内容は、上記 Listing 5.1の後半の方にあります。そこには、それに加えて、アクセサリー内部クラスで、ソルバーの目的関数を提供する ImpliedVolatilityHelper クラスの実装内容も書かれています。 addTimesTo( )メソッドは、Treeを使った価格モデルの為に使われます(TreeモデルについてはChapter VIIで説明します)。このメソッドは、引数として渡された時間のリスト (Treeを構築する際の時間間隔(Time Grid) にあわせた時間の配列)に、対象となる商品の価格評価にとって重要な時間のリスト(例えば、支払日、行使日、インデックス確定日など)を加えます。もし、現時点でこのプログラムを書いたとしたら、Treeの Time-Gridのリストに追加の時間を加えた全リストを返すのではなく、追加の時間の部分だけを返すようにプログラムしたかもしれません。しかし、それは重要なポイントではありません。注意すべき点は、このメソッドはある特定のタイプの価格モデル (Treeをベースにしたモデル)にのみ対応するもので、すべての Helper にとって必要なものではありません。従って、派生クラスで実装しなくても良い場合にそなえて、空の実装を含めています。しかし、このメソッドを他のクラス、例えば Treeベースのモデル専用の派生 Helperクラスに移すつもりはありません。ひとつには、そうする事によって複雑さが増すもののアップサイドが無い事、もうひとつは、Helperクラスをそのように分類する事が出来ないというのが理由です。どのような Helperクラスも複数の価格モデルの Calibration に使われる可能性があるという事です。 

最後に、Listing 5.1には、calibrationError( )メソッドの実装内容も示しています。このメソッドは、新しいパラメータに対するCalibrationの計算が行われる毎に呼び出され、価格モデルでの計算結果が市場価格からどの程度離れているかという誤差を返します。“どの程度離れているか”の定義は、enumerationのリストで提供されており、モデル価格と市場価格の相対価格、あるいは価格差の絶対値、あるいはVolatilityの差の絶対値のいずれかです。 

5.1.1 具体例: Hestonモデル

この章では、Heston Model(訳注:Stochastic Volatility Modelの一種。S. Heston著"A Closed-Form Solution for Options with Stochastic Volatility with Applications to Bond and Currency Options" 参照)を具体例として使います。ここでは Helperクラスの方の説明をします。Modelクラスの方は、これに続く CalibratedModelクラスの章で議論します。 

HestonModelHelperクラスの実装内容を Listing 5.2に示します。このクラスは‘ヨーロピアンオプション’をオブジェクトモデル化していますが、問題のにおいを感じます。まずクラス名から、どういった Instrumentを扱っているか判別できません。一方で、クラス名に Heston Model が含まれていますが、このクラスの実装内容にHeston Modelは含まれていません。このクラスがライブラリに加えられた際に、私も含めて、この問題をはっきり認識していませんでした。 

Listing 5.2: Implementation of the HestonModelHelper class. 

    class HestonModelHelper : public CalibrationHelper {
      public:
        HestonModelHelper(
                  const Period& maturity,
                  const Calendar& calendar,
                  const Real s0,
                  const Real strikePrice,
                  const Handle<Quote>& volatility,
                  const Handle<YieldTermStructure>& riskFreeRate,
                  const Handle<YieldTermStructure>& dividendYield,
                  CalibrationHelper::CalibrationErrorType errorType
                             = CalibrationHelper::RelativePriceError)
        : CalibrationHelper(volatility, riskFreeRate, errorType),
          dividendYield_(dividendYield),
          exerciseDate_(calendar.advance(
                           riskFreeRate->referenceDate(), maturity)),
          tau_(riskFreeRate->dayCounter().yearFraction(
                      riskFreeRate->referenceDate(), exerciseDate_)),
          s0_(s0), strikePrice_(strikePrice) {
            boost::shared_ptr<StrikedTypePayoff> payoff(
                 new PlainVanillaPayoff(Option::Call, strikePrice_));
            boost::shared_ptr<Exercise> exercise(
                                new EuropeanExercise(exerciseDate_));
            option_ = boost::shared_ptr<VanillaOption>(
                           new VanillaOption(payoff, exercise));
            marketValue_ = blackPrice(volatility->value());
        }
        Real modelValue() const {
            option_->setPricingEngine(engine_);
            return option_->NPV();
        }
        Real blackPrice(Real volatility) const {
            return blackFormula(Option::Call,
                                /* ...volatility, stored parameters... */);
        }
        void addTimesTo(std::list<Time>&) const {}
      private:
        boost::shared_ptr<VanillaOption> option_;
        // other data members, not shown
    }; 

いずれにしても、実装自体は複雑ではありません。コンストラクターは、対象オプションに関する情報(行使期限や行使価格など)、市場で Quoteされている Volatility やその他の市場データを引数として取りこんでいます。さらに Calibrationエラーの認識方法の enum も引数として取ります。引数の一部はベースクラスのコンストラクターに渡され、他の引数は自らのメンバー変数に保存されます。最後に、それらの引数を使って VanillaOptionクラスのインスタンスを生成し、その市場価格を計算します。その VanillaOptionインスタンスと計算された市場価格もメンバー変数に保存されます。 

 一見しただけでは判りにくいのですが、 Optionインスタンスの生成方法に少し問題があります。コンストラクターはオプション期日までの期間(年数)を示す \( \tau \) を、本日から満期日までの期間として計算しています。残念ながら、このプログラムでは本日の日付が計算の途中で変わる可能性を考慮していません。その様な場合でも正しく動作させる為には、\( \tau \) と marketValue_ を値が変わる度に再計算するようにしなければなりませんが、そうするには、performCalculations( )を Overrideしてその中で計算させるようにしなければなりません。 

 その他の部分は単純です。すでに述べたように、modelValue( )メソッドは価格モデルを備えた PricingEngineを対象オプションにセットし、それを使って NPV (Net Present Value)を計算します(この仕組みは、Hestonモデルに限らずどのような価格モデルの PricingEngineを使っても作動するので、クラス名が気に入らない理由です)。blackPrice( )メソッドは、引数で渡された Volatilityや他の変数を使って Black-Scholesモデルを使ったオプション価格を返します。(Black-Scholesモデルによるオプション価格は、Instrumentインスタンスに Black Engineをセットして、Instrumentから NPV( )メソッドを呼び出しても計算できます。QuantLibライブラリの中の他の Helperでは、そのような方法を取っているものもあります) 

 最後の、addTimesTo( )メソッドは何もしません。このメソッドは Hestonモデルを使った場合にのみ使われる予定でしたが、このライブラリの中には Treeベースの Hestonモデルは含まれていません。このメソッドを汎用的にしたいのであれば、このメソッドがオプション行使期間を返すようにすれば(その値は上記の \( \tau \) に相当するでしょうが)他のモデルでも使えるようになるでしょう。 

 HestonModelHelperクラスの説明は以上です。ベースクラスで構築された仕組みが、派生クラスで実装されたメソッドを利用して Calibrationに必要な計算機能を提供します。その部分の説明に行く前に、少し寄り道をします。 

< Aside : 前提が崩れた場合 >

 仮に、プログラムコードが前提としていた事が、実際にはそうでなかったとすればどうなるでしょうか?例えば、市場で Quoteされている Volatilityが Blackモデルを使っていなかったとすれば、あるいはそのオプション商品の Volatilityがそもそも Quoteされていなかったとすればどうなるでしょうか? 

 その場合でも、プログラムは作動しますが、クラス名やメソッド名が意図したものと異なるため、プログラムコードを理解する妨げになるでしょう。例えば、Volatilityからオプション価格を導出する計算式がBlackモデルではなかったとしたら、それでもその計算式をblackPrice( )メソッドの中に実装せざるを得ず、頭がこんがらがってしまうかもしれません。あるいは、市場価格のQuoteがVolatilityではなく、オプション価格そのものであった場合は、その価格そのものをメンバー変数であるvolatility_に格納し、それをblackPrice( )メソッドで呼び出そうとすると、次のような頭を混乱させるようなコードを実装する事になります。 

    Real blackPrice(Real volatility) const {
        return volatility;
    } 

このようなケースに対応するには、メソッドや変数の名前に、より汎用的なものを使って (例えば volatilityのかわりに quote、blackPrice( )のかわりに marketPrice( )といったように)実装内容を派生クラスに委託し、そこでそれらの意味をより明確にするようなメソッドの引数名に変えるという方法があります(派生クラスのメソッドの引数名を変更しても Compilerは気にしません)。そうやっても、プログラムコードを読む人間には、依然ややこしいものではありますが。 

 

<ライセンス表示>

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

目次

Page Top に戻る

// // //