目錄python
若是未作特別說明,文中的程序都是 Python3 代碼。算法
載入模塊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
:函數或函數對象,返回一個浮點數,所接受的參數是若干獨立的浮點數;c
:Constraint
對象,描述優化問題的約束條件;m
:OptimizationMethod
對象,優化算法引擎;e
:EndCriteria
對象,描述優化問題的終止條件;iv
:Array
對象,優化計算的初始值。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 函數(也簡稱爲香蕉函數)爲例測試優化器,這是一個經典的優化問題。函數定義以下:
\[ 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
接着,配置優化器,並測試 Simplex
和 ConjugateGradient
算法。初始值設定爲 \((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