QuantLib:Black / BSM 通過波動率表面處理和定價。結果不一樣?
我從幾個 C++ 函式開始這個問題,這將有助於顯示一些結果。因此,啟動您的Visual Studio C++ Express或Ceemple或任何您想要的,然後複製並粘貼:
#include <ql/quantlib.hpp> #include <boost/timer.hpp> #include <iostream> #include <iomanip> using namespace QuantLib; #if defined(QL_ENABLE_SESSIONS) namespace QuantLib { Integer sessionId() { return 0; } } #endif
標準引入後,第一個函式就像一個小包裝器:取
shared_ptr
模板BlackVolTermStructure
和一些數據,建構零平坦的無風險利率曲線並建構 aBlackProcess
;因此返回選項 NPV。double EurVanillaSurfacePricerBlack(boost::shared_ptr<BlackVolTermStructure> forwardVolSurface, Option::Type type, Real underlying, Real strike, Date maturity) { Rate riskFreeRate = 0.00; DayCounter dayCounter = Actual365Fixed(); Calendar calendar = TARGET(); Natural settlementDays = 3; // exercise boost::shared_ptr<Exercise> europeanExercise( new EuropeanExercise(maturity)); // underlying Handle<Quote> underlyingH(boost::shared_ptr<Quote>( new SimpleQuote(underlying))); // bootstrap the yield curve Handle<YieldTermStructure> flatTermStructure( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDays, calendar, riskFreeRate, dayCounter))); // payoff boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(type, strike)); // process boost::shared_ptr<BlackProcess> blackProcess( new BlackProcess(underlyingH, flatTermStructure, Handle<BlackVolTermStructure>(forwardVolSurface))); // options VanillaOption europeanOption(payoff, europeanExercise); europeanOption.setPricingEngine(boost::shared_ptr<PricingEngine>( new AnalyticEuropeanEngine(blackProcess))); double optionValue = europeanOption.NPV(); return(optionValue); }
由於我們不再使用基礎遠期價格,因此可以使用
BlackScholesMertonProcess
而不是 指定股息收益率期限結構來編寫相同的函式……但是在這裡我們將無風險利率和股息收益率都設置為零 - 因此給出BlackProcess
函式扁平且無意義的術語結構:double EurVanillaSurfacePricerBSM(boost::shared_ptr<BlackVolTermStructure> forwardVolSurface, Option::Type type, Real underlying, Real strike, Date maturity) { Spread dividendYield = 0.00; Rate riskFreeRate = 0.00; DayCounter dayCounter = Actual365Fixed(); Calendar calendar = TARGET(); Natural settlementDays = 3; // exercise boost::shared_ptr<Exercise> europeanExercise( new EuropeanExercise(maturity)); // underlying Handle<Quote> underlyingH(boost::shared_ptr<Quote>( new SimpleQuote(underlying))); // bootstrap the yield curve and the dividend curve Handle<YieldTermStructure> flatTermStructure( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDays, calendar, riskFreeRate, dayCounter))); Handle<YieldTermStructure> flatDividendTS( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDays, calendar, dividendYield, dayCounter))); // payoff boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(type, strike)); // process boost::shared_ptr<BlackScholesMertonProcess> bsmProcess( new BlackScholesMertonProcess(underlyingH, flatDividendTS, flatTermStructure, Handle<BlackVolTermStructure>(forwardVolSurface))); // options VanillaOption europeanOption(payoff, europeanExercise); europeanOption.setPricingEngine(boost::shared_ptr<PricingEngine>( new AnalyticEuropeanEngine(bsmProcess))); double optionValue = europeanOption.NPV(); return(optionValue); }
根據我所知道的一點期權理論,這兩個函式之間應該沒有任何區別:
$$ F(t)=S(0)e^{[r(t)-q(t)]t}, $$ 在哪裡 $ q $ 和 $ r $ 因此對於每個到期日都是零 $ F=S(0) $ 對於每一個成熟。 第三個函式是在同一主題上的一點變化:我們不使用需要期限結構的標準建構子,而是將恆定波動率值插入平面:
double EurVanillaPricer(Volatility volatility, Option::Type type, Real underlying, Real strike, Date maturity) { Spread dividendYield = 0.00; Rate riskFreeRate = 0.00; DayCounter dayCounter = Actual365Fixed(); Calendar calendar = TARGET(); // This was "Natural settlementDays = 3;" before Luigi Ballabio's correction Natural settlementDays = 0; // exercise boost::shared_ptr<Exercise> europeanExercise( new EuropeanExercise(maturity)); // underlying Handle<Quote> underlyingH(boost::shared_ptr<Quote>( new SimpleQuote(underlying))); // bootstrap the yield/dividend/vol curves Handle<YieldTermStructure> flatTermStructure( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDays, calendar, riskFreeRate, dayCounter))); Handle<YieldTermStructure> flatDividendTS( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDays, calendar, dividendYield, dayCounter))); Handle<BlackVolTermStructure> flatVolTS( boost::shared_ptr<BlackVolTermStructure>( new BlackConstantVol(settlementDays, calendar, volatility, dayCounter))); // payoff boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(type, strike)); // process boost::shared_ptr<BlackScholesMertonProcess> bsmProcess( new BlackScholesMertonProcess(underlyingH, flatDividendTS, flatTermStructure, flatVolTS)); // options VanillaOption europeanOption(payoff, europeanExercise); europeanOption.setPricingEngine(boost::shared_ptr<PricingEngine>( new AnalyticEuropeanEngine(bsmProcess))); double optionValue = europeanOption.NPV(); return(optionValue); }
最後但同樣重要的是,讓我介紹一個正向波動率表麵包裝器:
boost::shared_ptr<BlackVolTermStructure> ForwardImpliedVolSurface(Date todaysDate, Date forwardDate, Calendar calendar, std::vector<Date> maturityArray, std::vector<Real> strikeArray, Matrix volatilityMatrix) { // Handle to boost::shared_ptr DayCounter dayCounter = Actual365Fixed(); boost::shared_ptr<BlackVarianceSurface> volatilitySurface(new BlackVarianceSurface(todaysDate, calendar, maturityArray, strikeArray, volatilityMatrix, dayCounter)); Handle<BlackVolTermStructure> volatilitySurfaceH(volatilitySurface); // Volatility surface interpolation volatilitySurface->enableExtrapolation(true); // Change interpolator to bicubic splines volatilitySurface->setInterpolation<Bicubic>(Bicubic()); // Forward implied volatility surface boost::shared_ptr<BlackVolTermStructure> forwardVolSurface(new ImpliedVolTermStructure(volatilitySurfaceH, forwardDate)); return(forwardVolSurface); }
這些函式的輸出是什麼?讓我們看看:
int main() { try { boost::timer timer; std::cout << std::endl; /* +--------------------------------------------------------------------------------------------------- * | Date and calendars parameters * +--------------------------------------------------------------------------------------------------- * */ // set up dates Calendar calendar = TARGET(); Date todaysDate(03, Jul, 2014); Date settlementDate = calendar.advance(todaysDate, 3, Days); Settings::instance().evaluationDate() = todaysDate; // Maturity dates array Date expiry1(15, Aug, 2014); Date expiry2(19, Sep, 2014); Date expiry3(19, Dec, 2014); Date expiry4(20, Mar, 2015); Date expiry5(19, Jun, 2015); std::vector<Date> maturityArray; maturityArray.push_back(expiry1); maturityArray.push_back(expiry2); maturityArray.push_back(expiry3); maturityArray.push_back(expiry4); maturityArray.push_back(expiry5); // Strikes array std::vector<Real> strikeArray; for(int i = 2975; i < 2975 + (26 * 25); i = i + 25) { strikeArray.push_back(i); } // Implied volatility matrix Matrix volatilityMatrix(26, 5); volatilityMatrix[0][0] = 0.198989 ; volatilityMatrix[0][1] = 0.182889 ; volatilityMatrix[0][2] = 0.182256 ; volatilityMatrix[0][3] = 0.183319 ; volatilityMatrix[0][4] = 0.202197 ; volatilityMatrix[1][0] = 0.192338 ; volatilityMatrix[1][1] = 0.178463 ; volatilityMatrix[1][2] = 0.17982 ; volatilityMatrix[1][3] = 0.181494 ; volatilityMatrix[1][4] = 0.201261 ; volatilityMatrix[2][0] = 0.185184 ; volatilityMatrix[2][1] = 0.174239 ; volatilityMatrix[2][2] = 0.177315 ; volatilityMatrix[2][3] = 0.179669 ; volatilityMatrix[2][4] = 0.200291 ; volatilityMatrix[3][0] = 0.178718 ; volatilityMatrix[3][1] = 0.170046 ; volatilityMatrix[3][2] = 0.175143 ; volatilityMatrix[3][3] = 0.177845 ; volatilityMatrix[3][4] = 0.19928 ; volatilityMatrix[4][0] = 0.172647 ; volatilityMatrix[4][1] = 0.166123 ; volatilityMatrix[4][2] = 0.172826 ; volatilityMatrix[4][3] = 0.176046 ; volatilityMatrix[4][4] = 0.198271 ; volatilityMatrix[5][0] = 0.166556 ; volatilityMatrix[5][1] = 0.162275 ; volatilityMatrix[5][2] = 0.170328 ; volatilityMatrix[5][3] = 0.174391 ; volatilityMatrix[5][4] = 0.19764 ; volatilityMatrix[6][0] = 0.160933 ; volatilityMatrix[6][1] = 0.158344 ; volatilityMatrix[6][2] = 0.16825 ; volatilityMatrix[6][3] = 0.172892 ; volatilityMatrix[6][4] = 0.197454 ; volatilityMatrix[7][0] = 0.155747 ; volatilityMatrix[7][1] = 0.154688 ; volatilityMatrix[7][2] = 0.166199 ; volatilityMatrix[7][3] = 0.17105 ; volatilityMatrix[7][4] = 0.196211 ; volatilityMatrix[8][0] = 0.150464 ; volatilityMatrix[8][1] = 0.151097 ; volatilityMatrix[8][2] = 0.164325 ; volatilityMatrix[8][3] = 0.16875 ; volatilityMatrix[8][4] = 0.193533 ; volatilityMatrix[9][0] = 0.145234 ; volatilityMatrix[9][1] = 0.147602 ; volatilityMatrix[9][2] = 0.16217 ; volatilityMatrix[9][3] = 0.16793 ; volatilityMatrix[9][4] = 0.195104 ; volatilityMatrix[10][0] = 0.140751 ; volatilityMatrix[10][1] = 0.144357 ; volatilityMatrix[10][2] = 0.160261 ; volatilityMatrix[10][3] = 0.169107 ; volatilityMatrix[10][4] = 0.202441 ; volatilityMatrix[11][0] = 0.136502 ; volatilityMatrix[11][1] = 0.141208 ; volatilityMatrix[11][2] = 0.158546 ; volatilityMatrix[11][3] = 0.165058 ; volatilityMatrix[11][4] = 0.194346 ; volatilityMatrix[12][0] = 0.13342 ; volatilityMatrix[12][1] = 0.138357 ; volatilityMatrix[12][2] = 0.156949 ; volatilityMatrix[12][3] = 0.15057 ; volatilityMatrix[12][4] = 0.155503 ; volatilityMatrix[13][0] = 0.104896 ; volatilityMatrix[13][1] = 0.119273 ; volatilityMatrix[13][2] = 0.128517 ; volatilityMatrix[13][3] = 0.136208 ; volatilityMatrix[13][4] = 0.116855 ; volatilityMatrix[14][0] = 0.10099 ; volatilityMatrix[14][1] = 0.115047 ; volatilityMatrix[14][2] = 0.125638 ; volatilityMatrix[14][3] = 0.132476 ; volatilityMatrix[14][4] = 0.109273 ; volatilityMatrix[15][0] = 0.100313 ; volatilityMatrix[15][1] = 0.114395 ; volatilityMatrix[15][2] = 0.125642 ; volatilityMatrix[15][3] = 0.133834 ; volatilityMatrix[15][4] = 0.117099 ; volatilityMatrix[16][0] = 0.0981065 ; volatilityMatrix[16][1] = 0.112273 ; volatilityMatrix[16][2] = 0.124137 ; volatilityMatrix[16][3] = 0.132863 ; volatilityMatrix[16][4] = 0.118885 ; volatilityMatrix[17][0] = 0.0962976 ; volatilityMatrix[17][1] = 0.109955 ; volatilityMatrix[17][2] = 0.122498 ; volatilityMatrix[17][3] = 0.130647 ; volatilityMatrix[17][4] = 0.116549 ; volatilityMatrix[18][0] = 0.0950343 ; volatilityMatrix[18][1] = 0.107924 ; volatilityMatrix[18][2] = 0.121311 ; volatilityMatrix[18][3] = 0.129627 ; volatilityMatrix[18][4] = 0.116142 ; volatilityMatrix[19][0] = 0.094729 ; volatilityMatrix[19][1] = 0.106211 ; volatilityMatrix[19][2] = 0.119952 ; volatilityMatrix[19][3] = 0.12918 ; volatilityMatrix[19][4] = 0.116873 ; volatilityMatrix[20][0] = 0.0952533 ; volatilityMatrix[20][1] = 0.104712 ; volatilityMatrix[20][2] = 0.118585 ; volatilityMatrix[20][3] = 0.128231 ; volatilityMatrix[20][4] = 0.116804 ; volatilityMatrix[21][0] = 0.0977423 ; volatilityMatrix[21][1] = 0.103553 ; volatilityMatrix[21][2] = 0.117229 ; volatilityMatrix[21][3] = 0.126978 ; volatilityMatrix[21][4] = 0.116249 ; volatilityMatrix[22][0] = 0.0992171 ; volatilityMatrix[22][1] = 0.102743 ; volatilityMatrix[22][2] = 0.115987 ; volatilityMatrix[22][3] = 0.125834 ; volatilityMatrix[22][4] = 0.115905 ; volatilityMatrix[23][0] = 0.102137 ; volatilityMatrix[23][1] = 0.1025 ; volatilityMatrix[23][2] = 0.114716 ; volatilityMatrix[23][3] = 0.124794 ; volatilityMatrix[23][4] = 0.115759 ; volatilityMatrix[24][0] = 0.108426 ; volatilityMatrix[24][1] = 0.102351 ; volatilityMatrix[24][2] = 0.113496 ; volatilityMatrix[24][3] = 0.123768 ; volatilityMatrix[24][4] = 0.115648 ; volatilityMatrix[25][0] = 0.111779 ; volatilityMatrix[25][1] = 0.102869 ; volatilityMatrix[25][2] = 0.112514 ; volatilityMatrix[25][3] = 0.12274 ; volatilityMatrix[25][4] = 0.11554 ; /* +--------------------------------------------------------------------------------------------------- * | Forward volatility (ref. pag. 154-157 of "Dynamic Hedging - Managing Vanilla and Exotic Options") * +--------------------------------------------------------------------------------------------------- * */ // As instance, go 15 days forward Date forwardDate = calendar.advance(todaysDate, 15, Days); boost::shared_ptr<BlackVolTermStructure> forwardVolSurface = ForwardImpliedVolSurface(todaysDate, forwardDate, calendar, maturityArray, strikeArray, volatilityMatrix); Option::Type typeCall(Option::Call); Option::Type typePut(Option::Put); Real underlying = 3289.75; double myOption4; myOption4 = EurVanillaSurfacePricerBlack(forwardVolSurface, typeCall, underlying, 3300, expiry3); //disp(myOption4); double myOption5; myOption5 = EurVanillaSurfacePricerBSM(forwardVolSurface, typeCall, underlying, 3300, expiry3); //disp(myOption5); double myOption6; myOption6 = EurVanillaPricer(forwardVolSurface->blackVol(expiry3, 3300), typeCall, underlying, 3300, expiry3); //disp(myOption6); // Amend evaluation date... Settings::instance().evaluationDate() = forwardDate; double myOption1; myOption1 = EurVanillaSurfacePricerBlack(forwardVolSurface, typeCall, underlying, 3300, expiry3); //disp(myOption1); double myOption2; myOption2 = EurVanillaSurfacePricerBSM(forwardVolSurface, typeCall, underlying, 3300, expiry3); //disp(myOption2); double myOption3; myOption3 = EurVanillaPricer(forwardVolSurface->blackVol(expiry3, 3300), typeCall, underlying, 3300, expiry3); //disp(myOption3); return 0; } catch (std::exception& e) { std::cerr << e.what() << std::endl; return 1; } catch (...) { std::cerr << "unknown error" << std::endl; return 1; } }
輸出是什麼?好吧,當然 Black 模型和 BSM 模型具有扁平和空項結構返回相同的值,即 $ 105.743 $ 對於這兩個選項。儘管如此,然而,通過從隱含波動率表面“手動”選擇要使用的隱含波動率會
forwardVolSurface->blackVol(expiry3, 3300)
返回一個非常不同的值,即 $ 103.858 $ .您將如何解釋這種差異?
我怕的是……修正評估日期時波動面的自動移動。我試圖編寫程式碼來避免這種將可重新連結的句柄保留到函式中的行為,這樣一旦函式結束它們就會被銷毀。
但我不確定它是否按預期工作。
這是因為您在初始化平坦波動率曲線時經過的結算天數。您正在創建現貨、遠期和平坦的波動率:
boost::shared_ptr<BlackVarianceSurface> volatilitySurface( new BlackVarianceSurface(todaysDate, calendar, maturityArray, strikeArray, volatilityMatrix, dayCounter)); boost::shared_ptr<BlackVolTermStructure> forwardVolSurface( new ImpliedVolTermStructure(volatilitySurfaceH, forwardDate)); boost::shared_ptr<BlackVolTermStructure> flatVolCurve( new BlackConstantVol(settlementDays, calendar, forwardVolSurface->blackVol(expiry3, 3300), dayCounter)));
(前兩個是您的程式碼中的逐字記錄;在最後一個中,我加入了您的呼叫以檢索
main
建構子呼叫 in的波動性EurVanillaPricer
)。在最後一個中,您已設置settlementDays
為 3。現在,遠期曲線和平坦曲線對於您指定的運動和罷工具有相同的波動性;如果你執行
std::cout << forwardVolSurface->blackVol(expiry3, 3300) << std::endl; std::cout << flatVolTS->blackVol(expiry3, 3300) << std::endl;
你會回來的
0.132405 0.132405
也就是一樣。但不幸的是,這還不是全部。歐式期權的定價引擎不詢問波動率,而是直接詢問變異數( $ \sigma^2 T $ )。如果你這樣做:
std::cout << forwardVolSurface->blackVariance(expiry3, 3300) << std::endl; std::cout << flatVolTS->blackVariance(expiry3, 3300) << std::endl;
你會得到不同的數字:
0.00710851 0.00686836
為什麼?因為兩條曲線有不同的參考日期:
std::cout << forwardVolSurface->referenceDate() << std::endl; std::cout << flatVolTS->referenceDate() << std::endl;
給
July 24th, 2014 July 29th, 2014
在 的建構子中
forwardVolSurface
,您直接傳遞參考日期:它是forwardDate
,您也將其設置為評估日期。在 的建構子中flatVolTS
,您傳遞了settlementDays
andcalendar
,它們分別等於3
andTARGET()
。這意味著參考日期是評估日期後的三個工作日,結果是您考慮週末後的 5 天。因此,當兩條曲線計算變異數時 $ \sigma^2 T $ , 波動率 $ \sigma $ 是一樣的;但對於第一條曲線, $ T $ 是 7 月 24 日到到期之間的時間,而對於第二條曲線 $ T $ 是 7 月 29 日和到期之間的時間,並且短了 5 天。
要解決您的問題,只需
0
像settlementDays
創建平坦曲線時一樣通過。它將與遠期曲線具有相同的參考日期,並且期權價格將相同。(這不需要與您傳遞給無風險和股息曲線的結算天數相同。)注 1:一般來說,將非空的結算天數傳遞給波動性期限結構可能不是一個好主意(我希望這不是太多的負面因素)(該死,我又做了一次)。有結算日的可能性是存在的,因為它是從基
TermStructure
類繼承的,我們不認為要限制它,但它主要是為人們可能想要在現場工作的利率曲線添加的(即,從今天起兩天,這是大多數報價利率工具的結算日期)。對於股票,您可能無論如何都希望從評估日期開始。注 2:初始化期限結構的各種方法及其工作方式在http://www.implementingquantlib.com/2013/09/chapter-3-part-1-of-n-term-structures 有更詳細的解釋。 .html _