2011. 6. 6. 15:48 quantlib/Implementation
2. Financial instruments and pricing engines
Financial library
-price financial instruments
-add new pricing functionality
-add new financial instruments
-add new means of pricing an existing instrument
위 조건들을 만족하는 quantlib의 설계에 대하여 설명할 것이다.
2.1 The Instrument class
이쪽 도메인에서 Financial instrument가 base class로 만드는 것은 자연스러운 결정이다.
장점: 모든 instruments를 일괄적으로 처리 할 수 있다. ex. 포트폴리오 가치 평가시
2.1.1 Interface and requirements
여러가지의 상품들이 서로 불필요한 요건이 존재하기 때문에, 일반적인 공통 함수는 매우 적다.
returning its present value (possibly with an associated error estimate)
indicating whether or not the instrument has expired
반드시 vitual일 필요는 없다.
update : observer 인터페이스를 구현하고 calculated_를 false로 셋팅함으로써 직전의 결과를 무효로 만든다.
calculate : Template Method pattern으로 구현한다.
변하지 않는 부분(이 경우, 저장된 결과의 관리 부분)을 base 클래스로 구현한다.
그리고 다양한 부분(여기서는, 실제 계산하는 부분)은 virtual 함수(performCalculations)를 이용하여 위임한다. 위임된 함수는 base 클래스의 body에서 호출된다.
즉, 파생 클래스는 저장(caching)부분은 신경쓸 필요없이 자신의 특정 계산 부분만을 신경쓰면 된다.
the logic of the caching : 만약 현재 결과가 유효하지 않으면, 파생 클래스는 계산을 진행하고, 새로운 결과로 대체한다. 만약 현재의 결과가 유효하다면, 아무것도 하지 않으면 된다.
calculated_를 셋팅하는데 왜 try 블럭을 넣었어야했는지,
그리고 handler가 예외처리를 하기 전에 롤백하는 것에 대하여 궁금할 것이다.
다음과 같이 저장과 예외처리 부분을 제외하여 간단히 구현할 수 있다.
예를 들면, lazy 객체가 lazily하게 bootstrap하는 yield term structure인 경우에 performCalculations는 재귀적으로 calculate를 호출할 수 있다.
만약 calculated_가 true로 설정되어 있지 않다면, performCalculations를 다시 호출하게 될 것이며 이것은 무한 재귀호출을 하게 만들 것이다.
calculated_를 true로 설정하여 이러한 상황을 방지하며, 만약 예외가 발생하면 이것을 false로 복귀시켜야만 한다.
LazyObject에는 사용자가 결과를 재계산하는 것을 방지하거나 강요하게 되는 함수들이 몇 몇 더 있지만 여기서 다루지 않는다.("Read the source, Luke." by master Obi-Wan Kenobi)
Instrument 클래스는 LazyObject를 상속한다.
LazyObject의 인터페이스를 구현하기 위해 calculate 함수를 financial instrument에 특화된 코드로 decorate한다. 내용은 다음과 같다.
한번 더 말하지만, 추가된 코드는 instrument에 특화된 계산을 파생 클래스로 위임하기 위해 Template Method pattern을 사용한다.
데이터 멤버 NPV_는 계산된 결과값을 저장하기 위한 용도로 정의되었다.
파생 클래스는 특정 결과값(ex. errorEstimate_)을 저장하기 위한 다른 데이터 멤버들을 선언할 수 있다.
isExpired함수는 instrument가 만료가 되었는지를 확인한다. 만약 만료가 되었다면 setupExpired virtual 함수를 호출한다. 이 함수는 NPV_의 default 값으로 0을 설정하며 파생 클래스에 의해 호출된다.
만약 instrument가 만료가 되지 않았다면, LazyObject의 calculate함수가 호출된다. 이것은 performCalculations함수를 호출할 것이다.
불행하게도 위의 접근은 여전히 부족한 점이 있다. 이상적으로 Instrument 클래스에서 오직 한번의 dispatching code를 구현하기를 원한다. 그러나 사용된 코드는 충분히 포괄적이지 않다.
이것은 명시된 수와 인자의 타입을 받아들이는 엔진에 의존하고 있기 때문이다.
그러나 instrument 클래스들은 수와 타입에 있어 다양한 데이터 멤버들을 갖기 때문에 이것은 명백히 받아들일 수 없다. 이것은 결과값 리턴에 있어서도 마찬가지이다. 예를 들면, interest-rate swap은 fixed rate와 floating spread의 fair value를 리턴할 수 있을 것이고 ubiquitous European option은 Greeks를 리턴할지도 모른다.
위에서 정의한 것과 같이 함수를 통하여 엔진에 인자를 전달하는 인터페이스는 생각하지 않은 두 가지 결과를 가져온다. 하나는 우리가 하나의 베이스 클래스를 정의하는 것에 방해가 되는 것으로 각각 다른 instruments를 위한 pricing 엔진이 각각 다른 인터페이스를 가지게 될 것이다. 다른 것은 엔진을 호출하는 코드가 각각의 instrument 클래스에서 복제(replicated)되어져야만 할 것이다. 이것은 미친짓이다.
우리가 선택한 해결방법은 인자와 결과들이 불투명한(opaque) 구조가 적절히 호출하는 방법으로 엔진으로부터 보내고 받아지는 것이다. 그것들(인자들과 결과들)로부터 파생되고 instrument에 특정한 데이터를 가지고 그것들(인자들과 결과들)을 호출하는 두 개의 구조는 어떤 pricing 엔진에서도 저장될수 있다; 어떤 instrument는 엔진과 정보를 교환하기 위해서 그러한 데이터를 읽고 쓸수 있다.
다음은 PricingEngine 클래스의 인터페이스와 내부의 argument, results 클래스 그리고 helper GenericEngine 클래스 템플릿을 보여주고 있다. GenericEngine은 PricingEngine 인터페이스의 대부분을 구현하고 있으며, 특정 엔진들의 개발자들을 위해 calculate 함수의 구현만을 남겨두고 있다. 데이터를 드롭박스처럼 쉽게 사용하게 할 수 있도록 argument와 results 클래스들이 함수에게 주어진다. arguments::validate는 input 데이터가 유효한 범위내에 있는 값인지 확인하기 위해 쓰여진 이후에 호출된다; results::reset은 엔진이 이전의 결과를 지우기 위해서 계산을 시작하기 전에 호출된다.
이러한 새로운 클래스를 갖춤으로서 generic performCalculation 함수를 완료할 수 있다.
먼저 언급한 Strategy pattern외에 특정한 instrument가 missing bits를 채우기 위해 Template Method pattern을 사용할 것이다. 이것은 다음과 같다.
inner 클래스 Instrument::result가 정의되는 것을 알 수 있을 것이다; 이러한 클래스는 PricingEngine::results로부터 상속하며 어떤 instrument에도 제공해야만 하는 결과를 포함한다.(Instrument::results 클래스는 pricing 엔진이 추가적인 결과를 저장할 수 있도록 std::map을 포함하며 이러한 부분은 명확성을 위해 여기서는 빠져있다.)
performCalculation 함수에 대해서 말하자면, 실제 작업은 다수의 collaborating 클래스들 사이에서 분리되는 것으로 보인다 - instrument, pricing 엔진, arguments/results 클래스들.
이러한 collaboration의 역학(dynamics)은 다음의 UML sequence 다이어그램을 이용하면 가장 이해하기 쉬울것이다; 그 다음은 클래스들(과 가능한 concrete instrument) 사이의 static한 관계를 보여준다.
*Sequence diagram of the interplay between instruments and pricing engines *Class diagram of Instrument, PricingEngine, and related classes including a derived instrument class
instrument의 NPV 함수를 호출하면 이것의 performCalculations 함수를 호출하게 된다.(만약 instrument가 만료가 되지않고, 관련된 quantities가 계산할 필요가 있다면)
이곳이 instrument와 pricing 엔진이 상호작용하기 시작하는 곳이다.
가장 먼저 instrument는 엔진이 이용가능한지 확인한다. 만약 가능하지 않다면 계산을 중지한다.
만약 가능한 것을 찾으면 instrument는 그것으로 즉시 리셋한다. 메시지는 reset 함수에 의해 instrument-specific result structure로 보내진다; 이것이 수행된 이후에 structure는 새로운 result를 쓰기위해 clean slate상태로 대기한다. 이 시점에서 Template Method pattern가 그 상황으로 들어간다. instrument는 arguments의 포인터로서 return되는 argument structure를 위해 pricing 엔진에 문의한다. 포인터는 패턴에서 변수 부분으로 동작하는 instrument의 setupArguments 함수에 보내진다. instrument에 따라 이러한 함수는 전달된 인자가 올바른 타입인지 확인하고 올바른 값으로 데이터 멤버를 채우도록 진행한다. 마지막으로 arguments는 validate 함수를 호출함으로서 새롭게 쓰여진 값들을 확인하는 작업이 수행되는 것에 대해 질문을 받게 된다.
이제 Strategy pattern을 적용할 차례이다.
인자들이 셋팅되고 선택된 엔진은 calculate 함수에 구현된 특정 계산이 수행될지를 선택하게 된다. 이것이 진행되는 동안 엔진은 argument structure로부터 필요한 인풋을 읽을(read) 것이고, results structure로 대응되는 결과를 작성할 것이다(wrtie).
엔진이 작업을 완료한 이후에 제어는 Instrument 인스턴스로 넘어올 것이고 Template Method pattern은 계속해서 unfolding을 할 것이다. 호출된 함수인 fetchResults는 결과를 엔진에게 문의해야만하며, 포함된 데이터로 접근하기 위해 downcast하고 자신의 데이터 멤버로 해당 값을 복사한다.
Instrument 클래스는 결과를 모든 instruments로 fetch하는 default 구현을 정의하고 있다; 파생된 클래스는 특정 결과를 읽기 위해 이것을 확장할 수 있다.
2.2.1 Example: plain-vanilla option
위에서 기술된 것들이 instrument의 구현에 어떻게 사용되는지 살펴보자. 이것은 어쩌면 기존의 것에서 새로운 클래스를 빌드하는 것을 더 쉽게 만들며 코드를 재사용할수 있게하는 많은 instruments의 공통 개념을 찾고 추상화하는 library writer의 작업과 상충할수 있다.
???
예를들면, plain-vanilla options(간단한 call put equity options with either European, American or Bermudan exercise)를 구현하는 QuantLib에 존재하는 클래스가 클래스 계층에서 가장 밑의 leaf에 존재한다. Instrument 클래스를 root로써 갖는 이러한 계층은 Option 클래스를 가지고 첫번째로 특화한다. 그리고 하나의 기초자산을 가지고 일반화하며 우리가 관심있는 VanillaOption 클래스를 정의할 때까지하나 혹은 둘의 클래스를 통하여 passing하는 OneAssetOption 클래스를 가지고 특화한다.
이러한 증식이 좋은 여러가지 이유가 있다. 예를들면 OneAssetOption 클래스의 코드는 Asian options로 재활용 될 수 있는 반면, Option 클래스의 코드는 모든 종류의 basket options를 구현할때 재사용된다. 불행히도 이것은 plain option을 pricing하는 코드가 상속 chain의 모든 멤버에 퍼져 있게 만든다. 이것은 극단적으로 명백한 예제로서 도움이 되지 않는다. 그러므로 타협을 해야만 한다. 이번 section에서 synthesized VanillaOption 클래스를 라이브러리에서의 어떤 것과 같은 구현을 가지고 설명할 것이다. 하지만 Instrument 클래스로부터 직접 상속할 것이다; 구현된 모든 클래스를 보여줄 것이며 상속보다는 구현으로 보여줄 것이다.
다음은 vanilla-option 클래스의 synthesized 인터페이스를 보여준다. Instrument 인터페이스에서 필요로 하는 함수와 옵션의 greeks라 불리는 추가적인 결과에 접근하는 접근자를 선언한다; 지난번 얘기했듯이 논리적으로 constant인 calculate 함수에서 값을 설정하기 위해 대응하는 데이터 멤버들은 mutable로 선언한다.
자신의 데이터와 함수들과 같이 VanillaOption은 특정 argument와 result structures 그리고 base pricing engine과 같은 accessory 클래스들을 선언한다.
그러한 클래스들은 그들과 option 클래스들의 관계를 강조하기 위해 inner 클래스들로 정의한다; 다음의 인터페이스에 나타난다. 이러한 accessory 클래스들에 두개의 코멘트를 붙일 수 있다.
첫번째는 예제의 소개부분에서 소개한 것과는 반대로 results 클래스에 모든 데이터 멤버를 inline하지 않았다. 구현의 디테일을 보여주기 위해 이렇게 했다. 이것은 사용자가 results와 관련되면서 공통으로 사용되는 것들을 갖으면서 structures를 정의하도록 할 것이다; 이러한 structures는 상속의 수단으로 재사용될 수 있으며 마지막 structure를 획득하기위한 Value를 가지고 구성되는 Greeks structure의 예제가 될수 있다. 이 경우에 Results로부터의 가상 상속은 악명높은 상속 다이아몬드를 피하도록 사용되어져야만 한다.
두번째는 (올바른 argument와 result types을 가지고 instantiated되는)클래스 템플릿 GenericEngine으로부터 상속하는 것이 특정 instrument의 pricing 엔진을 위한 베이스 클래스를 제공하기에 충분하다는 것이다. 우리는 파생된 클래스는 오직 그들의 calculate 함수만을 구현하면 된다는 것을 보게 될 것이다.
다음의 VanillaOption 클래스의 구현을 보자. 생성자는 instrument를 정의하는 몇개의 objects를 취하고 있다. 그들의 대부분은 다음의 챕터 혹은 appendix A에서 설명될 것이다. 당분간(For the time being) payoff는 strike와 right(i.e., call or put) of option를 포함할 것이며 exercise는 exercise date와 variety(i.e., European, American, or Bermudan.)을 포함하는 것으로 충분할 것이다. 보내진 인자는 대응되는 데이터 멤버에 저장될 것이다. 시장 데이터를 포함하지 않으며 다른 어딘가에 저장된다는 것을 기억하라.
expiration롸 관련된 함수는 굉장히 단순하다; isExpired 함수는 최근의 행사일이 지났는지 아닌지를 확인하며 setupExpired 함수는 base 클래스의 구현을 호출하고 instrument-specific 데이터를 0으로 설정한다.
setupArguments와 fetchResults 함수는 좀 더 흥미롭다. setupArguments는 generic argument 포인터를 필요로 하는 실제 타입으로 downcasting하고 다른 타입이 들어오면 예외를 발생시킨다. 그리고 나서 실제 작업을 시작한다. 몇 몇 경우 데이터 멤버는 대응되는 argument slots으로 그대로 복사(copied verbatim)된다. 그러나 이것은 같은 계산이 여러 개의 엔진에서 필요한 경우일 것이다; setupArguments 함수는 오직 한번 그것들을 write할 수 있는 장소를 제공한다.
fetchResults 함수는 setupArguments 함수의 dual(이중)이다(이중으로 되어있다.) 이것 역시 전달된 results 포인터를 downcasting하는 것으로 시작한다; 실제 타입을 검증한 후 자신의 데이터 멤버로 결과를 복사한다.
엔진은 다음과 같으며 European 옵션을 위한 analytic Black-Scholes-Merton 공식을 구현한다. 생성자는 present value, risk-free rate, dividend yield 그리고 volatility를 포함하는 기초자산(underlying)에 대한 시장 정보를 포함하는 Black-Scholes stochastic process를 자체로 등록하고 가진다. 실제 계산은 BlackCalculator 클래스라는 다른 클래스의 인터페이스 뒤쪽에 숨어 있다. 그러나 코드는 몇몇 관련된 것들을 보여주기에 충분히 자세하다.
함수는 몇몇 사전 조건들을 검증하는 것으로부터 시작한다. 이것은 계산의 인자가 이미 calculate함수가 호출하는 time에 의해 검증되었기 때문에 놀라움으로 느껴질지도 모른다. 그러나 주어진 엔진은 계산이 수행되기 전에 수행되어야할 더많은 요구사항들을 가질 수 있다. 우리의 엔진의 경우, 그러한 하나의 요구사항은 옵션이 유로피언 옵션이라는 것이고 payoff가 plain call/put(castdown이 필요하다)이라는 것이다.(boost::dynamic_pointer_cast is the equivalent of dynamic_cast for shared pointers.)
함수의 중간부분에서 엔진은 전달된 인자로부터 정형화되지 않은 정보를 추출한다. 여기서 보여지는 것은 기초자산의 spot price이다; 엔진에 의해 필요한 다른 quantities, e.g., 기초자산의 forward price와 만기시 risk-free discount factor 또한 추출된다.
마지막으로 계산이 수행되며, 결과는 대응되는 results structure의 slots에 저장된다.
이것으로 calculate 함수와 예제를 마친다.
2.2.2 Alternative designs
여전히 downcasting이 없어질수 있는지 궁금할수 있다; 이것은 이것의 가치보다 더 큰 대가를 필요로 할 수 있다.
특정 instrument가 정확한 타입의 인자를 전달받기 위해서 그리고 포괄적인 방법으로 관련 함수를 작성하기 위해서는 template programming과 같은 것이 절대적으로 필요하다; 더욱이 Template Method pattern의 사용은 polymorphism과 같은 것을 필요로 한다. Curiously Recurring Template pattern을 사용하기를 제안한다(파생 클래스(derived class)에서 기반 클래스(base class)의 기능을 재정의하여 기반 클래스의 구현을 파생클래스의 구현으로 대체하는 것을 함수 오버라이딩(function overriding)이라고 한다. 파생 클래스가 기반 클래스의 함수를 오버라이드하기 위해서 일반적으로 가상 함수(virtual function)를 사용하는데 여기에는 두가지 단점이 있다.
1) vtable(virtual function table)을 위한 추가 공간이 필요하고 2) 함수 호출 과정이 vtable을 거쳐 함수 포인터를 참조하므로 오버헤드가 증가한다. 만약 컴파일 타임에 정적으로 기반 클래스의 함수를 오버라이드할 수 있다면 가상 함수를 사용할 때의 오버헤드를 줄일 수 있을 것이다. 템플릿을 사용하여 정적으로 오버라이딩할 수 있는 기법이 바로 Curiously Recurring Template Pattern (CRTP) 이다.); 이것의 가능한 구현은 다음과 같다.
GenericEngine 클래스를 수정해서 inspectors들이 불투명한 base 클래스들 대신에, 특정인자나 result structures를 리턴하게 만들수 있다. 이것은 downcasting의 필요성을 제거할 수 있다; 그러나 이것은 instrument가 타입을 정확히 맞춰야 하도록 강요한다.
그러므로 특정 argument와 results 타입은 Instrument 클래스에 특정 instrument 타입과 함께 template argument로 전달된다. 더이상 Instrument 인터페이스에 선언되지 않는 setupArguments 함수와 이것의 dual fetchResults 함수는 제외하고 대부분의 클래스는 변하지 않은 상태로 남게 된다. 이런 이유로 performCalculations 함수의 body는 반드시 수정되어야 한다: this pointer는 반드시 특정 instrument 타입으로 정적 cast되어야만 한다. 그래서 이러한 함수를 호출하는 것은 제한되어야한다(can be bound). 마지막으로 instrument의 개발자는 cycle을 close해야만 한다: 특정 instrument는 Instrument로부터 상속되어져야만 하며 같은 클래스로 template argument로써 전달되야 한다. 그리고 필요한 함수는 반드시 정의되어야만 한다. 또한 argument와 result structures는 template instantiation 시점에서 이용가능하도록 하기 위해 instrument 이전에 선언되어야만 한다.
비록 동작할지라도 이 구현은 실제로 장점을 제공하는 것 같지 않다. 이것은 오히려 새로운 instruments를 생성하는데 있어 개발자를 더욱 번거롭게 만든다. 또한 이것은 template cycle이closed될 때 클래스로부터 파생되는 것을 불가능하게 만든다; 예를 들면 파생된 fixed-coupon bond 클래스와 자체로 사용가능한 bond class(bond class usable on its own) 이 두개를 코딩하는 것으로부터 한개를 방해할지도 모른다. 이러한 측면에서 현재 Instrument의 구현은 변하지 않고 그대로 두는 경향이 있다.
-price financial instruments
-add new pricing functionality
-add new financial instruments
-add new means of pricing an existing instrument
위 조건들을 만족하는 quantlib의 설계에 대하여 설명할 것이다.
2.1 The Instrument class
이쪽 도메인에서 Financial instrument가 base class로 만드는 것은 자연스러운 결정이다.
장점: 모든 instruments를 일괄적으로 처리 할 수 있다. ex. 포트폴리오 가치 평가시
2.1.1 Interface and requirements
여러가지의 상품들이 서로 불필요한 요건이 존재하기 때문에, 일반적인 공통 함수는 매우 적다.
returning its present value (possibly with an associated error estimate)
indicating whether or not the instrument has expired
필요에 따라 base class에서 무언가를 정의 할 필요도 있다.
이것은 (1)어떤 generic FI에서 기대되는 것을 분석하거나,
(2)generic 방법으로 instrument를 구현해야 하는지 확인을 하고 정의해야 한다.
(1)주어진 어떤 FI는 상속에 의존하지 않고 여러가지 방법으로 price되어야 한다.
(하나 이상의 analytic formulas or numerical methods를 가지고)
=> strategy pattern!
(2)시장 데이터에 따라 값이 바뀔수 있는 FI의 가치를 감시하는 요건이다.
2.1.2 Implementation
Observer pattern!
FI : ovserver
input data : observables
input data에 대해 smart pointer류를 사용하기를 제안함.(메모리 누수 방지, 자동 소멸 호출)
data feed가 바뀔 수도 있다. 즉, 두군데의 변화를 관찰해야 한다.
스마트 포인터는 포인팅된 객체의 현재 값에 접근할 수 있도록 도와주지만
포인터의 복사본은 다른 객체를 포인팅할 수 없다.
그러므로 smart equivalent of a pointer to pointer를 필요로 한다.
이것은 Quantlib에 class template으로 구현되어 있다. 이름은 Handle이다. : ※appendix A
Handle의 복사본들은 객체의 링크를 공유한다.
링크가 다른 객체에 포인팅되면, 모든 복사본들은 공지되어지고, 핸들을 가지고 있는 holder는 새로운 포인터에 접근할 수 있다.
또한 Handles는 포인팅된 객체로부터의 변화를 Observer들에게 알려준다.
observable data로 구현되는 클래스들은 Handle에 저장된다.
가장 기본적인 클래스는 Quote 클래스이다.
yield, volatility term structures와 같은 FI의 다른 입력변수들은 훨씬 더 복잡하다.
특정 계산을 구현하는 파생된 클래스를 그대로 둔 상태에서 데이터를 저장하고 저장된 결과를 재계산하기 위한 코드를 추상화하는 것은 또 다른 문제이다.
이것은 Template Method pattern를 이용하여 해결하였다.
초기 quantlib에는 instrument 클래스에 포함이 되었으나, 현재 LazyObject 클래스로 떨어져 나왔다.
calculated_ : 계산이 완료가 되었고 유효한지 알려준다.이것은 (1)어떤 generic FI에서 기대되는 것을 분석하거나,
(2)generic 방법으로 instrument를 구현해야 하는지 확인을 하고 정의해야 한다.
(1)주어진 어떤 FI는 상속에 의존하지 않고 여러가지 방법으로 price되어야 한다.
(하나 이상의 analytic formulas or numerical methods를 가지고)
=> strategy pattern!
(2)시장 데이터에 따라 값이 바뀔수 있는 FI의 가치를 감시하는 요건이다.
2.1.2 Implementation
Observer pattern!
FI : ovserver
input data : observables
input data에 대해 smart pointer류를 사용하기를 제안함.(메모리 누수 방지, 자동 소멸 호출)
data feed가 바뀔 수도 있다. 즉, 두군데의 변화를 관찰해야 한다.
스마트 포인터는 포인팅된 객체의 현재 값에 접근할 수 있도록 도와주지만
포인터의 복사본은 다른 객체를 포인팅할 수 없다.
그러므로 smart equivalent of a pointer to pointer를 필요로 한다.
이것은 Quantlib에 class template으로 구현되어 있다. 이름은 Handle이다. : ※appendix A
Handle의 복사본들은 객체의 링크를 공유한다.
링크가 다른 객체에 포인팅되면, 모든 복사본들은 공지되어지고, 핸들을 가지고 있는 holder는 새로운 포인터에 접근할 수 있다.
또한 Handles는 포인팅된 객체로부터의 변화를 Observer들에게 알려준다.
observable data로 구현되는 클래스들은 Handle에 저장된다.
가장 기본적인 클래스는 Quote 클래스이다.
yield, volatility term structures와 같은 FI의 다른 입력변수들은 훨씬 더 복잡하다.
특정 계산을 구현하는 파생된 클래스를 그대로 둔 상태에서 데이터를 저장하고 저장된 결과를 재계산하기 위한 코드를 추상화하는 것은 또 다른 문제이다.
이것은 Template Method pattern를 이용하여 해결하였다.
초기 quantlib에는 instrument 클래스에 포함이 되었으나, 현재 LazyObject 클래스로 떨어져 나왔다.
update : observer 인터페이스를 구현하고 calculated_를 false로 셋팅함으로써 직전의 결과를 무효로 만든다.
calculate : Template Method pattern으로 구현한다.
변하지 않는 부분(이 경우, 저장된 결과의 관리 부분)을 base 클래스로 구현한다.
그리고 다양한 부분(여기서는, 실제 계산하는 부분)은 virtual 함수(performCalculations)를 이용하여 위임한다. 위임된 함수는 base 클래스의 body에서 호출된다.
즉, 파생 클래스는 저장(caching)부분은 신경쓸 필요없이 자신의 특정 계산 부분만을 신경쓰면 된다.
the logic of the caching : 만약 현재 결과가 유효하지 않으면, 파생 클래스는 계산을 진행하고, 새로운 결과로 대체한다. 만약 현재의 결과가 유효하다면, 아무것도 하지 않으면 된다.
calculated_를 셋팅하는데 왜 try 블럭을 넣었어야했는지,
그리고 handler가 예외처리를 하기 전에 롤백하는 것에 대하여 궁금할 것이다.
다음과 같이 저장과 예외처리 부분을 제외하여 간단히 구현할 수 있다.
예를 들면, lazy 객체가 lazily하게 bootstrap하는 yield term structure인 경우에 performCalculations는 재귀적으로 calculate를 호출할 수 있다.
만약 calculated_가 true로 설정되어 있지 않다면, performCalculations를 다시 호출하게 될 것이며 이것은 무한 재귀호출을 하게 만들 것이다.
calculated_를 true로 설정하여 이러한 상황을 방지하며, 만약 예외가 발생하면 이것을 false로 복귀시켜야만 한다.
LazyObject에는 사용자가 결과를 재계산하는 것을 방지하거나 강요하게 되는 함수들이 몇 몇 더 있지만 여기서 다루지 않는다.("Read the source, Luke." by master Obi-Wan Kenobi)
Instrument 클래스는 LazyObject를 상속한다.
LazyObject의 인터페이스를 구현하기 위해 calculate 함수를 financial instrument에 특화된 코드로 decorate한다. 내용은 다음과 같다.
한번 더 말하지만, 추가된 코드는 instrument에 특화된 계산을 파생 클래스로 위임하기 위해 Template Method pattern을 사용한다.
데이터 멤버 NPV_는 계산된 결과값을 저장하기 위한 용도로 정의되었다.
파생 클래스는 특정 결과값(ex. errorEstimate_)을 저장하기 위한 다른 데이터 멤버들을 선언할 수 있다.
isExpired함수는 instrument가 만료가 되었는지를 확인한다. 만약 만료가 되었다면 setupExpired virtual 함수를 호출한다. 이 함수는 NPV_의 default 값으로 0을 설정하며 파생 클래스에 의해 호출된다.
만약 instrument가 만료가 되지 않았다면, LazyObject의 calculate함수가 호출된다. 이것은 performCalculations함수를 호출할 것이다.
마지막으로 NPV함수는 결과를 보내기 전에 calculate를 호출함으로써 한번더 확인한다.
2.1.3 Example: interest-rate swap
interest-rate swap은 주어진 기간동안 cash flow를 교환하는 계약이다.
instrument의 Net present value는 cash flow를 받는지 주는지에 따라 discounted cash-flow의 크기를 더하고 빼는 것에 의해 계산되어진다.
Swap은 Instrument로 부터 파생된 새로운 클래스로서 구현되며 다음과 같다.
계산에 필요한 데이터 멤버들을 포함한다: cash flows on the first and second leg, discount를 위한 yield term structure, 추가적인 결과를 저장하기 위한 두개의 변수
Instrument 인터페이스를 구현한 함수들과 swap-specific 결과의 반환값을 선언했다.
관련 클래스들과 Swap의 다이어그램은 다음과 같다.
이번 장의 나머지 부분에서는 클래스의 구현에 대해서 다룰 것이며, 관련된 함수는 다음과 같다 Instrument 프레임워크에 맞는 클래스는 세 단계를 거쳐 완성된다.
첫번째 단계는 클래스 생성자에서 수행되며 arguments로 취해지는데 이것은 대응되는 데이터 멤버들로 복사된다. 교환될 두 개의 cash flows와 이 둘의 크기를 discount하기위한 term structure가 있다.
이 단계에서 swap은 observer로써 cash flows와 term structure를 등록한다.
이것은 등록된 두 객체가 swap에게 변화에 대해 알려줄 수 있도록 만들며 변화가 발생할 때마다 다시 계산을 하도록 trigger된다.
두번째는 인터페이스의 구현 단계이다.
2.1.3 Example: interest-rate swap
interest-rate swap은 주어진 기간동안 cash flow를 교환하는 계약이다.
instrument의 Net present value는 cash flow를 받는지 주는지에 따라 discounted cash-flow의 크기를 더하고 빼는 것에 의해 계산되어진다.
Swap은 Instrument로 부터 파생된 새로운 클래스로서 구현되며 다음과 같다.
계산에 필요한 데이터 멤버들을 포함한다: cash flows on the first and second leg, discount를 위한 yield term structure, 추가적인 결과를 저장하기 위한 두개의 변수
Instrument 인터페이스를 구현한 함수들과 swap-specific 결과의 반환값을 선언했다.
관련 클래스들과 Swap의 다이어그램은 다음과 같다.
이번 장의 나머지 부분에서는 클래스의 구현에 대해서 다룰 것이며, 관련된 함수는 다음과 같다 Instrument 프레임워크에 맞는 클래스는 세 단계를 거쳐 완성된다.
첫번째 단계는 클래스 생성자에서 수행되며 arguments로 취해지는데 이것은 대응되는 데이터 멤버들로 복사된다. 교환될 두 개의 cash flows와 이 둘의 크기를 discount하기위한 term structure가 있다.
이 단계에서 swap은 observer로써 cash flows와 term structure를 등록한다.
이것은 등록된 두 객체가 swap에게 변화에 대해 알려줄 수 있도록 만들며 변화가 발생할 때마다 다시 계산을 하도록 trigger된다.
두번째는 인터페이스의 구현 단계이다.
isExpired 함수는 저장된 cash flows의 payment dates를 루핑돌며 검사한다.
아직 발생하지 않은 payment가 발견되는 즉시 swap이 아직 만료되지 않은 것으로 간주하고,
만약 아무것도 발견하지 못하면 해당 instrument는 만료된 것으로 간주되며 setupExpired 함수를 호출한다. 이것은 베이스 클래스에서 구현한다.
계산은 Cashflows 클래스로부터 두 개의 외부 함수를 호출하는 것에 의해 수행된다.
첫번째 함수는의 이름은 npv이며 cash flows를 보고 future cash flows의 discounted amount를 더한다. NPV_ 변수에 두 개의 legs로부터의 결과에 대한 차이를 설정한다.
두번째 함수는 bps이며 cash flows의 basis-point sensitivity(BPS)를 계산하며, 한 leg당 한번 호출하며, 대응되는 데이터 멤버에 결과를 저장한다.
결과가 수와 관련하여 에러가 없으면 errorEstimate_ 변수는 NULL<Real>()로 설정된다.
이것은 부동소수점 값으로 유효하지 않은 숫자를 가르키는 감시값으로써 사용된다.
마지막 단계는 클래스가 추가적인 결과를 정의하는 경우에만 수행한다.
저장된 결과가 리턴되기 전에 계산이 (lazily하게) 수행되는 것을 확인하기 위한 함수로 이루어진다.(이 경우에는 firstLegBPS와 secondLegBPS이다)
2.1.4 Further developments
앞선 Instrument 클래스에 부족한 점이 있다. 현재의 Swap 클래스는 다른 통화로써 지불되는 두 개의 legs에서의 interest-rate swaps을 관리 하지 못한다. 사용자는 두 개를 계산하기 전에 하나를 다른 하나의 통화 가치로 수동으로 변환시켜아 한다.
instrument나 cash flow의 가치를 표현하는데 Real 타입(간단한 부동소수점 수)를 사용하였다. 그러므로 실사용에 있어서 통화정보는 누락되어 있다.
이러한 약점은 Money 클래스를 이용하여 해결할 수 있다. 이러한 클래스의 인스턴스는 통화정보를 포함한다. 더욱이 사용자 설정과 관련하여 이것들은 자동으로 공통 통화로 변환하여 계산할수 있도록 한다.
이러한 개발은 todo list에 포함되어 있고 major한 변화이므로 1.x 혹은 2.0에 포함될 것이다.
또 다른 부족한 점은 Swap 클래스는 이것이 표현하는 추상화의 두개의 다른 컴포넌트를 명시적으로 구분하지 못한다. 말하자면, 계약(cash-flow 명세서)를 정의하는 데이터와 instrument(current discount curve)를 계산하는데 사용되는 시장 데이터를 명확히 구분하지 못한다.
해결방법은 첫 번째 데이터 그룹(term sheet에 있을)만 Instrument에 저장을 하고 시장 데이터는 다른 곳에 저장하도록한다.-(FpML:serialization and deserialization of the instrument) 다음 섹션에서 이 주제에 대해서 다룬다.
2.2 Pricing engines
어떠한 Instrument에서 유일한 pricing 방법이 언제나 존재하지 않는다.
어떤 경우에는 다양한 이유로 여러 방법을 사용하기를 원한다.
classic textbook 예제로 European equity option을 보자.
같은 한 사람이 가격을 구하기 위해
1. 시장 가격으로부터 내제 변동성을 구하여 블랙숄즈 공식을 이용할 수 있으며
2. 더욱 exotic한 옵션들을 위해 사용하거나 나중에 calibrate하기 위해 통계적인(stochastic) 변동성 모델을 이용할 수도 있으며
3. 유한 차분의 구현을 검증하고 분석한 것의 결과를 비교하기 위해 유한 차분 스키마를 사용할 수 있고
4. 더욱 exotic한 것을 위해 통제변수(control variate)로써 유로피언 옵션을 사용하기 위해 몬테 카를로 모델을 사용할 수도 있다.
그러므로 하나의 instrument가 여러가지 방법으로 가격을 구할수 있음을 알아야 한다.
performCalculations함수에 여러가지 구현을 하는것은 이것이 하나의 instrument type에 다른 클래스들을 사용하도록 강요하기 때문에 바람직하지 않다.
virtual calculate 함수가 엔진 인터페이스에 정의되어 있고 concrete 엔진들에 구현되어 있다는 것을 가정한다.결과가 수와 관련하여 에러가 없으면 errorEstimate_ 변수는 NULL<Real>()로 설정된다.
이것은 부동소수점 값으로 유효하지 않은 숫자를 가르키는 감시값으로써 사용된다.
마지막 단계는 클래스가 추가적인 결과를 정의하는 경우에만 수행한다.
저장된 결과가 리턴되기 전에 계산이 (lazily하게) 수행되는 것을 확인하기 위한 함수로 이루어진다.(이 경우에는 firstLegBPS와 secondLegBPS이다)
2.1.4 Further developments
앞선 Instrument 클래스에 부족한 점이 있다. 현재의 Swap 클래스는 다른 통화로써 지불되는 두 개의 legs에서의 interest-rate swaps을 관리 하지 못한다. 사용자는 두 개를 계산하기 전에 하나를 다른 하나의 통화 가치로 수동으로 변환시켜아 한다.
instrument나 cash flow의 가치를 표현하는데 Real 타입(간단한 부동소수점 수)를 사용하였다. 그러므로 실사용에 있어서 통화정보는 누락되어 있다.
이러한 약점은 Money 클래스를 이용하여 해결할 수 있다. 이러한 클래스의 인스턴스는 통화정보를 포함한다. 더욱이 사용자 설정과 관련하여 이것들은 자동으로 공통 통화로 변환하여 계산할수 있도록 한다.
이러한 개발은 todo list에 포함되어 있고 major한 변화이므로 1.x 혹은 2.0에 포함될 것이다.
또 다른 부족한 점은 Swap 클래스는 이것이 표현하는 추상화의 두개의 다른 컴포넌트를 명시적으로 구분하지 못한다. 말하자면, 계약(cash-flow 명세서)를 정의하는 데이터와 instrument(current discount curve)를 계산하는데 사용되는 시장 데이터를 명확히 구분하지 못한다.
해결방법은 첫 번째 데이터 그룹(term sheet에 있을)만 Instrument에 저장을 하고 시장 데이터는 다른 곳에 저장하도록한다.-(FpML:serialization and deserialization of the instrument) 다음 섹션에서 이 주제에 대해서 다룬다.
2.2 Pricing engines
어떠한 Instrument에서 유일한 pricing 방법이 언제나 존재하지 않는다.
어떤 경우에는 다양한 이유로 여러 방법을 사용하기를 원한다.
classic textbook 예제로 European equity option을 보자.
같은 한 사람이 가격을 구하기 위해
1. 시장 가격으로부터 내제 변동성을 구하여 블랙숄즈 공식을 이용할 수 있으며
2. 더욱 exotic한 옵션들을 위해 사용하거나 나중에 calibrate하기 위해 통계적인(stochastic) 변동성 모델을 이용할 수도 있으며
3. 유한 차분의 구현을 검증하고 분석한 것의 결과를 비교하기 위해 유한 차분 스키마를 사용할 수 있고
4. 더욱 exotic한 것을 위해 통제변수(control variate)로써 유로피언 옵션을 사용하기 위해 몬테 카를로 모델을 사용할 수도 있다.
그러므로 하나의 instrument가 여러가지 방법으로 가격을 구할수 있음을 알아야 한다.
performCalculations함수에 여러가지 구현을 하는것은 이것이 하나의 instrument type에 다른 클래스들을 사용하도록 강요하기 때문에 바람직하지 않다.
예를 들면, AnalyticEuropeanOption, McEuropeanOption 그리고 기타 다른 옵션들과 같은 것들이 파생될 수 있는 base인 EuropeanOption 클래스가 마지막이 되도록 할 것이다.
이것은 최소 두가지 측면에서 옳지 않다.
개념적인 측면에서 보면 하나가 필요할 때, 두 개의 입구를 제공하는 것이다: a European option is a European option is a European option, Gertrude Stein said.
사용측면에서는 런타임에 프라이싱 함수를 바꾸는 것이 불가능하게 된다.
해결 방법은 Strategy pattern을 사용하는 것이다. 즉 instrument가 수행될 계산로직을 object encapsulating하게 하는 것이다. 우리는 이러한 객체를 pricing engine이라 부른다.
주어진 instrument는 가능한 엔진중에 어떤 하나를 취할 수 있어야 하고(당연히 instrument type에 해당하는) , 선택된 엔진에 필요한 인자를 넘길수 있어야 하며, instrument의 가치를 계산한 값과 다른 원하는 수량등을 취할 수 있어 하고 해당 결과를 이용할 수 있어야 한다.
그러므로 performCalculations 함수는 간단히 다음과 같이 구현되어야 한다.
불행하게도 위의 접근은 여전히 부족한 점이 있다. 이상적으로 Instrument 클래스에서 오직 한번의 dispatching code를 구현하기를 원한다. 그러나 사용된 코드는 충분히 포괄적이지 않다.
이것은 명시된 수와 인자의 타입을 받아들이는 엔진에 의존하고 있기 때문이다.
그러나 instrument 클래스들은 수와 타입에 있어 다양한 데이터 멤버들을 갖기 때문에 이것은 명백히 받아들일 수 없다. 이것은 결과값 리턴에 있어서도 마찬가지이다. 예를 들면, interest-rate swap은 fixed rate와 floating spread의 fair value를 리턴할 수 있을 것이고 ubiquitous European option은 Greeks를 리턴할지도 모른다.
위에서 정의한 것과 같이 함수를 통하여 엔진에 인자를 전달하는 인터페이스는 생각하지 않은 두 가지 결과를 가져온다. 하나는 우리가 하나의 베이스 클래스를 정의하는 것에 방해가 되는 것으로 각각 다른 instruments를 위한 pricing 엔진이 각각 다른 인터페이스를 가지게 될 것이다. 다른 것은 엔진을 호출하는 코드가 각각의 instrument 클래스에서 복제(replicated)되어져야만 할 것이다. 이것은 미친짓이다.
우리가 선택한 해결방법은 인자와 결과들이 불투명한(opaque) 구조가 적절히 호출하는 방법으로 엔진으로부터 보내고 받아지는 것이다. 그것들(인자들과 결과들)로부터 파생되고 instrument에 특정한 데이터를 가지고 그것들(인자들과 결과들)을 호출하는 두 개의 구조는 어떤 pricing 엔진에서도 저장될수 있다; 어떤 instrument는 엔진과 정보를 교환하기 위해서 그러한 데이터를 읽고 쓸수 있다.
다음은 PricingEngine 클래스의 인터페이스와 내부의 argument, results 클래스 그리고 helper GenericEngine 클래스 템플릿을 보여주고 있다. GenericEngine은 PricingEngine 인터페이스의 대부분을 구현하고 있으며, 특정 엔진들의 개발자들을 위해 calculate 함수의 구현만을 남겨두고 있다. 데이터를 드롭박스처럼 쉽게 사용하게 할 수 있도록 argument와 results 클래스들이 함수에게 주어진다. arguments::validate는 input 데이터가 유효한 범위내에 있는 값인지 확인하기 위해 쓰여진 이후에 호출된다; results::reset은 엔진이 이전의 결과를 지우기 위해서 계산을 시작하기 전에 호출된다.
이러한 새로운 클래스를 갖춤으로서 generic performCalculation 함수를 완료할 수 있다.
먼저 언급한 Strategy pattern외에 특정한 instrument가 missing bits를 채우기 위해 Template Method pattern을 사용할 것이다. 이것은 다음과 같다.
inner 클래스 Instrument::result가 정의되는 것을 알 수 있을 것이다; 이러한 클래스는 PricingEngine::results로부터 상속하며 어떤 instrument에도 제공해야만 하는 결과를 포함한다.(Instrument::results 클래스는 pricing 엔진이 추가적인 결과를 저장할 수 있도록 std::map을 포함하며 이러한 부분은 명확성을 위해 여기서는 빠져있다.)
performCalculation 함수에 대해서 말하자면, 실제 작업은 다수의 collaborating 클래스들 사이에서 분리되는 것으로 보인다 - instrument, pricing 엔진, arguments/results 클래스들.
이러한 collaboration의 역학(dynamics)은 다음의 UML sequence 다이어그램을 이용하면 가장 이해하기 쉬울것이다; 그 다음은 클래스들(과 가능한 concrete instrument) 사이의 static한 관계를 보여준다.
*Sequence diagram of the interplay between instruments and pricing engines *Class diagram of Instrument, PricingEngine, and related classes including a derived instrument class
instrument의 NPV 함수를 호출하면 이것의 performCalculations 함수를 호출하게 된다.(만약 instrument가 만료가 되지않고, 관련된 quantities가 계산할 필요가 있다면)
이곳이 instrument와 pricing 엔진이 상호작용하기 시작하는 곳이다.
가장 먼저 instrument는 엔진이 이용가능한지 확인한다. 만약 가능하지 않다면 계산을 중지한다.
만약 가능한 것을 찾으면 instrument는 그것으로 즉시 리셋한다. 메시지는 reset 함수에 의해 instrument-specific result structure로 보내진다; 이것이 수행된 이후에 structure는 새로운 result를 쓰기위해 clean slate상태로 대기한다. 이 시점에서 Template Method pattern가 그 상황으로 들어간다. instrument는 arguments의 포인터로서 return되는 argument structure를 위해 pricing 엔진에 문의한다. 포인터는 패턴에서 변수 부분으로 동작하는 instrument의 setupArguments 함수에 보내진다. instrument에 따라 이러한 함수는 전달된 인자가 올바른 타입인지 확인하고 올바른 값으로 데이터 멤버를 채우도록 진행한다. 마지막으로 arguments는 validate 함수를 호출함으로서 새롭게 쓰여진 값들을 확인하는 작업이 수행되는 것에 대해 질문을 받게 된다.
이제 Strategy pattern을 적용할 차례이다.
인자들이 셋팅되고 선택된 엔진은 calculate 함수에 구현된 특정 계산이 수행될지를 선택하게 된다. 이것이 진행되는 동안 엔진은 argument structure로부터 필요한 인풋을 읽을(read) 것이고, results structure로 대응되는 결과를 작성할 것이다(wrtie).
엔진이 작업을 완료한 이후에 제어는 Instrument 인스턴스로 넘어올 것이고 Template Method pattern은 계속해서 unfolding을 할 것이다. 호출된 함수인 fetchResults는 결과를 엔진에게 문의해야만하며, 포함된 데이터로 접근하기 위해 downcast하고 자신의 데이터 멤버로 해당 값을 복사한다.
Instrument 클래스는 결과를 모든 instruments로 fetch하는 default 구현을 정의하고 있다; 파생된 클래스는 특정 결과를 읽기 위해 이것을 확장할 수 있다.
2.2.1 Example: plain-vanilla option
위에서 기술된 것들이 instrument의 구현에 어떻게 사용되는지 살펴보자. 이것은 어쩌면 기존의 것에서 새로운 클래스를 빌드하는 것을 더 쉽게 만들며 코드를 재사용할수 있게하는 많은 instruments의 공통 개념을 찾고 추상화하는 library writer의 작업과 상충할수 있다.
???
예를들면, plain-vanilla options(간단한 call put equity options with either European, American or Bermudan exercise)를 구현하는 QuantLib에 존재하는 클래스가 클래스 계층에서 가장 밑의 leaf에 존재한다. Instrument 클래스를 root로써 갖는 이러한 계층은 Option 클래스를 가지고 첫번째로 특화한다. 그리고 하나의 기초자산을 가지고 일반화하며 우리가 관심있는 VanillaOption 클래스를 정의할 때까지하나 혹은 둘의 클래스를 통하여 passing하는 OneAssetOption 클래스를 가지고 특화한다.
이러한 증식이 좋은 여러가지 이유가 있다. 예를들면 OneAssetOption 클래스의 코드는 Asian options로 재활용 될 수 있는 반면, Option 클래스의 코드는 모든 종류의 basket options를 구현할때 재사용된다. 불행히도 이것은 plain option을 pricing하는 코드가 상속 chain의 모든 멤버에 퍼져 있게 만든다. 이것은 극단적으로 명백한 예제로서 도움이 되지 않는다. 그러므로 타협을 해야만 한다. 이번 section에서 synthesized VanillaOption 클래스를 라이브러리에서의 어떤 것과 같은 구현을 가지고 설명할 것이다. 하지만 Instrument 클래스로부터 직접 상속할 것이다; 구현된 모든 클래스를 보여줄 것이며 상속보다는 구현으로 보여줄 것이다.
다음은 vanilla-option 클래스의 synthesized 인터페이스를 보여준다. Instrument 인터페이스에서 필요로 하는 함수와 옵션의 greeks라 불리는 추가적인 결과에 접근하는 접근자를 선언한다; 지난번 얘기했듯이 논리적으로 constant인 calculate 함수에서 값을 설정하기 위해 대응하는 데이터 멤버들은 mutable로 선언한다.
자신의 데이터와 함수들과 같이 VanillaOption은 특정 argument와 result structures 그리고 base pricing engine과 같은 accessory 클래스들을 선언한다.
그러한 클래스들은 그들과 option 클래스들의 관계를 강조하기 위해 inner 클래스들로 정의한다; 다음의 인터페이스에 나타난다. 이러한 accessory 클래스들에 두개의 코멘트를 붙일 수 있다.
첫번째는 예제의 소개부분에서 소개한 것과는 반대로 results 클래스에 모든 데이터 멤버를 inline하지 않았다. 구현의 디테일을 보여주기 위해 이렇게 했다. 이것은 사용자가 results와 관련되면서 공통으로 사용되는 것들을 갖으면서 structures를 정의하도록 할 것이다; 이러한 structures는 상속의 수단으로 재사용될 수 있으며 마지막 structure를 획득하기위한 Value를 가지고 구성되는 Greeks structure의 예제가 될수 있다. 이 경우에 Results로부터의 가상 상속은 악명높은 상속 다이아몬드를 피하도록 사용되어져야만 한다.
두번째는 (올바른 argument와 result types을 가지고 instantiated되는)클래스 템플릿 GenericEngine으로부터 상속하는 것이 특정 instrument의 pricing 엔진을 위한 베이스 클래스를 제공하기에 충분하다는 것이다. 우리는 파생된 클래스는 오직 그들의 calculate 함수만을 구현하면 된다는 것을 보게 될 것이다.
다음의 VanillaOption 클래스의 구현을 보자. 생성자는 instrument를 정의하는 몇개의 objects를 취하고 있다. 그들의 대부분은 다음의 챕터 혹은 appendix A에서 설명될 것이다. 당분간(For the time being) payoff는 strike와 right(i.e., call or put) of option를 포함할 것이며 exercise는 exercise date와 variety(i.e., European, American, or Bermudan.)을 포함하는 것으로 충분할 것이다. 보내진 인자는 대응되는 데이터 멤버에 저장될 것이다. 시장 데이터를 포함하지 않으며 다른 어딘가에 저장된다는 것을 기억하라.
expiration롸 관련된 함수는 굉장히 단순하다; isExpired 함수는 최근의 행사일이 지났는지 아닌지를 확인하며 setupExpired 함수는 base 클래스의 구현을 호출하고 instrument-specific 데이터를 0으로 설정한다.
setupArguments와 fetchResults 함수는 좀 더 흥미롭다. setupArguments는 generic argument 포인터를 필요로 하는 실제 타입으로 downcasting하고 다른 타입이 들어오면 예외를 발생시킨다. 그리고 나서 실제 작업을 시작한다. 몇 몇 경우 데이터 멤버는 대응되는 argument slots으로 그대로 복사(copied verbatim)된다. 그러나 이것은 같은 계산이 여러 개의 엔진에서 필요한 경우일 것이다; setupArguments 함수는 오직 한번 그것들을 write할 수 있는 장소를 제공한다.
fetchResults 함수는 setupArguments 함수의 dual(이중)이다(이중으로 되어있다.) 이것 역시 전달된 results 포인터를 downcasting하는 것으로 시작한다; 실제 타입을 검증한 후 자신의 데이터 멤버로 결과를 복사한다.
엔진은 다음과 같으며 European 옵션을 위한 analytic Black-Scholes-Merton 공식을 구현한다. 생성자는 present value, risk-free rate, dividend yield 그리고 volatility를 포함하는 기초자산(underlying)에 대한 시장 정보를 포함하는 Black-Scholes stochastic process를 자체로 등록하고 가진다. 실제 계산은 BlackCalculator 클래스라는 다른 클래스의 인터페이스 뒤쪽에 숨어 있다. 그러나 코드는 몇몇 관련된 것들을 보여주기에 충분히 자세하다.
함수는 몇몇 사전 조건들을 검증하는 것으로부터 시작한다. 이것은 계산의 인자가 이미 calculate함수가 호출하는 time에 의해 검증되었기 때문에 놀라움으로 느껴질지도 모른다. 그러나 주어진 엔진은 계산이 수행되기 전에 수행되어야할 더많은 요구사항들을 가질 수 있다. 우리의 엔진의 경우, 그러한 하나의 요구사항은 옵션이 유로피언 옵션이라는 것이고 payoff가 plain call/put(castdown이 필요하다)이라는 것이다.(boost::dynamic_pointer_cast is the equivalent of dynamic_cast for shared pointers.)
함수의 중간부분에서 엔진은 전달된 인자로부터 정형화되지 않은 정보를 추출한다. 여기서 보여지는 것은 기초자산의 spot price이다; 엔진에 의해 필요한 다른 quantities, e.g., 기초자산의 forward price와 만기시 risk-free discount factor 또한 추출된다.
마지막으로 계산이 수행되며, 결과는 대응되는 results structure의 slots에 저장된다.
이것으로 calculate 함수와 예제를 마친다.
2.2.2 Alternative designs
여전히 downcasting이 없어질수 있는지 궁금할수 있다; 이것은 이것의 가치보다 더 큰 대가를 필요로 할 수 있다.
특정 instrument가 정확한 타입의 인자를 전달받기 위해서 그리고 포괄적인 방법으로 관련 함수를 작성하기 위해서는 template programming과 같은 것이 절대적으로 필요하다; 더욱이 Template Method pattern의 사용은 polymorphism과 같은 것을 필요로 한다. Curiously Recurring Template pattern을 사용하기를 제안한다(파생 클래스(derived class)에서 기반 클래스(base class)의 기능을 재정의하여 기반 클래스의 구현을 파생클래스의 구현으로 대체하는 것을 함수 오버라이딩(function overriding)이라고 한다. 파생 클래스가 기반 클래스의 함수를 오버라이드하기 위해서 일반적으로 가상 함수(virtual function)를 사용하는데 여기에는 두가지 단점이 있다.
1) vtable(virtual function table)을 위한 추가 공간이 필요하고 2) 함수 호출 과정이 vtable을 거쳐 함수 포인터를 참조하므로 오버헤드가 증가한다. 만약 컴파일 타임에 정적으로 기반 클래스의 함수를 오버라이드할 수 있다면 가상 함수를 사용할 때의 오버헤드를 줄일 수 있을 것이다. 템플릿을 사용하여 정적으로 오버라이딩할 수 있는 기법이 바로 Curiously Recurring Template Pattern (CRTP) 이다.); 이것의 가능한 구현은 다음과 같다.
GenericEngine 클래스를 수정해서 inspectors들이 불투명한 base 클래스들 대신에, 특정인자나 result structures를 리턴하게 만들수 있다. 이것은 downcasting의 필요성을 제거할 수 있다; 그러나 이것은 instrument가 타입을 정확히 맞춰야 하도록 강요한다.
그러므로 특정 argument와 results 타입은 Instrument 클래스에 특정 instrument 타입과 함께 template argument로 전달된다. 더이상 Instrument 인터페이스에 선언되지 않는 setupArguments 함수와 이것의 dual fetchResults 함수는 제외하고 대부분의 클래스는 변하지 않은 상태로 남게 된다. 이런 이유로 performCalculations 함수의 body는 반드시 수정되어야 한다: this pointer는 반드시 특정 instrument 타입으로 정적 cast되어야만 한다. 그래서 이러한 함수를 호출하는 것은 제한되어야한다(can be bound). 마지막으로 instrument의 개발자는 cycle을 close해야만 한다: 특정 instrument는 Instrument로부터 상속되어져야만 하며 같은 클래스로 template argument로써 전달되야 한다. 그리고 필요한 함수는 반드시 정의되어야만 한다. 또한 argument와 result structures는 template instantiation 시점에서 이용가능하도록 하기 위해 instrument 이전에 선언되어야만 한다.
비록 동작할지라도 이 구현은 실제로 장점을 제공하는 것 같지 않다. 이것은 오히려 새로운 instruments를 생성하는데 있어 개발자를 더욱 번거롭게 만든다. 또한 이것은 template cycle이closed될 때 클래스로부터 파생되는 것을 불가능하게 만든다; 예를 들면 파생된 fixed-coupon bond 클래스와 자체로 사용가능한 bond class(bond class usable on its own) 이 두개를 코딩하는 것으로부터 한개를 방해할지도 모른다. 이러한 측면에서 현재 Instrument의 구현은 변하지 않고 그대로 두는 경향이 있다.
'quantlib > Implementation' 카테고리의 다른 글
class template & Function Template (0) | 2011.06.15 |
---|---|
Aside: impure virtual methods (0) | 2011.06.15 |
Aside: handles and shared pointers (0) | 2011.06.11 |
Aside: Const or not const? (0) | 2011.06.09 |
1. Introduction (0) | 2011.06.06 |