天天看點

QuantLib 金融計算——自己動手封裝 Python 接口(2)

目錄

  • QuantLib 金融計算——自己動手封裝 Python 接口(2)
    • 概述
    • 如何封裝一項複雜功能?
      • 尋找最小功能集合的政策
    • 實踐
      • 估計期限結構參數
      • 修改官方接口檔案
    • 下一步的計劃
    • 擴充閱讀

對于一項簡單功能,通常隻需要包裝少數幾個類就可以,正如《自己動手封裝 Python 接口(1)》示範的那樣。

下面,将示範如何包裝 QuantLib 中的複雜功能,最終實作從固息債交易資料中估計期限結構模型的參數。

經過一翻摸索後發現,要封裝一項複雜功能,首先要找到最小功能集合,即這項功能直接或間接涉及的類和函數有哪些。然後,找到最小功能集合後再對涉及到的類或函數分别編寫接口檔案。最後,按照正常流程生成包裝好的 Python 接口。

對于簡單功能來說最小功能集合可能就是一兩個類或函數。而對于複雜功能來說,尋找最小功能集合是一個遞歸的過程(A 用到 B,B 用到 C,...),最終可能找到很多類或函數需要包裝。

尋找最小功能集合有一些經驗性的方法,以“從固息債交易資料中估計期限結構模型的參數”這項功能為例:

  1. 找到核心功能類,即

    FittedBondDiscountCurve

    ,最小功能集合要包含這個類、它的基類以及基類的基類,等等;
  2. 找到構造

    FittedBondDiscountCurve

    對象時涉及到一系列的類,例如

    Calendar

    FittingMethod

    等,這些類、它們的基類以及基類的基類也要包含在最小功能集合中;
  3. 找到

    FittedBondDiscountCurve

    成員函數涉及到一系列的類,這些類、它們的基類以及基類的基類也要包含在最小功能集合中;
  4. 把第 2 和第 3 步遞歸地進行下去,直到最小功能集合中的類和函數不再增加。

需要注意的是,到現在為止最小功能集合中出現的類有的可以發揮實際作用,例如

Date

;而有的隻是充當接口的基類,例如

FittingMethod

,對于這些情況,要把它們能夠發揮實際作用的派生類包含進最小功能集合。

QuantLib-SWIG 從 1.16 開始修改了智能指針的包裝方式,為了和最新版本保持一緻,這裡以 QuantLib 1.17 的 SWIG 接口檔案為基礎做适當修改,删去一些備援代碼,用以包裝 QuantLib 1.15 的接口。

官方釋出的接口檔案中

FittingMethod

的構造函數不能接受

OptimizationMethod

對象,也不能進行 \(L^2\) 正則化限制。在本次自定義的接口檔案中擴充了構造函數的接口,克服上述局限。

接口檔案請見 QuantLibEx-SWIG。

把《收益率曲線之建構曲線(5)》中的 C++ 代碼翻譯成 Python,驗證封裝後的接口是否可用。

import QuantLibEx as qlx

print(qlx.__version__)

bondNum = 16

cleanPrice = [100.4941, 103.5572, 104.4135, 105.0056, 99.8335, 101.25, 102.3832, 97.0053,
              99.5164, 101.2435, 104.0539, 101.15, 96.1395, 91.1123, 122.0027, 92.4369]
priceHandle = [qlx.QuoteHandle(qlx.SimpleQuote(p)) for p in cleanPrice]
issueYear = [1999, 1999, 2001, 2002, 2003, 1999, 2004, 2005,
             2006, 2007, 2003, 2008, 2005, 2006, 1997, 2007]
issueMonth = [qlx.February, qlx.October, qlx.January, qlx.January, qlx.May, qlx.January, qlx.January, qlx.April,
              qlx.April, qlx.September, qlx.January, qlx.January, qlx.January, qlx.January, qlx.July, qlx.January]
issueDay = [22, 22, 4, 9, 20, 15, 15, 26, 21, 17, 15, 8, 14, 11, 10, 12]

maturityYear = [2009, 2010, 2011, 2012, 2013, 2014, 2014, 2015,
                2016, 2017, 2018, 2019, 2020, 2021, 2027, 2037]

maturityMonth = [qlx.July, qlx.January, qlx.January, qlx.July, qlx.October, qlx.January, qlx.July, qlx.July,
                 qlx.September, qlx.September, qlx.January, qlx.March, qlx.July, qlx.September, qlx.July, qlx.March]

maturityDay = [15, 15, 4, 15, 20, 15, 15, 15,
               15, 15, 15, 15, 15, 15, 15, 15]

issueDate = []
maturityDate = []
for i in range(bondNum):
    issueDate.append(
        qlx.Date(issueDay[i], issueMonth[i], issueYear[i]))
    maturityDate.append(
        qlx.Date(maturityDay[i], maturityMonth[i], maturityYear[i]))

couponRate = [
    0.04, 0.055, 0.0525, 0.05, 0.038, 0.04125, 0.043, 0.035,
    0.04, 0.043, 0.0465, 0.0435, 0.039, 0.035, 0.0625, 0.0415]

# 配置 helper

frequency = qlx.Annual
dayCounter = qlx.Actual365Fixed(qlx.Actual365Fixed.Standard)
paymentConv = qlx.Unadjusted
terminationDateConv = qlx.Unadjusted
convention = qlx.Unadjusted
redemption = 100.0
faceAmount = 100.0
calendar = qlx.Australia()

today = calendar.adjust(qlx.Date(30, qlx.January, 2008))
qlx.Settings.instance().evaluationDate = today

bondSettlementDays = 0
bondSettlementDate = calendar.advance(
    today,
    qlx.Period(bondSettlementDays, qlx.Days))

instruments = []
maturity = []

for i in range(bondNum):
    bondCoupon = [couponRate[i]]

    schedule = qlx.Schedule(
        issueDate[i],
        maturityDate[i],
        qlx.Period(frequency),
        calendar,
        convention,
        terminationDateConv,
        qlx.DateGeneration.Backward,
        False)

    helper = qlx.FixedRateBondHelper(
        priceHandle[i],
        bondSettlementDays,
        faceAmount,
        schedule,
        bondCoupon,
        dayCounter,
        paymentConv,
        redemption)

    maturity.append(dayCounter.yearFraction(
        bondSettlementDate, helper.maturityDate()))

    instruments.append(helper)

accuracy = 1.0e-6
maxEvaluations = 5000
weights = qlx.Array()

# 正則化條件

l2Ns = qlx.Array(4, 0.5)
guessNs = qlx.Array(4)
guessNs[0] = 4 / 100.0
guessNs[1] = 0.0
guessNs[2] = 0.0
guessNs[3] = 0.5

l2Sv = qlx.Array(6, 0.5)
guessSv = qlx.Array(6)
guessSv[0] = 4 / 100.0
guessSv[1] = 0.0
guessSv[2] = 0.0
guessSv[3] = 0.0
guessSv[4] = 0.2
guessSv[5] = 0.15

optMethod = qlx.LevenbergMarquardt()

# 拟合方法

nsf = qlx.NelsonSiegelFitting(
    weights, optMethod, l2Ns)
svf = qlx.SvenssonFitting(
    weights, optMethod, l2Sv)

tsNelsonSiegel = qlx.FittedBondDiscountCurve(
    bondSettlementDate,
    instruments,
    dayCounter,
    nsf,
    accuracy,
    maxEvaluations,
    guessNs,
    1.0)

tsSvensson = qlx.FittedBondDiscountCurve(
    bondSettlementDate,
    instruments,
    dayCounter,
    svf,
    accuracy,
    maxEvaluations,
    guessSv)

print("NelsonSiegel Results: \t", tsNelsonSiegel.fitResults().solution())
print("Svensson Results: \t\t", tsSvensson.fitResults().solution())
           
NelsonSiegel Results: 	[ 0.0500803; -0.0105414; -0.0303842; 0.456529 ]
Svensson Results: 		[ 0.0431095; -0.00716036; -0.0340932; 0.0391339; 0.228995; 0.117208 ]
           

所得結果和《收益率曲線之建構曲線(5)》中的完全一緻。

QuantLib 金融計算——自己動手封裝 Python 接口(2)

如果已經安裝了 1.16 以後的 QuantLib,隻要對官方接口檔案稍加修改再重新包裝 Python 接口,就可以擴充

FittingMethod

的構造函數,使其能接受

OptimizationMethod

對象,并能進行正則化。

NelsonSiegelFitting

為例,需要在

fittedbondcurve.i

檔案中用

class NelsonSiegelFitting : public FittingMethod {
  public:
    NelsonSiegelFitting(
        const Array& weights = Array(),
        boost::shared_ptr< OptimizationMethod > optimizationMethod = boost::shared_ptr< OptimizationMethod >(),
        const Array &l2 = Array());
};
           

替換

class NelsonSiegelFitting : public FittingMethod {
  public:
    NelsonSiegelFitting(const Array& weights = Array());
};
           

  1. 包裝 QuantLibEx 中的幾個期限結構模型;
  2. scipy 的優化算法引擎要相較于 QuantLib 自身提供的要更豐富,嘗試使

    FittingMethod

    能接受 scipy 的算法。

《QuantLib 金融計算》系列合集

★ 持續學習 ★ 堅持創作 ★

繼續閱讀