2. "Implementing QuantLib"の和訳
Appendix A Odds and Ends 周辺の話題
A.2 Date Calculations: 日数計算方法
日数計算の機能は、クオンツファイナンスにおける基本的な道具のひとつです。当然ながら、QuantLibライブラリーも、いくつかの日数計算の機能を提供しています。以下のセクションで、これらについて簡単に解説します。
A.2.1 Dates and Periods: 日数と期間を扱うクラス
Dateクラスのインスタンスは、個別の日付(例えば2014年11月15日)を表現するオブジェクトです。このクラスは、いくつかのメソッドを用意しており、該当する日付に関する情報(たとえば営業日に該当するかどうか、ある月や年の何日目になるのか、等)を取りだす事ができます。その他、個別の日付で許容されている最小と最大の日付(今の所、それぞれ1901年1月1日と2099年12月31日に設定されています)、その年が閏年かどうか、その日をExcelで使っている日付のSerialナンバーに換算した値、その日が月末に該当するかどうか、といった情報も取りだす事ができます。提供されているメソッドとそのインターフェースの全リストは、QuantLibライブラリーのReference Manualに掲載されています。但し、「時間」の情報(その日の何時何分か)は提供されていません。(今、それを加えるか検討中です。)
Dateクラスでは、C++の機能を活用して、算術演算子をオーバーロード関数で定義しており、日付に関する代数計算を自然に行う事ができます。例えば、++d という表現は、特定の日 d を一日前に進める操作になり、d + 2 は d を 2日分先に進める操作を表し、d2 – d1 はふたつの日付 d1 と d2 の間の日数を表し、d – 3*Weeks は d の3週間前の日付を表します(WeeksはTimeUnitのenumerationで提供されているカレンダー単位のひとつで、他の enumメンバーにはDays、Months、Yearsがあります)。また d1< d2 という比較演算子は、d1 が d2 の前にあるなら trueを返します。Dateクラスが提供しているこういったオーバーロードされた演算子の機能は、カレンダー日付を前提にしており、休日や営業日調整の慣行は考慮されていません。
Periodクラスは、2日や3週間や5年といった期間の長さをオブジェクトモデル化したもので、TimeUnit(日、週、月、年といった期間の単位)と、その長さを表す整数を組合せて、データを保存しています。このクラスが提供している、オーバーロードされた算術演算子と比較演算子は限られています。理由は、カレンダーは完全な算術計算に馴染まない傾向がある為、2つのPeriodインスタンスについてどちらが長いか短いか、簡単に比較できないからです。例えば、11か月は1年より短いのは明らかですが、60日が2か月より短いか長いかは、どの2か月を取るかで異なります。仮に、比較がうまく出来ない場合は、例外処理に飛ぶようになっています。
比較内容が明らかな場合であっても、いくつかの驚くような事を克服する必要があります。例えば次のような比較ですが
Period (7, Days) == Period (1, Weeks)
true を返します。当然、正しい結果と思いますよね? 覚えておいて下さい。
A.2.2 Calendars カレンダークラス
Calendarクラスは、休日と営業日の情報を取扱います。このクラスには、数多くの派生クラスが存在しており、それらは具体的な市場毎の休日の情報を定義しています。ベースクラスは、いくつかのメソッドを提供しており、特定の日が休日か営業日かを判定するシンプルなメソッドや、仮にその日が休日だった場合に、最も近い営業日を特定する少し複雑なメソッド(最も近いという操作は、BusinessDayConvention の enumeration に従って決定されます)、さらに、与えられた日数あるいは営業日数をもとに、カレンダーをその期間だけ前に進めるメソッドなどです。
Calendarクラスの記述の仕方によって、その動作がどのように異なるのかを見てみるのは興味深い事です。ひとつの方法は、Calendarインスタンスに、その特定の市場の休日情報をすべてリストで持たせる事です。しかし、この方法だと休日の変更があった場合などメンテナンスの問題が発生します。
従って、我々は、休日決定のルール(例えば、11月の第4木曜日とか、各年の12月25日とかいう決まり)をプログラムコード化する方法を取るべきと考えました。そうすると、polymorphism(多相性)を持たせたTemplate Methodパターンを使うのが有力な方法です。この方法では、派生クラスで、ベースクラスのisBusinessDay( )メソッドをオーバーライドして、様々な国のカレンダーに対応します。これでもOKですが、欠点としてCalendarインスタンスを、shared_ptrsを使って、他のオブジェクト等に渡したり、データを保持したりする必要があります。このクラスは概念的にもシンプルで、しかも頻繁に使われるオブジェクトなのでユーザーが簡単にインスタンス化し、利用できるようにした方が良く、そうすると(shared_ptrsによる)メモリーのdynamic allocationの仕組みは余計な冗長さになるかも知れません。
最終的に我々が取った方法は、下記Listing A-1に示すような形です。これはpimple idiom (訳注:Pointer to Implementation idiomの略)のバリエーションで、StrategyパターンやBridgeパターンの名残でもあります。最近の若い人たちは、type erasure(型消去のテクニック)とも呼んでいるようです。
Listing A1:Calendarクラスの概要
class Calendar {
protected:
class Impl {
public:
virtual ~Impl() {}
virtual bool isBusinessDay(const Date&) const = 0;
};
boost::shared_ptr<Impl> impl_;
public:
bool isBusinessDay(const Date& d) const {
return impl_->isBusinessDay(d);
}
bool isHoliday(const Date& d) const {
return !isBusinessDay(d);
}
Date adjust(const Date& d, BusinessDayConvention c = Following) const {
// uses isBusinessDay() plus some logic
}
Date advance(const Date& d,
const Period& period,
BusinessDayConvention c = Following,
bool endOfMonth = false) const {
// uses isBusinessDay() and possibly adjust()
}
// more methods
};
手短に説明すると、Calendarクラスは polymorphic な内部クラス Impl を宣言し、営業日決定(or 休日決定)ルールの実装をその派生クラスに委任し、自らはその pointer を保持するものです。仮想関数では無い isBusinessDay( )メソッドは、それに対応する Impl派生クラスのメソッドを呼び出すだけです。その他のメソッドも、Template Methodパターンをある程度取り入れ、仮想関数としてでは無く、isBusinessDay( )メソッドを直接あるいは間接的に使って実装されています。
(注:同じテクニックは次のセクションのDayCounterクラスや、Chapter VIで説明したParameterクラスでも使われています。)
派生Calendarクラスは、Calendar::Impl の派生クラスを内部クラスとして定義し、具体的なカレンダーの動作を提供します。そのコンストラクターは、Implインスタンスへの shared pointer を生成し、それをベースクラスの impl_ に保持します。その結果、生成された Calendarインスタンスは、それを使いたいどんなオブジェクトであっても、安全にコピーできます。このインスタンスを分割しても、polymorphicな Implクラスへのポインターのおかげで、正しい動作を維持する事ができます。最後に、同じ派生Calendarインスタンスは、同じ Implインスタンスをシェアできる点を注意しておきます。これは Flyweightパターンの実装とみる事ができ、ひとつの単純なクラスの為に、合計で2.5種類のデザインパターンを使ったことになります。
Calendarクラスの実装内容の説明については十分したので、その動作の説明に移ります。前のセクションで説明した、“驚くべき事”についてです。Period(1,Weeks) はPeriod(7,Days) と同じ(==演算子が true を返す) と述べた所を覚えていますか?実は、Calendarクラスの advance( )メソッドだけは、7Days は 7 business Days(7営業日)として計算しています。従って、仮に2つの期間 p1 と p2 が同じ (すなわちp1==p2がtrueを返す) だったとしても、calendar.advance(p1)の結果は calendar.advance(p2) と違ってくる可能性があります。とんでも無い事をやってしまいました。
この問題に対する良い解決策を持っている訳ではありません。過去のバージョンとの互換性を考えれば、今のadvance( )メソッドでのDaysの使い方はそのままにしなければなりません。そうすると、(7カレンダー日だけ日を前に進めたい場合でも)calendar.advance(7, Days)として、それを7カレンダー日前に進めたと解釈する事はできません。ひとつの逃げ道は、今の状況はそのまま残して、enumerationの中にBusinessDays とCalendarDaysを追加する方法です。(今後開発されるプログラムコードで、これを使い分ければ、)意味の曖昧さが取り除かれ、次第にDaysを使われなくなるでしょう。あるいは、(advance( )メソッドも含めてQuantLib全体で)7daysは1weekとは違うという事に統一してしまう方法です。しかし私自身はあまり乗り気ではありません。
もし、過去のバージョンとの互換性をあきらめるなら(待ち遠しいQuantLib 2.0のバージョンで)、もっと別の解決策があります。ひとつは、Daysを常にカレンダー日数として使い、BusinessDaysは営業日数を示すものとして(TimeUnitの)enumerationに加える方法です。別の方法は、(私自身は、考えれば考えるほどこちらの方法がいいと思いますが)、Daysを常にカレンダー日数として使い、CalendarクラスにadvanceBusinessDays( )メソッドを追加するか、advance( )メソッドをオーバーロードしてadvance(n, BusinessDays )を加える方法です(ここでBusinessDaysは別のクラスのインスタンスとする)。しかし、これは例えば3 business daysは、もはや期間でなくなる(年数換算の計算が出来ない)ことになります。
既に申し上げた通り、明解な解決策はありません。もし読者の方が別の解決法をお持ちなら、お聞かせ下さい。
A.2.3 Day-count Convention: 日数計算方法
DayCounterクラスは、2つの日付の間の期間を計算するツールを提供しており、その期間は、“日数”あるいは“小数点付きの年数”で表されます。Actual360 やThirty360 といった派生クラスが存在しており、これら派生クラスは前のセクションで説明したCalendarクラスと同じ様なpolymorphic(多相的)な動作を実装しています。
(訳注:様々な日数計算方法については、Wiki参照)
そのインターフェースは、残念ながら少し粗い作りです。例えば、yearFraction( )メソッドは、単に2つの日付を取るのでは無く、次のようになっています。
Time yearFraction(const Date&,
const Date&,
const Date& refPeriodStart = Date(),
const Date& refPeriodEnd = Date()) const;
2つの任意引数 (refPeriodStart と refPeriodEnd) は、ある特別の日数計算方法 (ISMA Actual/Actualがそれです) の為だけに必要です。この日数計算方法では、2つの日付の他に2つのreference日付けを指定する必要があります。(訳注:ISMA Actual/Actualは、主に米国債の経過利息計算に使われているが、分母にあたる実日数は、クーポン期間の実日数で、年2回払いであれば、182日であったり、183日であったりする)
ところが、共通のインターフェースを持たせる為に、(ISMA Actual/Actual以外の)派生クラスでも、yearFranction( )メソッドの宣言に、この2つの追加の引数を加えなければなりませんでした (ほとんどのクラスで、問題なくそれを無視していますが)。このDayCounterが起こした問題は、これだけに留まりません。それらを次のセクションで見てみます。
A.2.4 Schedules クーポンスケジュール
次のListing A2に示すScheduleクラスは、クーポン日付のスケジュールを生成する為に使われます。
Listing A2:Scheduleクラスのインターフェース
class Schedule {
public:
Schedule(const Date& effectiveDate,
const Date& termination Date,
const Period& tenor,
const Calendar& calendar,
BusinessDayConvention convention,
BusinessDayConvention terminationDateConvention,
DateGeneration::Rule rule,
bool endOfMonth,
const Date& firstDate = Date(),
const Date& nextToLastDate = Date());
Schedule(const std::vector<Date>&,
const Calendar& calendar = NullCalendar(),
BusinessDayConvention convention = Unadjusted);
Size size() const;
bool empty() const;
const Date& operator[](Size i) const;
const Date& at(Size i) const;
const_iterator begin() const;
const_iterator end() const;
const Calendar& calendar() const;
const Period& tenor() const;
bool isRegular(Size i) const;
Date previousDate(const Date& refDate) const;
Date nextDate(const Date& refDate) const;
... // other inspectors and utilities
};
様々な市場慣行やISDAの決まり事を取り込む為、このクラスは相当数のパラメータを取る必要があります。それがいかに多いかは、コンストラクターの引数のリストを見れば一目瞭然です。(これらの引数の説明をしませんがお許し下さい。意味については皆さんお判りでしょう。) この数はおそらく多すぎるので、QuantLibではNamed Parameter Idiom(既にChapter IV キャッシュフロー配列の生成 の所で説明しました)を使い、より使いやすいFactoryクラスを提供しています。それを使えば、次のようなコードの記述で、Scheduleクラスのインスタンスが生成できます。
Schedule s = MakeSchedule().from(startDate).to(endDate)
.withFrequency(Semiannual)
.withCalendar(TARGET())
.withNextToLastDate(stubDate)
.backwards();
このクラスは他にも、メンバー変数のデータを読み取るインスペクター関数や、日付の配列に対するインターフェースとしてsize( )、operator[ ]、begin( )、end( )といったメソッドを提供しています。
Scheduleクラスは、もう一つ別のコンストラクターがあり、引数として事前に設定された日付の配列を取ります。但し、これについては未完成です。このコンストラクターを使って生成されたインスタンスは、インスペクター関数に対応するデータが揃っておらず、今の所、そういった場合は例外処理に飛びます。その例として、tenor( )やisRegular( )といったメソッドがあり、それについて少し説明する必要があります。
まずisRegular(i)メソッドですが、このメソッドが指し示すのはi番目のクーポン日付では無く、i番目のクーポン期間、すなわちi とi+1番目のクーポン日の間の期間を指します(訳注:その期間がregularならtrue、そうでなければfalseを返す)。そう言いましたが、では”regular”はどういう意味なのでしょうか?クーポンスケジュールが、“クーポン期間”をベースに生成された場合、大半のクーポン期間は引数で指定された期間と同じになります(それがregularの意味です)。しかし、最初と最後のクーポン期間は、最初のクーポン日あるいは最後からひとつ手前のクーポン日を明示的に指定した場合、指定されたクーポン期間より長かったり短かったりします。例えば、First Short Coupon日を特に設定したい場合などは、その日を明示的に指定します。
もし、Scheduleが、事前に設定されたクーポン日の配列でインスタンス化された場合、上記のような指定されたregularなクーポン期間情報を持たない為、isRegular(i)メソッドの問いかけに答えられません。より問題なのは、それにより、このScheduleインスタンスを使って、実際の債券クーポンのスケジュールを生成する事が出来なくなる事です。もし、このScheduleインスタンスを、固定クーポン債を生成するコンストラクターに渡した場合、例外処理に飛んでしまいます。
では、なぜ債券のコンストラクターは、クーポンスケジュールを生成するのに、この isRegular(i)の情報が必要なのでしょうか?理由は、もしその債券のクーポンの日数計算方法が ISMA actual/actual だった場合、reference期間の情報が必要になりますが、その reference期間の情報を計算する為には、クーポン期間の情報も必要になるからです。
幸いなことに、この問題を解決するのは難しくありません。ひとつは、日数計算方法をチェックし、必要な場合にだけreference期間を計算する事です。この場合でも、日数計算方法がISMA actual/actualの場合は例外処理に飛びますが、その他の日数計算方法ならすべてうまく行きます。別の方法は、(クーポン日配列を渡されて生成された)Scheduleインスタンスに、クーポン期間とregularityの情報を何とかして導出して加える事です。そうすれば、対応するメソッドはすべてうまくいきます。しかし、この方法に意味があるかどうか、確信がありません。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス