QuantLib 金融計算——數學工具之優化器

若是未作特別說明,文中的程序都是 Python3 代碼。算法

QuantLib 金融計算——數學工具之優化器

載入模塊dom

import QuantLib as ql
import scipy

print(ql.__version__)
1.12

概述

在量化金融的模型校準過程當中,最重要的工具是對函數 \(f : R^n \to R\) 的優化器。一般遇到的最優化問題是一個最小二乘問題。例如,尋找一個模型的參數使得某些損失函數最小化。函數

quantlib-python 中的最優化計算委託給 Optimizer 類,用戶須要配置合適的參數以描述最優化問題,須要注意的是 Optimizer 對象默認求解的是某個函數「最小化」問題。工具

Optimizer

Optimizer 類的構造函數不接受參數,求解最優化問題的方式也很是簡單,僅需調用 solve 函數便可:測試

solve(function,
      c,
      m,
      e,
      iv)
  • function:函數或函數對象,返回一個浮點數,所接受的參數是若干獨立的浮點數;
  • cConstraint 對象,描述優化問題的約束條件;
  • mOptimizationMethod 對象,優化算法引擎;
  • eEndCriteria 對象,描述優化問題的終止條件;
  • ivArray 對象,優化計算的初始值。

solve 函數返回一個 Array 對象,存儲找到的最小值點。優化

Constraint

quantlib-python 提供的具體約束條件均繼承自 Constraint 類,有以下幾種:spa

  • NoConstraint:無約束
  • PositiveConstraint:要求全部參數爲正數
  • BoundaryConstraint:要求全部參數在某個區間內
  • CompositeConstraint:要求全部參數同時知足兩個約束條件
  • NonhomogeneousBoundaryConstraint:對每一個參數分別約束,要求其在某個區間內

OptimizationMethod

quantlib-python 提供的具體優化算法均繼承自 OptimizationMethod 類,有以下幾種:code

  • LevenbergMarquardt:Levenberg-Marquardt 算法,實現基於 MINPACK;
  • Simplex:單純形法;
  • ConjugateGradient:共軛梯度法;
  • SteepestDescent:最速降低法;
  • BFGS:Broyden-Fletcher-Goldfarb-Shanno 算法;
  • DifferentialEvolution:微分進化算法;
  • GaussianSimulatedAnnealing:高斯模擬退火算法;
  • MirrorGaussianSimulatedAnnealing:鏡像高斯模擬退火算法;
  • LogNormalSimulatedAnnealing:對數高斯模擬退火算法。

EndCriteria

最優化計算一般是一個迭代過程,咱們須要定義一個終止條件以引導最優化計算結束,不然可能一直計算下去。終止條件由 EndCriteria 類參數化,其構造函數以下orm

EndCriteria(maxIteration,
            maxStationaryStateIterations,
            rootEpsilon,
            functionEpsilon,
            gradientNormEpsilon)
  • maxIteration:整數,最大迭代次數;
  • maxStationaryStateIterations:整數,穩定點(函數值和根同時穩定)的最大迭代次數;
  • rootEpsilon:浮點數,當前根與最新根的絕對差小於 rootEpsilon 時中止計算;
  • functionEpsilon:浮點數,當前函數值與最新函數值的絕對差小於 functionEpsilon 時中止計算;
  • gradientNormEpsilon:浮點數,當前梯度與最新梯度差的範數小於 gradientNormEpsilon 時中止計算;

注意,對於每種優化器來說,並非全部參數可能是必須的。

示例

Rosenbrock 問題

咱們以 Rosenbrock 函數(也簡稱爲香蕉函數)爲例測試優化器,這是一個經典的優化問題。函數定義以下:

\[ f(x,y) = (1-x)^2 + 100(y-x^2)^2 \]

最小值點落在 \((x,y)=(1, 1)\),此時的函數值 \(f(x,y)=0\)

首先定義 Rosenbrock 函數,注意,每一個參數是獨立的浮點數。

def RosenBrockFunction(x0, x1):
    res = (1 - x0) * (1 - x0) + 100.0 * (x1 - x0 * x0) * (x1 - x0 * x0)

    return res

接着,配置優化器,並測試 SimplexConjugateGradient 算法。初始值設定爲 \((x, y) = (0.1, 0.1)\),最優化類型爲「無約束」的。

例子 1

def testOptimizer1():
    maxIterations = 1000
    minStatIterations = 100
    rootEpsilon = 1e-8
    functionEpsilon = 1e-9
    gradientNormEpsilon = 1e-5

    myEndCrit = ql.EndCriteria(
        maxIterations,
        minStatIterations,
        rootEpsilon,
        functionEpsilon,
        gradientNormEpsilon)

    constraint = ql.NoConstraint()

    solver1 = ql.Simplex(0.1)
    solver2 = ql.ConjugateGradient()

    minimize = ql.Optimizer()

    min1 = minimize.solve(
        function=RosenBrockFunction,
        c=constraint,
        m=solver1,
        e=myEndCrit,
        iv=ql.Array(2, 0.1))

    min2 = minimize.solve(
        function=RosenBrockFunction,
        c=constraint,
        m=solver2,
        e=myEndCrit,
        iv=ql.Array(2, 0.1))

    print('{0:<30}{1}'.format('Root Simplex', min1))
    print('{0:<30}{1}'.format('Root ConjugateGradient', min2))
    print('{0:<40}{1}'.format(
        'Min F Value Simplex',
        RosenBrockFunction(min1[0], min1[1])))
    print('{0:<40}{1}'.format(
        'Min F Value ConjugateGradient',
        RosenBrockFunction(min2[0], min2[1])))


testOptimizer1()
Root Simplex                  [ 1; 1 ]
Root ConjugateGradient        [ 0.998904; 0.995025 ]
Min F Value Simplex                     2.929205541302239e-17
Min F Value ConjugateGradient           0.0007764961476745887

校準問題

下面虛擬一個模型校準問題。假設已知 4 個看漲期權的價格 \(C_1 , C_2 , C_3 , C_4\),以及對應的敲訂價 \(K_i\),未知量是股票價格 \(S_0\) 和波動率 \(\sigma\),經過解決下面的最小二乘問題來求解出 \((\sigma, S_0)\)

\[ f(\sigma, S_0) = \sum_{i=1}^4 (C(K_i, \sigma, S_0) - C_i)^2 \]

首先定義損失函數(函數對象),

class CallProblemFunction(object):
    def __init__(self,
                 rd, rf, tau, phi,
                 K1, K2, K3, K4,
                 C1, C2, C3, C4):
        self.rd_ = rd
        self.rf_ = rf
        self.tau_ = tau
        self.phi_ = phi
        self.K1_ = K1
        self.K2_ = K2
        self.K3_ = K3
        self.K4_ = K4
        self.C1_ = C1
        self.C2_ = C2
        self.C3_ = C3
        self.C4_ = C4

    @staticmethod
    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 values(self,
               x0,
               x1):
        res = ql.Array(4)
        res[0] = self.blackScholesPrice(
            x0, self.K1_, self.rd_, self.rf_, x1, self.tau_, self.phi_) - self.C1_
        res[1] = self.blackScholesPrice(
            x0, self.K2_, self.rd_, self.rf_, x1, self.tau_, self.phi_) - self.C2_
        res[2] = self.blackScholesPrice(
            x0, self.K3_, self.rd_, self.rf_, x1, self.tau_, self.phi_) - self.C3_
        res[3] = self.blackScholesPrice(
            x0, self.K4_, self.rd_, self.rf_, x1, self.tau_, self.phi_) - self.C4_

        return res

    def __call__(self,
                 x0,
                 x1):
        tmpRes = self.values(x0, x1)

        res = tmpRes[0] * tmpRes[0]
        res += tmpRes[1] * tmpRes[1]
        res += tmpRes[2] * tmpRes[2]
        res += tmpRes[3] * tmpRes[3]

        return res

例子 2

def testOptimizer2():
    spot = 98.51
    vol = 0.134
    K1 = 87.0
    K2 = 96.0
    K3 = 103.0
    K4 = 110.0
    rd = 0.002
    rf = 0.01
    phi = 1
    tau = 0.6

    C1 = CallProblemFunction.blackScholesPrice(
        spot, K1, rd, rf, vol, tau, phi)
    C2 = CallProblemFunction.blackScholesPrice(
        spot, K2, rd, rf, vol, tau, phi)
    C3 = CallProblemFunction.blackScholesPrice(
        spot, K3, rd, rf, vol, tau, phi)
    C4 = CallProblemFunction.blackScholesPrice(
        spot, K4, rd, rf, vol, tau, phi)

    optFunc = CallProblemFunction(
        rd, rf, tau, phi, K1, K2, K3, K4, C1, C2, C3, C4)

    maxIterations = 1000
    minStatIterations = 100
    rootEpsilon = 1e-5
    functionEpsilon = 1e-5
    gradientNormEpsilon = 1e-5

    myEndCrit = ql.EndCriteria(
        maxIterations,
        minStatIterations,
        rootEpsilon,
        functionEpsilon,
        gradientNormEpsilon)

    startVal = ql.Array(2)
    startVal[0] = 80.0
    startVal[1] = 0.20

    constraint = ql.NoConstraint()
    solver = ql.BFGS()

    minimize = ql.Optimizer()

    min1 = minimize.solve(
        function=optFunc,
        c=constraint,
        m=solver,
        e=myEndCrit,
        iv=startVal)

    print('Root', min1)
    print('Min Function Value', optFunc(min1[0], min1[1]))
Root [ 98.51; 0.134 ]
Min Function Value 5.979965971506814e-22
相關文章
相關標籤/搜索