以下文字源自我對源代碼的理解,如有不同意見,歡迎留言討論或發郵件(
xuruilong100@163.com
)
QuantLib 金融計算——原理之 Bootstrap
這次新開一個系列,不講應用案例了,嘗試介紹 QuantLib 某些核心功能背后的代碼邏輯與數學原理。
Bootstrap 的原理
通過合約報價推算期限結構的過程稱為“bootstrap”,其思想和實踐非常類似於理論證明中用到的“數學歸納法”,大體過程如下:
- 首先將要用到的已知利率和金融工具根據期限升序排列;
- 假設已經求得期限結構上的第 \(n\) 個值——\(TS_n\),對應於第 \(n\) 個報價;
- 令 \(TS_{n+1}\) 是個待定參數 \(x\),並給定一個初值;
- 用已知的期限結構數據——\(TS_1,\dots,TS_n,x\),對第 \(n+1\) 個金融工具進行估值;
- 調整 \(x\),使得估值結果與報價達到一致;
- 此時,\(x\) 便是要求解的 \(TS_{n+1}\);
- 以此類推。
其中 \(TS\) 可以是即期利率、遠期利率、貼現因子三者中的任意一個,而可用的插值方法也有線性插值、樣條插值、對數線性插值和常數插值等等。兩個維度相互搭配可以產生非常多的組合,QuantLib 通過模板技術實現兩個維度的自由搭配,具體選擇哪種組合要視業務需要而定。
其他問題語境下的 bootstrap 計算原理類似。
QuantLib 中的 bootstrap 計算(利率語境)
要在 QuantLib 中 bootstrap 出一條利率曲線所涉及的最核心的類有兩個:
- 直接調用的類是
PiecewiseYieldCurve
模板類,它直接接受一組報價; - 另外一個間接調用的類是
IterativeBootstrap
模板類,作為PiecewiseYieldCurve
的默認模板參數。
PiecewiseYieldCurve
需要三個模板參數,前兩個分別確定底層數據和插值方法的選擇。比如說 PiecewiseYieldCurve<Discount, LogLinear>
表示程序內部 bootstrap 出一條貼現因子曲線,用對數線性法插值得到任意日期上的利率值。
IterativeBootstrap
是 PiecewiseYieldCurve
需要的第三個模板參數,並且是默認選項。它本身就是個模板類,而它的模板參數正是實例化后的 PiecewiseYieldCurve
。C++ 模板就是這么神奇!
在整個 bootstrap 計算過程中,實際出苦力的類是 IterativeBootstrap
,它負責從若干金融工具的報價數據中迭代地求解出利率曲線,而 PiecewiseYieldCurve
則是充當 Boss 的角色。
核心代碼細節
現在來看看 QuantLib 具體怎么實現 bootstrap 的。
通過一些模板技巧,PiecewiseYieldCurve
其實是 YieldTermStructure
的派生類,所以它最核心的方法是 discountImpl
(要了解這一點,請閱讀《構建 QuantLib》)。
DiscountFactor PiecewiseYieldCurve<C,I,B>::discountImpl(Time t) const
{
calculate();
return base_curve::discountImpl(t);
}
在返回數據之前,PiecewiseYieldCurve
會先調用 calculate
,bootstrap 的計算就發生在這里。
PiecewiseYieldCurve
的 calculate
方法直接復用其基類 LazyObject
的 calculate
方法。這里應用了“模板方法模式”,calculate
只起到傳達命令的作用,實際的計算任務被重新委派回了 PiecewiseYieldCurve
的 performCalculations
方法。
void PiecewiseYieldCurve<C,I,B>::performCalculations() const
{
// just delegate to the bootstrapper
bootstrap_.calculate();
}
在 performCalculations
方法中 IterativeBootstrap
的一個實例 bootstrap_
最終執行所有計算。
盡管 IterativeBootstrap
的 calculate
方法代碼很長,但毛教員教育我們分析問題要“抓大放小”,其實最核心的代碼只有兩行,
if (validData)
solver_.solve(*errors_[i], accuracy, guess, min, max);
else
firstSolver_.solve(*errors_[i], accuracy, guess, min, max);
solver_
和 firstSolver_
分別是 Brent
和 FiniteDifferenceNewtonSafe
對象,用於求解(非)線性函數的根,也就是求解利率。
errors_[i] = ext::shared_ptr<BootstrapError<Curve> >(
new BootstrapError<Curve>(ts_, helper, i));
errors_
是 BootstrapError
對象,也是一個函數對象,它負責返回估值結果與實際報價之間的差距,充當待求解的(非)線性函數。
const ext::shared_ptr<typename Traits::helper>& helper = ts_->instruments_[j];
而 helper
就是某個傳遞給 PiecewiseYieldCurve
的報價對象,在利率語境下通常是 FraRateHelper
或 SwapRateHelper
等 XXXRateHelper
對象,這些 XXXRateHelper
類專門用於 bootstrap 計算。
上述代碼便是 QuantLib 對 bootstrap 原理的實現。
開放問題:如何實現 FR007 互換的相關分析?
利率互換的分析分為兩大類:
- 對存續合約估計、計算敏感性等;
- 根據最新合約的報價推算利率期限結構。
FR007 互換的現金流結構和普通的利率互換(例如 Shibor3M 互換)基本一致,每隔三個月交換現金流,但確定浮動端利率的方式有不同。在三個月的付息區間內 FR007 利率要每隔七天確定一次,付息區間內的若干 FR007 利率還需要經過某些計算才能最終確定浮動端利率。
根據上述代碼的分析,要想最大限度的復用當前邏輯並最小程度的編寫代碼,需要在 helper
上做文章,可以考慮實現 SwapRateHelper
的兄弟類 FR007SwapRateHelper
,以及配套的 FR007Swap
類(作為 VanillaSwap
的派生類或兄弟類)。
QuantLib 當前實現的 ArithmeticAverageOIS
和 ArithmeticOISRateHelper
也許是非常值得效仿的范例。
下一步將嘗試按照上述思路實現 C++ 代碼。