2011. 6. 16. 17:01 quantlib/Implementation
3. Term structure - the shape of things to come
변화는 오직 거듭된다. - 헤라클레이토스(Heraclitus). 역설적으로 이 격언은 25세기가 지난 이후에도 여전히 유효하다; Quantitative finance에서 모두는 아닐지라도 대부분의 quantities는 분명 시간이 지날수록 다르게 된다. 이것이 우리를 term structures의 주제로 이끈다. 이번 챕터에서 이것을 위한 기본과 사용되고 있는 몇개의 term structures에 대하여 다룰 것이다.
3.1 The TermStructure class
term structures를 위한 현재의 base 클래스는 사후의 디자인으로서 좋은 예제가 된다. 조금 생각해보면 이러한 클래스를 위한 명세를 생각해 낼 수 있을 것이다; 우리가 라이브러리를 시작했을 때 우리는 하지 못했었다. 몇 년 후 나이가 더 들고 조금 더 현명해져 우리는 존재하는 term structures를 보고 그것들의 공통 특성을 추상화냈다. 그 결과가 이 섹션에서 다루게 되는 TermStructure class이다.
3.1.1 Interface and requirements
추상화되고 나면, 다음의 리스트에 나타나는 base term-structure 클래스는 세개의 기본적인 tasks에 대한 책임이 있다. 첫번째는 자신의 reference date 즉 어떤면으로 보면 미래가 시작하는 date를 계속 따라가야 하는 것이다(이것은 모든 term structures에 반드시 적용되야 되는 것은 아니지만, 당분간 이대로 진행할 것이다.) volatility term structure인 경우, 대부분은 오늘인 경우가 많다. yield curve인 경우, 오늘일지도 모른다; 그러나 desk에서 사용되는 conventions에 따라(예를 들면, 두번째 비지니스 데이에 모든것을 정산하는 interest-rate swap desk의 거래같은 경우) reference date는 몇 settlement days에 의해 오늘보다 앞에 있을수도 있다. 우리의 term-structure 클래스는 필요하다면 반드시 이러한 계산을 수행할 수 있어야 한다. 또한 reference date가 외부에 명시되는 경우(sequence of dates, including the reference가 대응되는 discount factors로 표를 만드는 경우)가 있을 수 있다. 마지막으로 reference date의 계산은 다른 객체로 위임될 수 있다; 이것은 3.2.4에서 다룰 것이다.
모든 경우에 reference date는 referenceDate 함수에 의해 클라이언트 코드로 제공될 것이다. 이와 관련된 calendar와 settlementDays 함수는 calendar와 settlement day를 리턴한다.
두번째 task는 dates를 times으로 변환한다. 즉 reference date에서 t = 0로 시작하는 real-valued time axis상에 표시한다. 이러한 times는 discount factor를 zero-yield rates로 바꾸거나 curve와 관련된 수학적인 모델에서 사용될수 있다. timeFromRederence 함수에 의해 파생된 클래스들의 작성자는 계산을 이용할 수 있다.
세번째 task는 주어진 date와 time이 term structure에 해당하는 도메인에 속하는지 아닌지를 판단하는 것이다. TermStructure 클래스는 파생클래스에 도메인에서 latest date의 명세(maxDate 함수에서 반드시 구현되어야만 하는)를 위임하며 실제 테스트를 수행하는 overloaded checkRange 함수와 대응되는 maxTime 함수를 제공한다; 도메인은 reference date에 시작하는 것을 가정하므로 minDate 함수는 존재하지 않는다.
3.1.2 Implementation
첫번째 task는 term structure를 instantiated하면서 시작한다. 어떻게 reference date를 계산하는지에 따라 다른 생성자가 호출된다.(Section A.2에 date, time과 관련된 설명이 있다) 그러한 모든 생성자는 두 개의 boolean 데이터 멤버를 set한다. 첫번째는 moving_이라 불리며 오늘의 date가 변할때 reference date가 이동하면 true로 셋팅하고 date가 고정이면 false로 셋팅한다. 두번째 updated_는 다른 데이터 멤버 값(reference date의 최종 계산된 값이 저장되는 referenceDate_)이 현재 최신값이거나 아니면 재계산이 되어야하는지를 나타낸다. 세개의 생성자가 가능하다. 하나는 간단히 time 계산에 사용되는 day counter를 가지지만, reference-date 계산과 관련된 인자를 갖지는 않는다. 결과로 나오는 term structure는 date를 계산하기 위한 수단을 갖지 않는다; 그러므로 이 생성자를 호출하는 파생 클래스는 반드시 virtual referenceDate 함수를 overriding해서 계산을 해야만 한다. 구현은 base 클래스에서 더많은 계산을 하는 것을 억제하기 위해 updated_를 true로 moving_을 false로 설정한다.
다른 생성자는 date를 가지며 optional calendar 그리고 day counter를 갖는다. 이것이 사용되면 reference date는 주어진 date와 같으며 고정으로 간주된다. 게다가 referenceDate_를 전달된 date로 셋팅하고 moving_를 false, update_를 true로 설정한다.
마지막으로 세번째 생성자는 여러개의 settlement days와 하나의 calendar를 갖는다. 이게 사용되면, reference date는 주어진 calendar에 의한 business days에 의해 advanced된 today's date로 계산된다. 게다가 대응되는 데이터 멤버로 넘겨받은 데이터를 복사하면서, moving_를 true로 updated_를 false(이 시기에 계산이 수행되지 않으므로)로 설정한다. 그러나 이것이 전부가 아니다; 만약 오늘의 date가 변하면 term structure는 반드시 reference date를 갱신해야한다. Settings 클래스(Section A.6에서 설명됨)는 observer로써 등록된 term structure를 가지고 현재 측정한 date에 global access를 제공한다. 변화가 감지되면 update함수가 실행된다. 만약 reference date가 move하면 함수의 body는 term structure 자신의 observers에 알리기 전에 updated_를 false로 바꾼다.
calendar 함수와 같이 하찮은 것을 제외하고 첫번째 task의 구현은 referenceDate 함수와 함께 완성된다. 만약 reference date가 계산되야 하면, 이것을 리턴하기 전에 referenceDate_ 데이터 멤버에 결과를 저장하고 현재 측정된 date, advancing it as specified,를 가지고 계산한다. 이것은 새롭게 정의된 evaluation date로 term structure를 옮긴다.
두번째 task는 dates를 times으로의 변환이 전부 DayCounter instance로 위임할 수 있기 때문에 더욱 간단하다. 이러한 day counter는 보통 term structure를 생성자 인자로서 넘겨지고 dayCounter_ 데이터 멤버로 저장된다. 이러한 변환은 reference date와 넘겨진 date사이의 years를 위한 day counter를 ask하는 timeFromReference 함수에 의해 다루어진다. 함수의 body에서 day counter와 reference date는 데이터 멤버가 아니라 대응되는 함수에 의해 접근된다. 이것은 referenceDate 함수가 전부 overridden될 수 있어서 데이터 멤버를 무시할수 있기 때문에 필수적이다; 이와 같은 것은 dayCounter 함수에도 마찬가지로 적용된다.
당신은 이것이 code smell(Kent Beck이 만든 용어)이라는것에 반대할지도 모른다. term-structure instance는 함수에 의해 사용되는 실제의 것과 대응되지 않는 day counter 또는 reference date(or likely both)를 저장할지도 모른다. 이것이 헷갈리게 만든다; 사실 이전버젼의 클래스는 완전히 virtual인 dayCounter 함수를 선언했었으며 데이터 멤버를 포함하지 않았었다. 그러나 reference date의 경우 계산된 값을 저장할 데이터 멤버를 필요로 하기 때문에 이것은 필요악이다. broken-window effect로 인해 day count, calendar 그리고 settlement days는 뒤를 따랐다(모두 같은 데이터 멤버들을 정의해야만 했던 많은 파생된 term structures를 개발한 기간 이후에)
마지막 질문이 남았다: what day counter should be used? 다행히도, 이것은 크게 상관이 없다. 만약 누군가 오직 dates(term structure의 생성자에 date를 input으로 전달하고 값을 찾기위해 인자로서 dates를 사용한다면)와 작업한다면, day counter가 충분히 잘 동작하는 한 특정 day counter를 선택하는 노력은 아무런 대가를 얻지 못할 것이다: 예를들면, 만약 이것이 동질의 것이고(만약 두짝의 dates가 same number of dates를 갖지만 다르다면 즉 두 개의 dates d1, d2 사이에 time T(d1, d2)이 d3, d4 사이의 time T(d3, d4)가 같다), additive한것(T(d1, d2) + T(d2, d3) = T(d1, d3)). 두 개의 day counters는 actual/360과 actual/365-fixed이다. 비슷하게 만약 어떤것이 오직 times와 관련해 동작한다면 day counter는 전혀 사용되지 않을 것이다.
세번째 그리고 마지막 작업이다. 유효한 date range를 정의하는 것은 파생 클래스에 위임한다. 파생클래스는 maxDate 함수(여기서 purely virtual로 선언된)를 정의해야만 한다. 대응되는 time range는 timeFromReference 함수에 의해 latest valid date로 변환하는 maxTime 함수를 사용하여 계산한다. 마지막으로 두개의 checkRange 함수는 실제 range checking (time으로 변환한 후에 다른 곳으로 request를 forward하는 것으로 date를 취하는 것)을 구현하고 만약 전달된 인자가 valid range에 없으면 예외를 발생시킨다. 이러한 확인은 term-structure의 도메인 밖에서 추정(외삽법)하기 위한 요청에 의해 overridden된다; 이것은 optional boolean 인자를 checkRange 함수로 전달하거나 TermStructure를 상속하는 Extrapolator 클래스(Section A.5)에서 제공하는 facilities를 사용하여 이루어 진다. 외삽법은 오직 maximum date에 의해 수행된다; reference date가 거부되기 전의 dates를 위한 요청.
3.2 Yield term structures
YieldTermStructure 클래스는 TermStructure보다 먼저 date가 온다(predates) - 사실 이것은 라이브러리에서 유일한 term structure일때 TermStructure back in the day라고 불리기도 한다. 하지만 우리는 아직 그런걸 본적은 없다(we still hadn't seen the world). 이것의 인터페이스는 curve 도메인에서 특정 date에 interest rates와 discount factors를 예측할 수 있는 수단을 제공한다; 또한 이것은 concrete yield curve를 작성하는 일을 더 쉽게 도와주는 장치를 구현한다.
3.2.1 Interface and implementation
YieldTermStructure 클래스의 인터페이스는 listing 3.3에 나타나 있다. 생성자는 인자를 TermStructure 클래스의 대응되는 생성자로 보낸다 - nothing to write home about. 다른 함수는 다른 방식으로 yield structure상의 정보를 리턴한다; zero rates, forward rates 그리고 discount factors를 리턴할 수 있다.(rates는 InterestRate 클래스의 인스턴스로 리턴된다-section A.4); 이것들은 overloaded되서 dates 혹은 times의 함수로서 정보를 리턴할 수 있다. 당연히 zero rates, forward rates 그리고 discount factors 사이에 관계가 있다; 이것들의 어떤 하나의 지식도 다른 것을 연역(deduce)하는데 충분하다(여기서는 다루지 않는다). 구현에 나타나 있고 listing 3.4에 있다; protected discountImpl abstract 함수에 의해 직/간접적으로 모든 public 함수들을 구현하는데 Template Method pattern을 사용한다. 파생된 클래스들은 오직 위의 quantities를 리턴하기 위해서만latter를 구현할 필요가 있다.
3.2.2 Discount, forward-rate, and zero-rate curves
만약 파생 클래스의 저자가 discountImpl을 구현하지 않기를 원한다면? 결국 누군가 zero rates에 의해 yield curve를 기술하기를 원할지도 모른다. Quantlib는 이러한 경우에 사용되는 몇개의 클래스를 제공한다. 두개의 클래스인 ZeroYieldStructure와 ForwardRateStructure이며 listing 3.5에 나타난다.
이것들은 YieldTermStructure의 zero-discount 기반의 인터페이스를 zero-yield와 instantaneous-forward rates의 인터페이스로 변환하기 위해 Adapter pattern을 사용한다.
ZeroYieldStructure의 구현은 간단하다. 여기에는 없는 몇 개의 생성자는 자신의 인자를 parent YieldTermStructure 클래스의 대응되는 생성자로 전달한다. Adapter pattern은 protected section에서 구현된다: abstract zeroYieldImpl 함수가 선언되고 discountImpl 함수가 구현되는데 사용된다. 그러므로 파생 클래스의 저자는 fully functional yield curve를 얻기 위해 zeroYieldImpl의 구현을 제공하기만 하면 된다(당연히 다른 필요한 함수(maxDate와 같은)는 반드시 구현되어야만 한다). discount factor를 구하기 위해 사용된 formula 때문에 이러한 함수들은 continuously-compounded annualized rates로서 zero-yields를 리턴해야만 한다.
비슷하게, ForwardRateStructure 클래스는 파생 클래스에서 forwardImpl 함수를 구현함으로써 instantaneous forward rates(on an annual basis)에 의해 curve를 기술할수 있는 수단을 제공한다. 그러나 이것은 added twist를 갖는다. 주어진 time T에서 discount를 얻기 위해 우리는 0와 T사이의 instantaneous forwards의 평균을 구해야 하고 그러므로 원하는 zero-yield rate를 구할수 있다. 이 클래스는 forwards의 shape상에서 어떠한 가정을 만들수 없다; 그러므로 할수 있는 모든 것은 numerical integration이다 - an expensive calculation. 최적화를 위한 hook를 제공하기 위해 평균은 더 빠른 계산이 가능할때 overridden을 할 수 있는 virtual zeroYieldImpl 함수에서 수행된다. 당신은 만약 zero yields를 위한 expression이 가능하다면 zeroYieldStructure를 상속하는 것을 반대할지도 모른다; 그러나 이것은 만약 모델에 실제 초점을 맞춘다면 forwards에 의해 curve를 표현하는 것이 개념적으로 더 쉽다.
두개의 adapter classes와 base YieldTermStructure 클래스로 interpolated discount, zero-yield 그리고 forward curves를 구현할 수 있다. Listing 3.6에서 InterpolatedZeroCurve 클래스 템플릿을 outline한다; 다른 두개(InterpolatedForwardCurve와 InterpolatedDiscountCurve)는 같은 방식으로 구현된다.
template argument Interpolator는 두배의 task를 갖는다. 반면 이것은 traits(특성) 클래스로써 행동한다[18]. 이것은 이것의 속성(points-linear interpolation을 위해서는 최소 두개)과 interpolation 그리고 선택한 interpolation이 global인지(data point를 moving하는 것이 point를 포함하지 않는 interval에서의 interpolation을 변화시키는지 아닌지; cubic splines와 같은 것이 있음)를 명시한다. 반면 poor man's factory로 double된다; data points의 set이 주어졌을 때, 대응되는 Interpolation instance를 build하고 return하는 것이 가능하다 - (Interpolation 클래스는 section A.5에 나온다. A.5는 몇개의 이용가능한 interpolations와 대응되는 traits에 대해 다룬다)
public 생성자는 curve를 build하는데 필요한 data를 취한다: interpolate하기 위한 dates의 set, 대응되는 zero yields, day counter 그리고 optional interpolator instance. 대부분의 interpolations에는마지막 parameter는 필요하지 않다; 이것은 필요할 때 보내질수 있다. 구현은 parent ZeroYieldStructure 클래스로 passed dates의 첫번째 것(curve를 위한 reference date로 가정)과 day counter를 보낸다; 다른 인자들은 대응되는 데이터 멤버에 저장된다. 몇개의 일관성 확인을 수행한 후, dates를 times(전달된 reference date와 day counter를 이용하여) 변환하고, Interpolation instance를 생성할 것을 interpolator로 문의하고 그 결과를 저장한다.
이때 curve는 사용준비가 된다. 필요한 다른 함수는 one-liners로 구현된다; maxDate 함수는 전달된 dates의 제일 마지막을 return하고 zeroYieldImpl은 zero yield의 interpolated value를 리턴한다. TermStructure machinery는 이미 range-checking을 했기 때문에, Interpolation instance를 호출하는 것은 true argument를 포함한다. 이것은 만약 전달된 time이 주어진 range를 벗어났다면 그 값을 extrapolated하도록 만든다.
마지막으로 InterpolatedZeroCurve 클래스는 몇개의 protected 생성자를 정의한다. 이것들은 parent ZeroYieldStructure 클래스와 optional Interpolator instance의 생성자와 같은 인자를 취한다. 생성자들은 parent-클래스 생성자에 대응되는 인자를 보내지만, interpolation을 생성하지는 않는다 - 이것들은 zero-yield 데이터를 가지고 있지 않기 때문에 할수가 없다. 이것들은 InterpolatedZeroCurve를 상속하는 것이 가능하다; 파생된 클래스들은 data를 제공하며 그들이 취한 인자가 무엇이든(다음 섹션에서 다룸) 그것을 기반으로 interpolation을 생성한다. 같은 이유로 대부분 데이터 멤버는 mutable로 선언된다; 이것은 파생클래스가 interpolation을 lazily하게 update할수 있도록 만든다.
3.2.3 Example: bootstrapping an interpolated yield curve
이번 섹션에서는 all-purpose yield-curve template를 build할 것이다. 지난 서브 섹션에서 기술한 클래스 build하면서 discount facotrs, zero-yields 혹은 instantaneous forward rates상에서 다양한 방법의 interpolate하기 위한 방법을 설명하였다. curve의 nodes는 quoted market rates로부터 bootstrapped될 것이다.
말할 필요없이 이번 예제는 꽤 복잡할 것이다. 설명할 class template(PieceWiseYieldCurve)은 몇개의 helper 클래스들과 template tricks를 사용할 것이다. 필요한 모든것을 설명하도록 노력할 것이다.
class template는 listing 3.7에 있다. 클래스는 세개의 template arguments를 취한다. 처음 두개는 underlying data와 interpolation 함수를 선택한다; 우리의 목표는 template를 다음과 같같거나 비슷한 것으로 instantiate하는 것이다: PiecewiseYieldCurve<Discount, LogLinear>
첫번째 parameter는 bootstrap traits이다; 두번째는 이미 기술한 interpolator이다. 세번째 parameter는 bootstrapping algorithm을 구현하는 클래스를 명시한다. parameter는 default value(InterativeBootstrap class)를 갖기 때문에 대부분 이것을 몰라도 된다; 다른 bootstrapping algorithm으로 바꾸어도 된다. 이것의 문법이 익숙치 않은 사람들을 위해 template template parameter를 기술할 것이다[22]. template 두번이 맞다; parameter는 typename보다는 uninstantiated class template(이 경우 하나의 template argument를 취한다)이어야만 한다.(uninstantiated template는 std::vector와 같은 것이어야 하고 std::vector<double>과 같은 instantiated one과 반대의 개념이다. 두번째는 type의 이름을 주었지만 두번째는 아니다)
class interface를 선언하기 전에, curve의 parent class를 결정하기 위한 template programming가 필요하다. 반면 우리는 LazyObject class로부터 term structure를 상속한다; chapter 2에서 기술한 것과 같이 이것은 curve가 re-bootstrap를 스스로 할수 있도록 만든다. 반면 section 3.2.2에서 기술한 interpolated curves중 하나로부터 그것을 상속하기를 원한다; underlying data(discount, zero yields, forward rates)의 선택에 따라 class templates를 선택해야만 하고 선택한 interpolator class를 가지고 instantiate해야한다. 이것은 class template를 bootstrap traits에 저장하는것으로 이루어진다. 불행히, C++은 template typedefs를 허용하지 않는다(Template aliases는 아마 C++ standard 다음 revision에 소개될것이다). 그러므로 traits는 template parameter로서 interpolator를 취하고 typedef로서 instantiated parent class를 정의하는 inner class template curve를 정의할 것이다; 이것은 Discount traits의 정의에서 볼수 있으며 listing 3.8에 부분적으로 나타나 있다.
기술된 machinery는 다음 표현에 의해 선택된 class를 알리며 우리는 이것을 parent classes의 list에 추가할수 있다(이 표현을 컴파일러가 읽을때 컴파일러는 Traits가 무엇인지 모르며 Traits::curve가 class template 라는 것을 결정 할 수 있는 수단을 가지고 있지 않다. keyword를 추가하는 것이 컴파일러에 표현의 나머지 부분을 제대로 처리하는데 필요로 하는 정보를 준다): Traits::template curve<Interpolator>::type
instantiation은 bootstrap traits로서 Discount와 interpolator로서 LogLinear가 예제로 있으며 Discount::curve<LogLinear>::type는 InterpolateDiscountCurve<LogLinear>와 대응된다.
curve를 구현하기 전에 long template 표현을 피하기 위해 몇개의 typedefs를 정의한다: base_curve, this_curve, helper. 첫번째는 parent class를 의미하고, 두번째는 우리가 정의한 방금 그 클래스를 의미하며; 세번째는 helper class의 type을 bootstrap traits로부터 추출한다. 이것은 instrument의 quoted value와 bootstrapped되는 term structure의 instrument를 평가하는 수단으로 제공된다. bootstrap은 두개의 값이 일치(coincide)할 때까지 curve를 수정할 것이다. 마지막으로 두개의 typedefs는 두개의 대응되는 template arguments를 저장하므로서 나중에 검색할 수 있게된다.
마지막으로 실제 인터페이스이다. listing에서 보여지는 유일한 생성자들은 parent interpolated curve를 instantiating하기 위해 필요한 인자들과 a vector of helpers 그리고 bootstrap를 위한 target accuracy를 취한다. 다음으로 많은 inspectors(such as times or data)가 정의되고 parent class의 versions을 overriding한다; 이렇게 하는 이유는 마지막에 설명한다. public interface는 update 함수에 의해 완성된다.
performCalculations를 포함한 protected 함수들은 LazyObject interface를 구현하는데 필요하다; parent-class 버젼을 override하는 discountImpl(public inspectors와 같은). Bootstrap class template는 curve의 type과 함께 instantiated된다. 이것은 curve의 internals로 접근이 필요할 것이기 때문에 결과 클래스는 PiecewiseYieldCurve class의 friend로써 선언된다; BootstrapError class도 마찬가지다. 마지막으로 bootstrap class의 instance와 required curve accuracy 그리고 helper를 저장한다.
구현을 보자. 생성자는 놀랄일이 없다: base class에 필요한 인자를 넘기고 다른것들은 클래스에 저장한다. 마지막으로 저장된 Bootstrap instance에 curve를 전달하고 some preliminary work를 수행하도록 만든다. update 함수는 정확함(disambiguation)을 위해 필요하다; 함수의 구현을 정의하는 두개의 클래스(LazyObject와 TermStructure)로 부터 상속한다. 컴파일러는 당연하게 second-guess를 거부하므로서 우리는 명시적으로 두개의 parent 구현을 호출해야만 한다.
performCalculations는 작업을 Bootstrap instance에 위임한다. 마지막으로 discountImpl 함수를 보면 왜 이러한 함수가 overridden되어야만 하는지를 알수 있다; parent-class 구현을 호출하기 전에, data가 calculate 함수를 호출하는 것에 의해 bootstrapped되어야 한다는 것을 확실히 해야만 한다. 이것은 또한 같은 패턴을 따르면서 다른 overridden inspectors를 hold한다. 이시점에 BootstrapHelper class template를 기술해야 한다; 이것은 listing 3.9에 있다. curve를 위해 우리는 이것(이전의 Discount traits에서 볼수 있는)을 BootstrapHelper<YieldTermStructure>로서 instantiate한다; 편리를 위해 라이브러리는 더욱 verbose type name에서 사용할 수 있는 RateHelper로 불리는 클래스에 alias를 제공한다.
이 클래스의 각각의 instance - 혹은 파생된 클래스의; 클래스 자체가 abstract이다 - 는 curve상에 하나의 노드를 bootstrapping하는 것을 도울 것이다. node를 위한 input datum은 instrument의 quoted value이다; 값이 시간에 따라 변하기 때문에 Quote instance로의 Handle로서 제공된다. yield curve의 경우 이러한 instruments는 deposits 혹은 swaps이고 market rates로서 quoted된다. 각각의 instrument에는 파생된 클래스가 제공되야만 한다.
모든 helper에 공통인 functionality는 base class에서 구현된다. BootstrapHelper는 Observer와 Observable을 상속한다; 두개의 역할은 market value를 등록할수 있게 하며 새로운 bootstrap의 수행의 필요성을 signaling하면서 변화를 curve에 알릴수 있다. 이것의 생성자는 input market value를 제공하는 Handle<Quote>를 취하고 그것을 데이터 멤버에 저장하며 observer로서 자신을 등록한다. 세개의 함수는 underlying instrument value를 다룬다. quote 함수는 quoted value를 포함하는 handle를 반환한다; abstract impliedValue 함수는 bootstrapped되는 curve상에서 계산한 값을 리턴한다; convenience 함수인 quoteError는 두 값의 signed 차이를 리턴한다.
두개의 함수가 bootstrap algorithm을 set up하기 위해 더 사용된다. setTermStructure 함수는 만들어지는 curve와 함께 helper를 연결한다. latestDate 함수는 시장 데이터의 implied value를 계산하기 위해 사용되는 curve data를 위한 latest date를 리턴한다(latest required date는 instrument의 maturity와 일치할 필요성은 없다. 예를 들어 만약 instrument가 constant-maturity swap인 경우, curve는 마지막 coupon에 의해 지불되는 rate를 예측하기 위한 swap maturity보다 몇년을 확장해야 할 것이다.); 이러한 date는 bootstrapped되는 노드의 coordinate로서 사용될 것이다. 마지막 함수(update)는 quote로부터 helper의 observers로 notification을 전달한다.
라이브러리는 RateHelper를 상속하는 몇개의 concrete helper classes를 제공한다. 간략함을 위해 실제 코드를 보여주는 대신 hand-waving(간단한 말?)을 사용할 것이다. 각각의 helper 클래스는 특정 instrument를 위해 impliedValue를 구현하고 적절한 latest date를 리턴하기 위한 code를 포함한다. 예를들면, DepositRateHelper 클래스는 이것의 시작과 maturity date 사이의 forward rate를 위한 curve를 asking함으로서 quoted deposit rate를 예측한다; 반면 SwapRateHelper 클래스는 Swap 객체를 instrantiating하고 bootstarapped되는 curve상에서 pricing하고 fair rate를 implying함으로서 swap rate를 예측한다.
bootstrap code를 보자. Listing 3.10에서 라이브러리에서 제공되고 default로 사용되는IterativeBootstrap class의 인터페이스를 보자. 편의를 위해 curve로부터 traits와 interpolator types를 추출하기 위해 typedefs가 정의된다. 생성자와 setup 함수는 특정 흥미 대상이 아니다. 첫번째는 term-structure pointer를 null로 초기화한다; 두번째는 전달된 curve pointer를 저장하고 curve를 bootstrap하기 위해 충분한 helper를 가지고 있는지 확인해야하며 각각의 helper의 observer로서 curve를 등록한다. bootstrap algorithm은 calculate 함수에 의해 구현된다. 여기서는 details와 corner cases는 대충 넘어간다; 관심있다면 라이브러리 풀 코드를 보면 된다.
첫번째로 모든 helper는 현재의 term structure로 셋팅한다. 이것은 a set of helpers가 각각 다른 curves를 사용하도록 할 때마다 수행된다(멀티 쓰레드 환경이라면 안전하게 하기 위해 당연히 같은 helpers은 다른 curves에 넘겨질수 없다. QuantLib의 대부분은 thread-safe가 아니다.) 그리고 vectors가 초기화된다. 초기 노드를 위한 date와 value는 전달된 traits에 의해 제공된다; yield term structure를 위해 초기 date는 curve의 reference date에 대응한다. 초기값은 underlying data의 선택에따라 달라진다; discount factors를 위해 1, zero or forward rates를 위해 더미값(bootstrap procedure가 진행되는 동안 overwritten될 것이다)이 설정된다. 다른 노드들을 위한 dates는 대응되는 helpers의 latest needed dates이다; times는 이용가능한 curve facilities를 사용함으로써 구할수 있다.
이시점에서 각 노드(appendix A에 자세히 나온다)에서 사용하고 실제 bootstrap을 시작할 one-dimensional solver를 instantiate할 것이다. 계산은 두개의 nested loops로 작성된다; 각 노드에서 수행되는 inner one(bootstrap proper)과 프로세스를 반복하는 outer one. Iterative bootstrap은 non-local interpolation(cubic splines와 같은)이 사용될때 필요하다. 이 경우 노드의 값을 설정하는 것은 전의 노드를 무효화하면서 전체의 curve를 수정한다; 그러므로 convergence가 일어날 때까지 노드를 계속 주시해야만한다.
전에 말했듯이 inner loop는 각 노드를 돌아다닌다-물론 earliest date부터 시작한다. 첫번째 iteration(when iteration == 0)동안 interpolation은 한번에 한 point씩 확장한다; later iterations는 full data range를 사용한다 그래서 이전 결과는 starting point와 refined로서 사용된다. 각 노드가 추가된 후에, one-dimensional root-finding 알고리즘은 대응되는 시장 quote를 reproduce(재생산)하기 위해 사용된다. 첫번째 iteration을 위해, guess for the solution은 bootstrap traits 혹은 지금까지 built된 curve를 extrapolating하는 것에 의해 제공될 수 있다; 앞으로의 iteration을 위해, 이전의 결과는 guess로 사용된다. 최대/최소값은 이미 bootstrap된 노드를 기반으로 한 traits에 의해 제공된다. 예를 들면, bootstrapping zero 혹은 forward rates를 위한 traits는 최소값을 0으로 설정함으로써 음수값을 방지할 수 있다; discount factors를 위한 traits는 discounts가 증가하지 않도록 함으로써 같은 것을 할수 있다, 즉 이전 노드에서 discount를 최대로 셋팅하게 함으로서.
root-finding 알고리즘에서 딱 하나 빠진것은 zero는 반드시 찾아져야만 하는 기능이다. 이것은 BootstrapError class template(shown in listing 3.11)에 나온다. 이것은 helper's quoteError 계산을 function-object interface로 채택한다. 생성자는 만들어지고 있는 curve, 현재노드를 위한 helper 그리고 node index를 취하고 저장한다. operator()는 functions로써 사용될수 있는 class의 인스턴스를 만든다; node value를 위한 guess를 취하고 curve data를 수정하며 quote error를 리턴한다.
이 시점에서 모든것이 set된다. inner bootstrap loop는 BootstrapError 인스턴스를 생성하고 이것을 error가 zero인 것을 위한 노드 value를 리턴하는 solver로 보낸다. 즉 implied quote가 market quote와 같은 것(within accuracy)을 위해. curve 데이터는 리턴된 값을 포함하기 위해 업데이트 되고, 루프는 다음 노드로 진행한다.
남은 machinery(interpolated curves, piecewise bootstrap 등)은 설명한 다른 curves와 비슷하다. zero-coupon과 year-on-year rates를 위한 separate sub-hirarchies때문에, 여러 다른 점이 있다. 반면 discounts를 위한 ZeroStructure라던지 default probabilities를 위한 HazardRateStructure와 같은 single underlying quantity를 기반으로하는 whole interface를 구현하는 adapter classes는 없다. 반면, piecewise curves는 underlying quantity(예를 들면, in PiecewiseYieldCurve<Discount, LogLinear>)를 선택하기 위해 traits를 사용하지 않는다. 선택하기 위해서 적절한 class를 선택해야만 할것이고 PiecewiseZeroInflation<Linear>와 같은 것을 작성해야 할 것이다.
3.3.3 Volatility term structures
3.1 The TermStructure class
term structures를 위한 현재의 base 클래스는 사후의 디자인으로서 좋은 예제가 된다. 조금 생각해보면 이러한 클래스를 위한 명세를 생각해 낼 수 있을 것이다; 우리가 라이브러리를 시작했을 때 우리는 하지 못했었다. 몇 년 후 나이가 더 들고 조금 더 현명해져 우리는 존재하는 term structures를 보고 그것들의 공통 특성을 추상화냈다. 그 결과가 이 섹션에서 다루게 되는 TermStructure class이다.
3.1.1 Interface and requirements
추상화되고 나면, 다음의 리스트에 나타나는 base term-structure 클래스는 세개의 기본적인 tasks에 대한 책임이 있다. 첫번째는 자신의 reference date 즉 어떤면으로 보면 미래가 시작하는 date를 계속 따라가야 하는 것이다(이것은 모든 term structures에 반드시 적용되야 되는 것은 아니지만, 당분간 이대로 진행할 것이다.) volatility term structure인 경우, 대부분은 오늘인 경우가 많다. yield curve인 경우, 오늘일지도 모른다; 그러나 desk에서 사용되는 conventions에 따라(예를 들면, 두번째 비지니스 데이에 모든것을 정산하는 interest-rate swap desk의 거래같은 경우) reference date는 몇 settlement days에 의해 오늘보다 앞에 있을수도 있다. 우리의 term-structure 클래스는 필요하다면 반드시 이러한 계산을 수행할 수 있어야 한다. 또한 reference date가 외부에 명시되는 경우(sequence of dates, including the reference가 대응되는 discount factors로 표를 만드는 경우)가 있을 수 있다. 마지막으로 reference date의 계산은 다른 객체로 위임될 수 있다; 이것은 3.2.4에서 다룰 것이다.
모든 경우에 reference date는 referenceDate 함수에 의해 클라이언트 코드로 제공될 것이다. 이와 관련된 calendar와 settlementDays 함수는 calendar와 settlement day를 리턴한다.
두번째 task는 dates를 times으로 변환한다. 즉 reference date에서 t = 0로 시작하는 real-valued time axis상에 표시한다. 이러한 times는 discount factor를 zero-yield rates로 바꾸거나 curve와 관련된 수학적인 모델에서 사용될수 있다. timeFromRederence 함수에 의해 파생된 클래스들의 작성자는 계산을 이용할 수 있다.
세번째 task는 주어진 date와 time이 term structure에 해당하는 도메인에 속하는지 아닌지를 판단하는 것이다. TermStructure 클래스는 파생클래스에 도메인에서 latest date의 명세(maxDate 함수에서 반드시 구현되어야만 하는)를 위임하며 실제 테스트를 수행하는 overloaded checkRange 함수와 대응되는 maxTime 함수를 제공한다; 도메인은 reference date에 시작하는 것을 가정하므로 minDate 함수는 존재하지 않는다.
3.1.2 Implementation
첫번째 task는 term structure를 instantiated하면서 시작한다. 어떻게 reference date를 계산하는지에 따라 다른 생성자가 호출된다.(Section A.2에 date, time과 관련된 설명이 있다) 그러한 모든 생성자는 두 개의 boolean 데이터 멤버를 set한다. 첫번째는 moving_이라 불리며 오늘의 date가 변할때 reference date가 이동하면 true로 셋팅하고 date가 고정이면 false로 셋팅한다. 두번째 updated_는 다른 데이터 멤버 값(reference date의 최종 계산된 값이 저장되는 referenceDate_)이 현재 최신값이거나 아니면 재계산이 되어야하는지를 나타낸다. 세개의 생성자가 가능하다. 하나는 간단히 time 계산에 사용되는 day counter를 가지지만, reference-date 계산과 관련된 인자를 갖지는 않는다. 결과로 나오는 term structure는 date를 계산하기 위한 수단을 갖지 않는다; 그러므로 이 생성자를 호출하는 파생 클래스는 반드시 virtual referenceDate 함수를 overriding해서 계산을 해야만 한다. 구현은 base 클래스에서 더많은 계산을 하는 것을 억제하기 위해 updated_를 true로 moving_을 false로 설정한다.
다른 생성자는 date를 가지며 optional calendar 그리고 day counter를 갖는다. 이것이 사용되면 reference date는 주어진 date와 같으며 고정으로 간주된다. 게다가 referenceDate_를 전달된 date로 셋팅하고 moving_를 false, update_를 true로 설정한다.
마지막으로 세번째 생성자는 여러개의 settlement days와 하나의 calendar를 갖는다. 이게 사용되면, reference date는 주어진 calendar에 의한 business days에 의해 advanced된 today's date로 계산된다. 게다가 대응되는 데이터 멤버로 넘겨받은 데이터를 복사하면서, moving_를 true로 updated_를 false(이 시기에 계산이 수행되지 않으므로)로 설정한다. 그러나 이것이 전부가 아니다; 만약 오늘의 date가 변하면 term structure는 반드시 reference date를 갱신해야한다. Settings 클래스(Section A.6에서 설명됨)는 observer로써 등록된 term structure를 가지고 현재 측정한 date에 global access를 제공한다. 변화가 감지되면 update함수가 실행된다. 만약 reference date가 move하면 함수의 body는 term structure 자신의 observers에 알리기 전에 updated_를 false로 바꾼다.
calendar 함수와 같이 하찮은 것을 제외하고 첫번째 task의 구현은 referenceDate 함수와 함께 완성된다. 만약 reference date가 계산되야 하면, 이것을 리턴하기 전에 referenceDate_ 데이터 멤버에 결과를 저장하고 현재 측정된 date, advancing it as specified,를 가지고 계산한다. 이것은 새롭게 정의된 evaluation date로 term structure를 옮긴다.
두번째 task는 dates를 times으로의 변환이 전부 DayCounter instance로 위임할 수 있기 때문에 더욱 간단하다. 이러한 day counter는 보통 term structure를 생성자 인자로서 넘겨지고 dayCounter_ 데이터 멤버로 저장된다. 이러한 변환은 reference date와 넘겨진 date사이의 years를 위한 day counter를 ask하는 timeFromReference 함수에 의해 다루어진다. 함수의 body에서 day counter와 reference date는 데이터 멤버가 아니라 대응되는 함수에 의해 접근된다. 이것은 referenceDate 함수가 전부 overridden될 수 있어서 데이터 멤버를 무시할수 있기 때문에 필수적이다; 이와 같은 것은 dayCounter 함수에도 마찬가지로 적용된다.
당신은 이것이 code smell(Kent Beck이 만든 용어)이라는것에 반대할지도 모른다. term-structure instance는 함수에 의해 사용되는 실제의 것과 대응되지 않는 day counter 또는 reference date(or likely both)를 저장할지도 모른다. 이것이 헷갈리게 만든다; 사실 이전버젼의 클래스는 완전히 virtual인 dayCounter 함수를 선언했었으며 데이터 멤버를 포함하지 않았었다. 그러나 reference date의 경우 계산된 값을 저장할 데이터 멤버를 필요로 하기 때문에 이것은 필요악이다. broken-window effect로 인해 day count, calendar 그리고 settlement days는 뒤를 따랐다(모두 같은 데이터 멤버들을 정의해야만 했던 많은 파생된 term structures를 개발한 기간 이후에)
마지막 질문이 남았다: what day counter should be used? 다행히도, 이것은 크게 상관이 없다. 만약 누군가 오직 dates(term structure의 생성자에 date를 input으로 전달하고 값을 찾기위해 인자로서 dates를 사용한다면)와 작업한다면, day counter가 충분히 잘 동작하는 한 특정 day counter를 선택하는 노력은 아무런 대가를 얻지 못할 것이다: 예를들면, 만약 이것이 동질의 것이고(만약 두짝의 dates가 same number of dates를 갖지만 다르다면 즉 두 개의 dates d1, d2 사이에 time T(d1, d2)이 d3, d4 사이의 time T(d3, d4)가 같다), additive한것(T(d1, d2) + T(d2, d3) = T(d1, d3)). 두 개의 day counters는 actual/360과 actual/365-fixed이다. 비슷하게 만약 어떤것이 오직 times와 관련해 동작한다면 day counter는 전혀 사용되지 않을 것이다.
세번째 그리고 마지막 작업이다. 유효한 date range를 정의하는 것은 파생 클래스에 위임한다. 파생클래스는 maxDate 함수(여기서 purely virtual로 선언된)를 정의해야만 한다. 대응되는 time range는 timeFromReference 함수에 의해 latest valid date로 변환하는 maxTime 함수를 사용하여 계산한다. 마지막으로 두개의 checkRange 함수는 실제 range checking (time으로 변환한 후에 다른 곳으로 request를 forward하는 것으로 date를 취하는 것)을 구현하고 만약 전달된 인자가 valid range에 없으면 예외를 발생시킨다. 이러한 확인은 term-structure의 도메인 밖에서 추정(외삽법)하기 위한 요청에 의해 overridden된다; 이것은 optional boolean 인자를 checkRange 함수로 전달하거나 TermStructure를 상속하는 Extrapolator 클래스(Section A.5)에서 제공하는 facilities를 사용하여 이루어 진다. 외삽법은 오직 maximum date에 의해 수행된다; reference date가 거부되기 전의 dates를 위한 요청.
3.2 Yield term structures
YieldTermStructure 클래스는 TermStructure보다 먼저 date가 온다(predates) - 사실 이것은 라이브러리에서 유일한 term structure일때 TermStructure back in the day라고 불리기도 한다. 하지만 우리는 아직 그런걸 본적은 없다(we still hadn't seen the world). 이것의 인터페이스는 curve 도메인에서 특정 date에 interest rates와 discount factors를 예측할 수 있는 수단을 제공한다; 또한 이것은 concrete yield curve를 작성하는 일을 더 쉽게 도와주는 장치를 구현한다.
3.2.1 Interface and implementation
YieldTermStructure 클래스의 인터페이스는 listing 3.3에 나타나 있다. 생성자는 인자를 TermStructure 클래스의 대응되는 생성자로 보낸다 - nothing to write home about. 다른 함수는 다른 방식으로 yield structure상의 정보를 리턴한다; zero rates, forward rates 그리고 discount factors를 리턴할 수 있다.(rates는 InterestRate 클래스의 인스턴스로 리턴된다-section A.4); 이것들은 overloaded되서 dates 혹은 times의 함수로서 정보를 리턴할 수 있다. 당연히 zero rates, forward rates 그리고 discount factors 사이에 관계가 있다; 이것들의 어떤 하나의 지식도 다른 것을 연역(deduce)하는데 충분하다(여기서는 다루지 않는다). 구현에 나타나 있고 listing 3.4에 있다; protected discountImpl abstract 함수에 의해 직/간접적으로 모든 public 함수들을 구현하는데 Template Method pattern을 사용한다. 파생된 클래스들은 오직 위의 quantities를 리턴하기 위해서만latter를 구현할 필요가 있다.
3.2.2 Discount, forward-rate, and zero-rate curves
만약 파생 클래스의 저자가 discountImpl을 구현하지 않기를 원한다면? 결국 누군가 zero rates에 의해 yield curve를 기술하기를 원할지도 모른다. Quantlib는 이러한 경우에 사용되는 몇개의 클래스를 제공한다. 두개의 클래스인 ZeroYieldStructure와 ForwardRateStructure이며 listing 3.5에 나타난다.
이것들은 YieldTermStructure의 zero-discount 기반의 인터페이스를 zero-yield와 instantaneous-forward rates의 인터페이스로 변환하기 위해 Adapter pattern을 사용한다.
ZeroYieldStructure의 구현은 간단하다. 여기에는 없는 몇 개의 생성자는 자신의 인자를 parent YieldTermStructure 클래스의 대응되는 생성자로 전달한다. Adapter pattern은 protected section에서 구현된다: abstract zeroYieldImpl 함수가 선언되고 discountImpl 함수가 구현되는데 사용된다. 그러므로 파생 클래스의 저자는 fully functional yield curve를 얻기 위해 zeroYieldImpl의 구현을 제공하기만 하면 된다(당연히 다른 필요한 함수(maxDate와 같은)는 반드시 구현되어야만 한다). discount factor를 구하기 위해 사용된 formula 때문에 이러한 함수들은 continuously-compounded annualized rates로서 zero-yields를 리턴해야만 한다.
비슷하게, ForwardRateStructure 클래스는 파생 클래스에서 forwardImpl 함수를 구현함으로써 instantaneous forward rates(on an annual basis)에 의해 curve를 기술할수 있는 수단을 제공한다. 그러나 이것은 added twist를 갖는다. 주어진 time T에서 discount를 얻기 위해 우리는 0와 T사이의 instantaneous forwards의 평균을 구해야 하고 그러므로 원하는 zero-yield rate를 구할수 있다. 이 클래스는 forwards의 shape상에서 어떠한 가정을 만들수 없다; 그러므로 할수 있는 모든 것은 numerical integration이다 - an expensive calculation. 최적화를 위한 hook를 제공하기 위해 평균은 더 빠른 계산이 가능할때 overridden을 할 수 있는 virtual zeroYieldImpl 함수에서 수행된다. 당신은 만약 zero yields를 위한 expression이 가능하다면 zeroYieldStructure를 상속하는 것을 반대할지도 모른다; 그러나 이것은 만약 모델에 실제 초점을 맞춘다면 forwards에 의해 curve를 표현하는 것이 개념적으로 더 쉽다.
두개의 adapter classes와 base YieldTermStructure 클래스로 interpolated discount, zero-yield 그리고 forward curves를 구현할 수 있다. Listing 3.6에서 InterpolatedZeroCurve 클래스 템플릿을 outline한다; 다른 두개(InterpolatedForwardCurve와 InterpolatedDiscountCurve)는 같은 방식으로 구현된다.
template argument Interpolator는 두배의 task를 갖는다. 반면 이것은 traits(특성) 클래스로써 행동한다[18]. 이것은 이것의 속성(points-linear interpolation을 위해서는 최소 두개)과 interpolation 그리고 선택한 interpolation이 global인지(data point를 moving하는 것이 point를 포함하지 않는 interval에서의 interpolation을 변화시키는지 아닌지; cubic splines와 같은 것이 있음)를 명시한다. 반면 poor man's factory로 double된다; data points의 set이 주어졌을 때, 대응되는 Interpolation instance를 build하고 return하는 것이 가능하다 - (Interpolation 클래스는 section A.5에 나온다. A.5는 몇개의 이용가능한 interpolations와 대응되는 traits에 대해 다룬다)
public 생성자는 curve를 build하는데 필요한 data를 취한다: interpolate하기 위한 dates의 set, 대응되는 zero yields, day counter 그리고 optional interpolator instance. 대부분의 interpolations에는마지막 parameter는 필요하지 않다; 이것은 필요할 때 보내질수 있다. 구현은 parent ZeroYieldStructure 클래스로 passed dates의 첫번째 것(curve를 위한 reference date로 가정)과 day counter를 보낸다; 다른 인자들은 대응되는 데이터 멤버에 저장된다. 몇개의 일관성 확인을 수행한 후, dates를 times(전달된 reference date와 day counter를 이용하여) 변환하고, Interpolation instance를 생성할 것을 interpolator로 문의하고 그 결과를 저장한다.
이때 curve는 사용준비가 된다. 필요한 다른 함수는 one-liners로 구현된다; maxDate 함수는 전달된 dates의 제일 마지막을 return하고 zeroYieldImpl은 zero yield의 interpolated value를 리턴한다. TermStructure machinery는 이미 range-checking을 했기 때문에, Interpolation instance를 호출하는 것은 true argument를 포함한다. 이것은 만약 전달된 time이 주어진 range를 벗어났다면 그 값을 extrapolated하도록 만든다.
마지막으로 InterpolatedZeroCurve 클래스는 몇개의 protected 생성자를 정의한다. 이것들은 parent ZeroYieldStructure 클래스와 optional Interpolator instance의 생성자와 같은 인자를 취한다. 생성자들은 parent-클래스 생성자에 대응되는 인자를 보내지만, interpolation을 생성하지는 않는다 - 이것들은 zero-yield 데이터를 가지고 있지 않기 때문에 할수가 없다. 이것들은 InterpolatedZeroCurve를 상속하는 것이 가능하다; 파생된 클래스들은 data를 제공하며 그들이 취한 인자가 무엇이든(다음 섹션에서 다룸) 그것을 기반으로 interpolation을 생성한다. 같은 이유로 대부분 데이터 멤버는 mutable로 선언된다; 이것은 파생클래스가 interpolation을 lazily하게 update할수 있도록 만든다.
3.2.3 Example: bootstrapping an interpolated yield curve
이번 섹션에서는 all-purpose yield-curve template를 build할 것이다. 지난 서브 섹션에서 기술한 클래스 build하면서 discount facotrs, zero-yields 혹은 instantaneous forward rates상에서 다양한 방법의 interpolate하기 위한 방법을 설명하였다. curve의 nodes는 quoted market rates로부터 bootstrapped될 것이다.
말할 필요없이 이번 예제는 꽤 복잡할 것이다. 설명할 class template(PieceWiseYieldCurve)은 몇개의 helper 클래스들과 template tricks를 사용할 것이다. 필요한 모든것을 설명하도록 노력할 것이다.
class template는 listing 3.7에 있다. 클래스는 세개의 template arguments를 취한다. 처음 두개는 underlying data와 interpolation 함수를 선택한다; 우리의 목표는 template를 다음과 같같거나 비슷한 것으로 instantiate하는 것이다: PiecewiseYieldCurve<Discount, LogLinear>
첫번째 parameter는 bootstrap traits이다; 두번째는 이미 기술한 interpolator이다. 세번째 parameter는 bootstrapping algorithm을 구현하는 클래스를 명시한다. parameter는 default value(InterativeBootstrap class)를 갖기 때문에 대부분 이것을 몰라도 된다; 다른 bootstrapping algorithm으로 바꾸어도 된다. 이것의 문법이 익숙치 않은 사람들을 위해 template template parameter를 기술할 것이다[22]. template 두번이 맞다; parameter는 typename보다는 uninstantiated class template(이 경우 하나의 template argument를 취한다)이어야만 한다.(uninstantiated template는 std::vector와 같은 것이어야 하고 std::vector<double>과 같은 instantiated one과 반대의 개념이다. 두번째는 type의 이름을 주었지만 두번째는 아니다)
class interface를 선언하기 전에, curve의 parent class를 결정하기 위한 template programming가 필요하다. 반면 우리는 LazyObject class로부터 term structure를 상속한다; chapter 2에서 기술한 것과 같이 이것은 curve가 re-bootstrap를 스스로 할수 있도록 만든다. 반면 section 3.2.2에서 기술한 interpolated curves중 하나로부터 그것을 상속하기를 원한다; underlying data(discount, zero yields, forward rates)의 선택에 따라 class templates를 선택해야만 하고 선택한 interpolator class를 가지고 instantiate해야한다. 이것은 class template를 bootstrap traits에 저장하는것으로 이루어진다. 불행히, C++은 template typedefs를 허용하지 않는다(Template aliases는 아마 C++ standard 다음 revision에 소개될것이다). 그러므로 traits는 template parameter로서 interpolator를 취하고 typedef로서 instantiated parent class를 정의하는 inner class template curve를 정의할 것이다; 이것은 Discount traits의 정의에서 볼수 있으며 listing 3.8에 부분적으로 나타나 있다.
기술된 machinery는 다음 표현에 의해 선택된 class를 알리며 우리는 이것을 parent classes의 list에 추가할수 있다(이 표현을 컴파일러가 읽을때 컴파일러는 Traits가 무엇인지 모르며 Traits::curve가 class template 라는 것을 결정 할 수 있는 수단을 가지고 있지 않다. keyword를 추가하는 것이 컴파일러에 표현의 나머지 부분을 제대로 처리하는데 필요로 하는 정보를 준다): Traits::template curve<Interpolator>::type
instantiation은 bootstrap traits로서 Discount와 interpolator로서 LogLinear가 예제로 있으며 Discount::curve<LogLinear>::type는 InterpolateDiscountCurve<LogLinear>와 대응된다.
curve를 구현하기 전에 long template 표현을 피하기 위해 몇개의 typedefs를 정의한다: base_curve, this_curve, helper. 첫번째는 parent class를 의미하고, 두번째는 우리가 정의한 방금 그 클래스를 의미하며; 세번째는 helper class의 type을 bootstrap traits로부터 추출한다. 이것은 instrument의 quoted value와 bootstrapped되는 term structure의 instrument를 평가하는 수단으로 제공된다. bootstrap은 두개의 값이 일치(coincide)할 때까지 curve를 수정할 것이다. 마지막으로 두개의 typedefs는 두개의 대응되는 template arguments를 저장하므로서 나중에 검색할 수 있게된다.
마지막으로 실제 인터페이스이다. listing에서 보여지는 유일한 생성자들은 parent interpolated curve를 instantiating하기 위해 필요한 인자들과 a vector of helpers 그리고 bootstrap를 위한 target accuracy를 취한다. 다음으로 많은 inspectors(such as times or data)가 정의되고 parent class의 versions을 overriding한다; 이렇게 하는 이유는 마지막에 설명한다. public interface는 update 함수에 의해 완성된다.
performCalculations를 포함한 protected 함수들은 LazyObject interface를 구현하는데 필요하다; parent-class 버젼을 override하는 discountImpl(public inspectors와 같은). Bootstrap class template는 curve의 type과 함께 instantiated된다. 이것은 curve의 internals로 접근이 필요할 것이기 때문에 결과 클래스는 PiecewiseYieldCurve class의 friend로써 선언된다; BootstrapError class도 마찬가지다. 마지막으로 bootstrap class의 instance와 required curve accuracy 그리고 helper를 저장한다.
구현을 보자. 생성자는 놀랄일이 없다: base class에 필요한 인자를 넘기고 다른것들은 클래스에 저장한다. 마지막으로 저장된 Bootstrap instance에 curve를 전달하고 some preliminary work를 수행하도록 만든다. update 함수는 정확함(disambiguation)을 위해 필요하다; 함수의 구현을 정의하는 두개의 클래스(LazyObject와 TermStructure)로 부터 상속한다. 컴파일러는 당연하게 second-guess를 거부하므로서 우리는 명시적으로 두개의 parent 구현을 호출해야만 한다.
performCalculations는 작업을 Bootstrap instance에 위임한다. 마지막으로 discountImpl 함수를 보면 왜 이러한 함수가 overridden되어야만 하는지를 알수 있다; parent-class 구현을 호출하기 전에, data가 calculate 함수를 호출하는 것에 의해 bootstrapped되어야 한다는 것을 확실히 해야만 한다. 이것은 또한 같은 패턴을 따르면서 다른 overridden inspectors를 hold한다. 이시점에 BootstrapHelper class template를 기술해야 한다; 이것은 listing 3.9에 있다. curve를 위해 우리는 이것(이전의 Discount traits에서 볼수 있는)을 BootstrapHelper<YieldTermStructure>로서 instantiate한다; 편리를 위해 라이브러리는 더욱 verbose type name에서 사용할 수 있는 RateHelper로 불리는 클래스에 alias를 제공한다.
이 클래스의 각각의 instance - 혹은 파생된 클래스의; 클래스 자체가 abstract이다 - 는 curve상에 하나의 노드를 bootstrapping하는 것을 도울 것이다. node를 위한 input datum은 instrument의 quoted value이다; 값이 시간에 따라 변하기 때문에 Quote instance로의 Handle로서 제공된다. yield curve의 경우 이러한 instruments는 deposits 혹은 swaps이고 market rates로서 quoted된다. 각각의 instrument에는 파생된 클래스가 제공되야만 한다.
모든 helper에 공통인 functionality는 base class에서 구현된다. BootstrapHelper는 Observer와 Observable을 상속한다; 두개의 역할은 market value를 등록할수 있게 하며 새로운 bootstrap의 수행의 필요성을 signaling하면서 변화를 curve에 알릴수 있다. 이것의 생성자는 input market value를 제공하는 Handle<Quote>를 취하고 그것을 데이터 멤버에 저장하며 observer로서 자신을 등록한다. 세개의 함수는 underlying instrument value를 다룬다. quote 함수는 quoted value를 포함하는 handle를 반환한다; abstract impliedValue 함수는 bootstrapped되는 curve상에서 계산한 값을 리턴한다; convenience 함수인 quoteError는 두 값의 signed 차이를 리턴한다.
두개의 함수가 bootstrap algorithm을 set up하기 위해 더 사용된다. setTermStructure 함수는 만들어지는 curve와 함께 helper를 연결한다. latestDate 함수는 시장 데이터의 implied value를 계산하기 위해 사용되는 curve data를 위한 latest date를 리턴한다(latest required date는 instrument의 maturity와 일치할 필요성은 없다. 예를 들어 만약 instrument가 constant-maturity swap인 경우, curve는 마지막 coupon에 의해 지불되는 rate를 예측하기 위한 swap maturity보다 몇년을 확장해야 할 것이다.); 이러한 date는 bootstrapped되는 노드의 coordinate로서 사용될 것이다. 마지막 함수(update)는 quote로부터 helper의 observers로 notification을 전달한다.
라이브러리는 RateHelper를 상속하는 몇개의 concrete helper classes를 제공한다. 간략함을 위해 실제 코드를 보여주는 대신 hand-waving(간단한 말?)을 사용할 것이다. 각각의 helper 클래스는 특정 instrument를 위해 impliedValue를 구현하고 적절한 latest date를 리턴하기 위한 code를 포함한다. 예를들면, DepositRateHelper 클래스는 이것의 시작과 maturity date 사이의 forward rate를 위한 curve를 asking함으로서 quoted deposit rate를 예측한다; 반면 SwapRateHelper 클래스는 Swap 객체를 instrantiating하고 bootstarapped되는 curve상에서 pricing하고 fair rate를 implying함으로서 swap rate를 예측한다.
bootstrap code를 보자. Listing 3.10에서 라이브러리에서 제공되고 default로 사용되는IterativeBootstrap class의 인터페이스를 보자. 편의를 위해 curve로부터 traits와 interpolator types를 추출하기 위해 typedefs가 정의된다. 생성자와 setup 함수는 특정 흥미 대상이 아니다. 첫번째는 term-structure pointer를 null로 초기화한다; 두번째는 전달된 curve pointer를 저장하고 curve를 bootstrap하기 위해 충분한 helper를 가지고 있는지 확인해야하며 각각의 helper의 observer로서 curve를 등록한다. bootstrap algorithm은 calculate 함수에 의해 구현된다. 여기서는 details와 corner cases는 대충 넘어간다; 관심있다면 라이브러리 풀 코드를 보면 된다.
첫번째로 모든 helper는 현재의 term structure로 셋팅한다. 이것은 a set of helpers가 각각 다른 curves를 사용하도록 할 때마다 수행된다(멀티 쓰레드 환경이라면 안전하게 하기 위해 당연히 같은 helpers은 다른 curves에 넘겨질수 없다. QuantLib의 대부분은 thread-safe가 아니다.) 그리고 vectors가 초기화된다. 초기 노드를 위한 date와 value는 전달된 traits에 의해 제공된다; yield term structure를 위해 초기 date는 curve의 reference date에 대응한다. 초기값은 underlying data의 선택에따라 달라진다; discount factors를 위해 1, zero or forward rates를 위해 더미값(bootstrap procedure가 진행되는 동안 overwritten될 것이다)이 설정된다. 다른 노드들을 위한 dates는 대응되는 helpers의 latest needed dates이다; times는 이용가능한 curve facilities를 사용함으로써 구할수 있다.
이시점에서 각 노드(appendix A에 자세히 나온다)에서 사용하고 실제 bootstrap을 시작할 one-dimensional solver를 instantiate할 것이다. 계산은 두개의 nested loops로 작성된다; 각 노드에서 수행되는 inner one(bootstrap proper)과 프로세스를 반복하는 outer one. Iterative bootstrap은 non-local interpolation(cubic splines와 같은)이 사용될때 필요하다. 이 경우 노드의 값을 설정하는 것은 전의 노드를 무효화하면서 전체의 curve를 수정한다; 그러므로 convergence가 일어날 때까지 노드를 계속 주시해야만한다.
전에 말했듯이 inner loop는 각 노드를 돌아다닌다-물론 earliest date부터 시작한다. 첫번째 iteration(when iteration == 0)동안 interpolation은 한번에 한 point씩 확장한다; later iterations는 full data range를 사용한다 그래서 이전 결과는 starting point와 refined로서 사용된다. 각 노드가 추가된 후에, one-dimensional root-finding 알고리즘은 대응되는 시장 quote를 reproduce(재생산)하기 위해 사용된다. 첫번째 iteration을 위해, guess for the solution은 bootstrap traits 혹은 지금까지 built된 curve를 extrapolating하는 것에 의해 제공될 수 있다; 앞으로의 iteration을 위해, 이전의 결과는 guess로 사용된다. 최대/최소값은 이미 bootstrap된 노드를 기반으로 한 traits에 의해 제공된다. 예를 들면, bootstrapping zero 혹은 forward rates를 위한 traits는 최소값을 0으로 설정함으로써 음수값을 방지할 수 있다; discount factors를 위한 traits는 discounts가 증가하지 않도록 함으로써 같은 것을 할수 있다, 즉 이전 노드에서 discount를 최대로 셋팅하게 함으로서.
root-finding 알고리즘에서 딱 하나 빠진것은 zero는 반드시 찾아져야만 하는 기능이다. 이것은 BootstrapError class template(shown in listing 3.11)에 나온다. 이것은 helper's quoteError 계산을 function-object interface로 채택한다. 생성자는 만들어지고 있는 curve, 현재노드를 위한 helper 그리고 node index를 취하고 저장한다. operator()는 functions로써 사용될수 있는 class의 인스턴스를 만든다; node value를 위한 guess를 취하고 curve data를 수정하며 quote error를 리턴한다.
이 시점에서 모든것이 set된다. inner bootstrap loop는 BootstrapError 인스턴스를 생성하고 이것을 error가 zero인 것을 위한 노드 value를 리턴하는 solver로 보낸다. 즉 implied quote가 market quote와 같은 것(within accuracy)을 위해. curve 데이터는 리턴된 값을 포함하기 위해 업데이트 되고, 루프는 다음 노드로 진행한다.
모든 노드가 bootstrapped되고나서 outer loop는 다른 iteration이 필요한지를 확인한다. local interpolation의 경우는 해당되지 않고, loop를 break out할 수 있다. non-local의 경우 obtained accuracy가 확인되며 iteration은 convergence에 도달할때까지 추가된다.
여기서 bootstrap과 예제를 마친다. PiecewiseYieldCurve 클래스를 사용하는 예제는 QuantLib의 swap-valuation 예제에 나와있다.
3.2.4 Example: adding z-spread to a yield curve
이번 예제는 다른 것을 기반으로 하여 어떻게 term-structure를 build하는지를 보여준다. 기존의 risk-free curve를 취하고 credit risk를 포함하기 위해 그것을 수정할 것이다. risk는 z-spread(zero-yield rates로 added되는 constant spread)로 표현된다. 여기서 Decorator pattern을 사용할 수 있다; 기존의 객체를 감싸서 some behavior과 위임을 추가할 것이다.
ZeroSpreadedTermStructure의 구현은 listing 3.12에 있다. 이미 말했듯이 이것은 zero-yield rates를 기반으로 한다; 그러므로 section 3.2.2에서 기술한 ZeroYieldStructure adapter를 상속하고 zeroYieldImpl 함수를 구현할 것이다. 생성자는 수정될 risk-free curve와 적용될 z-spread 를 인자로 취한다; data sources를 바꾸는 것을 허용하기 위해 둘다 handles로써 전달된다. 데이터 멤버에 저장된 인자와 curve는 observer로써 등록한다. update 함수는 notification을 전달하는 역할을 한다.
ZeroSpreadedTermStructure의 구현은 listing 3.12에 있다. 이미 말했듯이 이것은 zero-yield rates를 기반으로 한다; 그러므로 section 3.2.2에서 기술한 ZeroYieldStructure adapter를 상속하고 zeroYieldImpl 함수를 구현할 것이다. 생성자는 수정될 risk-free curve와 적용될 z-spread 를 인자로 취한다; data sources를 바꾸는 것을 허용하기 위해 둘다 handles로써 전달된다. 데이터 멤버에 저장된 인자와 curve는 observer로써 등록한다. update 함수는 notification을 전달하는 역할을 한다.
명시적으로 베이스 클래스의 생성자가 불리워지지 않는다. section 3.1.2에서 보듯이 이것은 우리의 curve가 데이터(TermStructure machinery에 의해 사용되는)를 저장하지 않는 것을 의미한다; 그러므로 reference-date 계산과 관련된 함수들의 구현을 제공해야만 한다. 진짜 Decorator fashion에서는 wrapped object에 행동을 위임함으로서 수행된다; referenceDate, dayCounter, calendar, settlementDays, and maxDate 함수는 각각 risk-free curve의 대응되는 함수로 보낸다.
마지막으로 특정 행동을 구현한다 - adding the z-spread. zeroYieldImpl함수에서 수행된다; 필요한 time(continuously compounded, 우리의 함수가 리턴해야만 하기 때문에)에 zero-yield rate를 risk-free curve에 문의할 것이고, z-spread의 값을 더하고, 새로운 zero-yield rate로서의 결과를 리턴할 것이다. ZeroYieldStructure adapter의 machinery는 원하는 risky curve를 주면서 나머지를 다룰 것이다.
3.3 Other term structures
이 챕터에서는 yield term structures에 초점을 맞추었다. 물론 다른 종류의 term structure도 라이브러리에 구현되어 있다. 그것들을 간단히 리뷰할 것이다: yield term structure와 어떻게 다른지 어떤 특징이 있는지를 살펴볼 것이다.
3.3.1 Default-probability term structures
Default-probability term structures는 yield term structures의 디자인 면에서 대부분 비슷하다. 이것들은 default probability, survival probability, default density 또는 hazard rate에 의해 표현될 수 있다. 이 네개는 하나만 있으면 다른 하나를 얻을 수 있다; zero rates와 discount factors와 흡사하다.
yield term structure(discountImpl함수에 의해 구현되는 모든 함수에 있는)와 다르게, base default-probability structure는 다른 것들이 build upon 할 수 있는 single 함수를 가지고 있지 않다. 대신에 listing 3.13에서 보는 것과 같이 다음의 두 abstract 함수를 선언한다: 이것은 파생클래스가 어떤것을 구현할지를 결정하게 된다; survivalProbabilityImpl, defaultDensityImpl. base class는 각각의 구현 함수를 기반으로 survivalProbability 그리고 defaultDensity를, survivalProbability를 기반으로 defaultProbability를, survival probability와 default density에 의해 hazardRate를 리턴한다.(survivalProbability와 defaultDensity의 구현은 여기서 보여지는 것과 같이 간단하지 않다-여기서는 clarity를 위해 range checking과 extrapolation을 뺐다.)
listing 3.14에서는 adapter classes를 보여주며 yield term structure에 대해 말하자면, 선택(survival probability, default density, or hazard rate - default probability는 survival probability와 너무 많이 관련되어 있어서 필수적으로 대응되는 adapter를 제공하지 않는다)의 single quantity에 의해 새로운 default-probability structure를 정의해보자. 첫번째 것(SurvivalProbabilityStructure)은 purely virtual이고 파생 클래스에서 반드시 제공되어야만 하는 survivalProbabilityImpl의 구현에 의해 defaultDensityImpl을 정의한다; 두번째(DefaultDensityStructure)는 반대이다; 세번째(HazardRateStructure)는 newly 선언된 purely abstract hazardRateImpl 함수에 의해 survival probability와 default density 두개를 다 정의한다.
불행히, 몇몇 adapters는 quantities들을 conversion하기위해서 numerical intergration을 의존한다. 제공되는 구현은 효율적으로 intergration을 수행하기 위해 maths와 coding에서 dark magic을 사용한다 - Gaussian quadratures(구적법)and boost::bind; 그러나 이러한 클래스를 상속할 때, 만약 integral에 closed formula를 이용할 수 있다면, adapter 함수를 overriding하는 것을 고려해야만 한다.
yield curve의 경우, 라이브러리는 discrete data를 interpolating하는 것에 의해 adapter interface를 구현하는 template 클래스와 generic piecewise default-probability curve template, 그리고 underlying quantity를 선택하기 위해 필요한 traits를 제공한다. 기존의 interpolation traits와 함께, 이것은 PiecewiseDefaultCurve<DefaultDensity, Linear>와 같은 클래스를 instantiate하는 것을 허락한다. 구현은 section 3.2.3에서와 비슷하다; 너무 비슷해서 여기서 다룰 가치가 없다. 한가지 기억할 것은 default-probability structure는 self-sufficient하지 않다는 것이다: bootstrap이 필요한 instrument를 price하기 위해, discount curve가 필요하다. 당신의 pricing engine을 위해 같은 curve를 사용해야만 할 것이다; 그렇지 않다면 당신의 CDS는 갑자기 더이상 at the money가 아닌 것을 발견하게 될 것이다.
3.3.2 Inflation term structures
Inflation term structure는 지금까지 설명한 term structure와는 다른 특징들을 가지고 있다. 이것은 클래스의 복잡함을 증가시킨다.
가장 두드러진 차이는 하나가 아니고 두개의 다른 inflation term structures(그리고 두개의 다른 interfaces)를 갖는다는 것이다. 라이브러리는 하나의 base class InflationTermStructure를 제공하며 이것은 몇몇 inspectors와 공통 behavior를 포함한다; 그러나 실제 inflation rates를 리턴하는 interfaces는 두개의 다른 child classes에서 선언되고 이 계층은 listing 3.15에 나타난다. 두개의 subclasses 모델 zero-coupon과 year-on-year inflation rates(서로 쉽게 convert되지 않는다)는 우리의 usual multiple-adapter scheme를 foil(좌절)시킨다.
이것은 장/단점을 갖는다. 이것은 duplicated sub-hierarchies의 pair를 일으키며 이것은 obviously a smell이다(이것은 더 안좋은 결과를 가지고 올 수 있다. 지금까지 annual말고 period-on-period with a frequency를 다루지 않았다. 바라던데, 그것들은 오직 year-on-year curve의 일반화를 이끈다.) 반면 이것은 sub-hierarchies를 간단하게 만든다; 예를들면, 각각의 term structure가 오직 하나의 underlying quantity(즉, zero-coupon rates 아니면 year-on-year rates이다)를 갖기 때문에 adapter 클래스가 필요없다.
다른 차이는 inflation fixings의 특이 사항(specific quirks)때문이다. 주어진 month를 위한 inflation figures는 observation lag 이후에 announced되기 때문에, inflation term structures는 reference date 뿐만 아니라 base date를 갖는다; base date는 latest announced fixing과 대응되는 것이다. 만약 inflation figure가 오늘의 date와 관련되지만 base date 이후인 과거의 date를 위해 필요하다면, 반드시 예상되어야만 한다.(이것은 이 챕터의 초반에 모호하게 제안했던 future doesn't always begin at the reference date의 예외이다.) 또한 inflation fixings는 계절적 변동에 영향을 받기 때문에, inflation term structures는 polymorphic Seasonality class(간결함을 위해 설명하지 않는)의 인스턴스를 저장할 수 있는 수단을 제공한다. 만약 이것이 주어진다면, 이러한 인스턴스는 Strategy pattern을 이용하여 returned inflation rates를 위한 seasonal correction을 model한다.
다른 term structure과 다르게, inflation interface는 date 대신에 time을 취하는 함수를 제공하지 않는다; 모든 fixing machinery는 많은 date calculations(what month we're in, the corresponding period for the fixing, and whatnot)에 의존하며, times to dates의 변환에 대해 신뢰할 만한 방법이 없기 때문에 관련된 모든것을 중지(call off) 했다.
또 다른것은 inflation term structures는 discount curve를 저장하는 것이다.
== 여기서는 고칠점이 많다. 추후에 고치겠다는 ==
3.3.3 Volatility term structures
3.3.4 Equity volatility structures
3.3.5 Cap and caplet volatility structures
3.3.6 Swaption volatility structures
'quantlib > Implementation' 카테고리의 다른 글
Aside: symmetry break (0) | 2011.06.19 |
---|---|
Aside: evaluation date tricks (0) | 2011.06.18 |
Strategy pattern (0) | 2011.06.16 |
Template Method pattern (0) | 2011.06.15 |
class template & Function Template (0) | 2011.06.15 |