• QuantLib 金融计算——数学工具之求解器


    如果未做特别说明,文中的程序都是 Python3 代码。

    QuantLib 金融计算——数学工具之求解器

    载入模块

    import QuantLib as ql
    import scipy
    from scipy.stats import norm
    
    print(ql.__version__)
    
    1.12
    

    概述

    QuantLib 提供了多种类型的一维求解器,用以求解单参数函数的根,

    [f(x)=0 ]

    其中 (f : R o R) 是实数域上的函数。

    QuantLib 提供的求解器类型有:

    • Brent
    • Bisection
    • Secant
    • Ridder
    • Newton(要求提供成员函数 derivative,计算导数)
    • FalsePosition

    这些求解器的构造函数均为默认构造函数,不接受参数。例如,Brent 求解器实例的构造语句为 mySolv = Brent()

    调用方式

    求解器的成员函数 solve 有两种调用方式:

    solve(f,
          accuracy,
          guess,
          step)
    
    solve(f,
          accuracy,
          guess,
          xMin,
          xMax)
    
    • f:单参数函数或函数对象,返回值为一个浮点数。
    • accuracy:浮点数,表示求解精度 (epsilon),用于停止计算。假设 (x_i) 是根的准确解,
      • (|f(x)| < epsilon)
      • (|x - x_i| < epsilon) 时停止计算。
    • guess:浮点数,对根的初始猜测值。
    • step:浮点数,在第一种调用方式中,没有限定根的区间范围,算法需要自己搜索,确定一个范围。step 规定了搜索算法的步长。
    • xMinxMax:浮点数,左右区间范围

    根求解器在量化金融中最经典的应用是求解隐含波动率。给定期权价格 (p) 以及其他参数 (S_0)(K)(r_d)(r_f)( au),我们要计算波动率 (sigma),满足

    [f(sigma) = mathrm{blackScholesPrice}(S_0 , K, r_d , r_f , sigma , au, phi) - p = 0 ]

    其中 Black-Scholes 函数中 (phi = 1) 代表看涨期权;(phi = −1) 代表看跌期权。

    非 Newton 算法(不需要导数)

    下面的例子显示了如何加一个多参数函数包装为一个单参数函数,并使用 QuantLib 求解器计算隐含波动率。

    例子 1

    # Black-Scholes 函数
    def blackScholesPrice(spot,
                          strike,
                          rd,
                          rf,
                          vol,
                          tau,
                          phi):
        domDf = scipy.exp(-rd * tau)
        forDf = scipy.exp(-rf * tau)
        fwd = spot * forDf / domDf
        stdDev = vol * scipy.sqrt(tau)
    
        dp = (scipy.log(fwd / strike) + 0.5 * stdDev * stdDev) / stdDev
        dm = (scipy.log(fwd / strike) - 0.5 * stdDev * stdDev) / stdDev
    
        res = phi * domDf * (fwd * norm.cdf(phi * dp) - strike * norm.cdf(phi * dm))
    
        return res
    
    
    # 包装函数
    def impliedVolProblem(spot,
                          strike,
                          rd,
                          rf,
                          tau,
                          phi,
                          price):
        def inner_func(v):
            return blackScholesPrice(spot, strike, rd, rf, v, tau, phi) - price
    
        return inner_func
    
    
    def testSolver1():
        # setup of market parameters
        spot = 100.0
        strike = 110.0
        rd = 0.002
        rf = 0.01
        tau = 0.5
        phi = 1
        vol = 0.1423
    
        # calculate corresponding Black Scholes price
    
        price = blackScholesPrice(spot, strike, rd, rf, vol, tau, phi)
        # setup a solver
        mySolv1 = ql.Bisection()
        mySolv2 = ql.Brent()
        mySolv3 = ql.Ridder()
    
        accuracy = 0.00001
        guess = 0.25
    
        min = 0.0
        max = 1.0
    
        myVolFunc = impliedVolProblem(spot, strike, rd, rf, tau, phi, price)
    
        res1 = mySolv1.solve(myVolFunc, accuracy, guess, min, max)
        res2 = mySolv2.solve(myVolFunc, accuracy, guess, min, max)
        res3 = mySolv3.solve(myVolFunc, accuracy, guess, min, max)
    
        print('{0:<35}{1}'.format('Input Volatility:', vol))
        print('{0:<35}{1}'.format('Implied Volatility Bisection:', res1))
        print('{0:<35}{1}'.format('Implied Volatility Brent:', res2))
        print('{0:<35}{1}'.format('Implied Volatility Ridder:', res3))
    
    
    testSolver1()
    
    # Input Volatility:                  0.1423
    # Implied Volatility Bisection:      0.14229583740234375
    # Implied Volatility Brent:          0.14230199334812577
    # Implied Volatility Ridder:         0.1422999996313447
    

    Newton 算法(需要导数)

    Newton 算法要求为根求解器提供 (f(sigma)) 的导数 (frac{partial f}{partial sigma})(即 vega)。下面的例子显示了如何将导数添加进求解隐含波动率的过程。为此我们需要一个类,一方面提供作为一个函数对象,另一方面要提供成员函数 derivative

    例子 2

    class BlackScholesClass:
        def __init__(self,
                     spot,
                     strike,
                     rd,
                     rf,
                     tau,
                     phi,
                     price):
            self.spot_ = spot
            self.strike_ = strike
            self.rd_ = rd
            self.rf_ = rf
            self.phi_ = phi
            self.tau_ = tau
            self.price_ = price
            self.sqrtTau_ = scipy.sqrt(tau)
            self.d_ = norm
    
            self.domDf_ = scipy.exp(-self.rd_ * self.tau_)
            self.forDf_ = scipy.exp(-self.rf_ * self.tau_)
            self.fwd_ = self.spot_ * self.forDf_ / self.domDf_
            self.logFwd_ = scipy.log(self.fwd_ / self.strike_)
    
        def blackScholesPrice(self,
                              spot,
                              strike,
                              rd,
                              rf,
                              vol,
                              tau,
                              phi):
            domDf = scipy.exp(-rd * tau)
            forDf = scipy.exp(-rf * tau)
            fwd = spot * forDf / domDf
            stdDev = vol * scipy.sqrt(tau)
    
            dp = (scipy.log(fwd / strike) + 0.5 * stdDev * stdDev) / stdDev
            dm = (scipy.log(fwd / strike) - 0.5 * stdDev * stdDev) / stdDev
    
            res = phi * domDf * (fwd * norm.cdf(phi * dp) - strike * norm.cdf(phi * dm))
    
            return res
    
        def impliedVolProblem(self,
                              spot,
                              strike,
                              rd,
                              rf,
                              vol,
                              tau,
                              phi,
                              price):
            return self.blackScholesPrice(
                spot, strike, rd, rf, vol, tau, phi) - price
    
        def __call__(self,
                     x):
            return self.impliedVolProblem(
                self.spot_, self.strike_, self.rd_, self.rf_,
                x,
                self.tau_, self.phi_, self.price_)
    
        def derivative(self,
                       x):
            # vega
            stdDev = x * self.sqrtTau_
            dp = (self.logFwd_ + 0.5 * stdDev * stdDev) / stdDev
            return self.spot_ * self.forDf_ * self.d_.pdf(dp) * self.sqrtTau_
    
    
    def testSolver2():
    
        # setup of market parameters
        spot = 100.0
        strike = 110.0
        rd = 0.002
        rf = 0.01
        tau = 0.5
        phi = 1
        vol = 0.1423
    
        # calculate corresponding Black Scholes price
        price = blackScholesPrice(
            spot, strike, rd, rf, vol, tau, phi)
        solvProblem = BlackScholesClass(
            spot, strike, rd, rf, tau, phi, price)
    
        mySolv = ql.Newton()
    
        accuracy = 0.00001
        guess = 0.10
        step = 0.001
    
        res = mySolv.solve(
            solvProblem, accuracy, guess, step)
    
        print('{0:<20}{1}'.format('Input Volatility:', vol))
        print('{0:<20}{1}'.format('Implied Volatility:', res))
    
    
    testSolver2()
    
    # Input Volatility:   0.1423
    # Implied Volatility: 0.14230000000000048
    

    导数的使用明显提高了精度。

  • 相关阅读:
    洛谷P2466 [SDOI2008]Sue的小球 题解 区间DP+费用提前计算
    中国国家集训队论文集目录(1999-2008)
    洛谷P1726 上白泽慧音 题解 强连通分量
    洛谷P1410 子序列 题解 动态规划
    树堆(Treap)学习笔记 2020.8.12
    伸展树(Splay)学习笔记
    git操作
    yii 缓存探究
    yii之srbac详解
    一个PDO类
  • 原文地址:https://www.cnblogs.com/xuruilong100/p/9839441.html
Copyright © 2020-2023  润新知