若是未作特別說明,文中的程序都是 C++11 代碼。算法
1976 年,Merton 最先在衍生品訂價中引入並分析了跳擴散過程,正由於如此 QuantLib 中和跳擴散相關的隨機過程類稱之爲 Merton76Process
,一個通常的跳擴散過程能夠由下面的 SDE 描述,app
\[ \frac{dS(t)}{S(t-)} = \mu dt + \sigma dW(t) + dJ(t)\\ J(t) = \sum_{j=1}^{N(t)}(Y_j-1) \]框架
其中 \(Y_j\) 是隨機變量,\(N(t)\) 是計數過程。\(dJ(t)\) 表示 \(J(t)\) 在 \(t\) 時刻發生的跳躍幅度,若是發生一次跳,則幅度爲 \(Y_j-1\);若是沒有發生跳,則幅度爲 0。dom
在應用於衍生品訂價時,須要對上述 SDE 中的項作出一些特殊約定,常見的約定有:ide
令 \(X(t) = \log S(t)\),那麼函數
\[ \begin{aligned} X(t_{i+1}) = & X(t_i) + \left(\mu - \frac12 \sigma^2 \right)(t_{i+1} - t_i)\\ & +\sigma[W(t_{i+1}) - W(t_i)] + \sum_{j = N(t_i)+1}^{N(t_{i+1})}\log Y_j \end{aligned} \]ui
在 \(X(t_i)\) 的基礎上模擬 \(X(t_{i+1})\) 的算法以下:編碼
那麼spa
\[ \begin{aligned} S_{t_{i+1}} =& S_{t_i} e^{(r-\lambda m -\frac{1}{2}\sigma^{2})\Delta t+ \sigma Z \sqrt{\Delta t} + M}\\ =& S_{t_i} e^{(r-\frac{1}{2}\sigma^{2})\Delta t+ \sigma Z \sqrt{\Delta t}}e^{(-\lambda m)\Delta t+M} \end{aligned} \]
其中,\(\Delta t = t_{i+1} - t_i\),而 \(e^{(-\lambda m)\Delta t+M}\) 是跳擴散相對於通常 Black-Scholes-Merton 過程的修正項。
目前 QuantLib 中提供的 Merton76Process
類只能配合「解析訂價引擎」使用,自己不具有模擬隨機過程路徑的功能。究其緣由,問題出在 QuantLib 的編碼約定和 StochasticProcess1D
提供的接口兩方面:
StochasticProcess
派生出的子類僅能描述 SDE 的結構信息,也就是 SDE 的參數、漂移和擴散項的函數形式,子類不攜帶有關隨機數生成的信息,全部隨機數生成的相關信息均由 Monte Carlo 框架中其餘組件控制;evolve
方法,StochasticProcess1D
提供的接口是 evolve(Time t0, Real x0, Time dt, Real dw)
。若是 Merton76Process
按約定實現 evolve
方法的話,形式必須是 evolve(Time t0, Real x0, Time dt, const Array &dw)
,由於模擬跳須要額外的隨機性,因此 dw
必須是一個 Array
。很明顯,不匹配。在不改變當前接口的前提下,若要實現模擬跳擴散過程,須要用比較「髒」一點兒的手段,即打破約定,讓隨機過程類攜帶一個隨機數發生器,爲模擬跳提供額外的隨機性。
具體來講,須要聲明一個 Merton76Process
的派生類,該類攜帶一個高斯隨機數發生器。由於從數學上來說跳擴散過程推廣自通常 Black-Scholes-Merton 過程,添加了一個修正項,因此遵循「適配器模式」(或「裝飾器模式」)的思想,絕大部分計算能夠委託給一個 BlackScholesMertonProcess
對象,僅須要對 drift
和 evolve
方法做必要的修改。
固然,「乾淨」的方法要改變當前接口:
StochasticProcess1D
平行的新類 StochasticProcess1DJump
,兩者惟一的區別是 evolve
方法,在 StochasticProcess1DJump
中形式是 evolve(Time t0, Real x0, Time dt, const Array &dw)
;Merton76Process
改爲繼承自 StochasticProcess1DJump
。下面的代碼實現了前面提到的「髒」的方法,由於隨機數發生器的種類有不少,且沒有基類提供統一的接口,因此使用了模板技術讓類能夠接受不一樣類型的隨機數發生器。同時,許多計算被委託給了一個 BlackScholesMertonProcess
對象。
#ifndef MERTON76JUMPDIFFUSIONPROCESS_HPP #define MERTON76JUMPDIFFUSIONPROCESS_HPP #include <ql/math/distributions/normaldistribution.hpp> #include <ql/math/distributions/poissondistribution.hpp> #include <ql/math/randomnumbers/boxmullergaussianrng.hpp> #include <ql/math/randomnumbers/mt19937uniformrng.hpp> #include <ql/processes/blackscholesprocess.hpp> #include <ql/processes/merton76process.hpp> namespace QuantLib { template<typename GAUSS_RNG> class Merton76JumpDiffusionProcess : public Merton76Process { public: Merton76JumpDiffusionProcess(const Handle<Quote>& stateVariable, const Handle<YieldTermStructure>& dividendTS, const Handle<YieldTermStructure>& riskFreeTS, const Handle<BlackVolTermStructure>& blackVolTS, const Handle<Quote>& jumpInt, const Handle<Quote>& logJMean, const Handle<Quote>& logJVol, const GAUSS_RNG& gauss_rng, const ext::shared_ptr<discretization>& disc = ext::shared_ptr<discretization>(new EulerDiscretization)) : Merton76Process( stateVariable, dividendTS, riskFreeTS, blackVolTS, jumpInt, logJMean, logJVol, disc) , blackProcess_( new BlackScholesMertonProcess( stateVariable, dividendTS, riskFreeTS, blackVolTS, disc)) , gauss_rng_(gauss_rng) { } virtual ~Merton76JumpDiffusionProcess() {} Real x0() const { return blackProcess_->x0(); } Time time(const Date& d) const { return blackProcess_->time(d); } Real diffusion(Time t, Real x) const { return blackProcess_->diffusion(t, x); } Real apply(Real x0, Real dx) const { return blackProcess_->apply(x0, dx); } Size factors() const { return 1; } Real drift(Time t, Real x) const { Real lambda_ = Merton76Process::jumpIntensity()->value(); Real delta_ = Merton76Process::logJumpVolatility()->value(); Real nu_ = Merton76Process::logMeanJump()->value(); Real m_ = std::exp(nu_ + 0.5 * delta_ * delta_) - 1; return blackProcess_->drift(t, x) - lambda_ * m_; } Real evolve(Time t0, Real x0, Time dt, Real dw) const; private: const CumulativeNormalDistribution cumNormalDist_; ext::shared_ptr<GeneralizedBlackScholesProcess> blackProcess_; GAUSS_RNG gauss_rng_; }; template<typename GAUSS_RNG> Real Merton76JumpDiffusionProcess<GAUSS_RNG>::evolve(Time t0, Real x0, Time dt, Real dw) const { Real lambda_ = Merton76Process::jumpIntensity()->value(); Real delta_ = Merton76Process::logJumpVolatility()->value(); Real nu_ = Merton76Process::logMeanJump()->value(); Real m_ = std::exp(nu_ + 0.5 * delta_ * delta_) - 1; Real p = cumNormalDist_(gauss_rng_.next().value); if (p < 0.0) p = 0.0; else if (p >= 1.0) p = 1.0 - QL_EPSILON; Real j = gauss_rng_.next().value; const Real n = InverseCumulativePoisson(lambda_ * dt)(p); Real retVal = blackProcess_->evolve( t0, x0, dt, dw); retVal *= std::exp(-lambda_ * m_ * dt + nu_ * n + delta_ * std::sqrt(n) * j); return retVal; } } #endif // MERTON76JUMPDIFFUSIONPROCESS_HPP
下面模擬兩條曲線
#include <iostream> #include <ql/math/randomnumbers/boxmullergaussianrng.hpp> #include <ql/math/randomnumbers/mt19937uniformrng.hpp> #include <ql/processes/blackscholesprocess.hpp> #include <ql/quotes/simplequote.hpp> #include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp> #include <ql/termstructures/yield/flatforward.hpp> #include <ql/time/calendars/target.hpp> #include <ql/time/date.hpp> #include <ql/time/daycounters/actualactual.hpp> #include "Merton76JumpDiffusionProcess.hpp" int main() { using namespace std; using namespace QuantLib; Date refDate = Date(27, Mar, 2019); Rate riskFreeRate = 0.03; Rate dividendRate = 0.01; Real spot = 52.0; Rate vol = 0.2; Calendar cal = TARGET(); DayCounter dc = ActualActual(); ext::shared_ptr<YieldTermStructure> rdStruct( new FlatForward(refDate, riskFreeRate, dc)); ext::shared_ptr<YieldTermStructure> rqStruct( new FlatForward(refDate, dividendRate, dc)); Handle<YieldTermStructure> rdHandle(rdStruct); Handle<YieldTermStructure> rqHandle(rqStruct); ext::shared_ptr<SimpleQuote> spotQuote(new SimpleQuote(spot)); Handle<Quote> spotHandle(spotQuote); ext::shared_ptr<BlackVolTermStructure> volQuote( new BlackConstantVol(refDate, cal, vol, dc)); Handle<BlackVolTermStructure> volHandle(volQuote); // Specify the jump intensity, jump mean and // jump volatility objects Real jumpIntensity = 0.2; // lambda Real jumpVolatility = 0.3; Real jumpMean = 0.0; ext::shared_ptr<SimpleQuote> jumpInt(new SimpleQuote(jumpIntensity)); ext::shared_ptr<SimpleQuote> jumpVol(new SimpleQuote(jumpVolatility)); ext::shared_ptr<SimpleQuote> jumpMn(new SimpleQuote(jumpMean)); Handle<Quote> jumpI(jumpInt), jumpV(jumpVol), jumpM(jumpMn); ext::shared_ptr<BlackScholesMertonProcess> bsmProcess( new BlackScholesMertonProcess( spotHandle, rqHandle, rdHandle, volHandle)); unsigned long seed = 12324u; MersenneTwisterUniformRng unifMt(seed); MersenneTwisterUniformRng unifMtJ(25u); typedef BoxMullerGaussianRng<MersenneTwisterUniformRng> GAUSS; GAUSS bmGauss(unifMt); GAUSS jGauss(unifMtJ); ext::shared_ptr<Merton76JumpDiffusionProcess<GAUSS>> mtProcess( new Merton76JumpDiffusionProcess<GAUSS>( spotHandle, rqHandle, rdHandle, volHandle, jumpI, jumpM, jumpV, jGauss)); Time dt = 0.004, t = 0.0; Real x = spotQuote->value(); Real y = spotQuote->value(); Real dw; Size numVals = 250; std::cout << "Time, Jump, NoJump" << std::endl; std::cout << t << ", " << x << ", " << y << std::endl; for (Size j = 1; j <= numVals; ++j) { dw = bmGauss.next().value; x = mtProcess->evolve(t, x, dt, dw); y = bsmProcess->evolve(t, y, dt, dw); std::cout << t + dt << ", " << x << ", " << y << std::endl; t += dt; } return EXIT_SUCCESS; }