波动率模型是学界搞期权的重点,BS模型中,使用的是固定的波动率。现在提的较多的是局部波动率模型和随机波动率模型。局部波动率模型是随机波动率模型的一种简化,将波动率定义为标的价格S和时间t的函数sigma(S,t),随机波动率模型中,对波动率描述存在随机项(独立于价格中的随机项)。
局部波动率模型中,常用的是SVI模型(stochastic volatility inspired),SVI模型本质上是描述了波动率微笑曲线,并且增加了一些期权性质上的约束。
我前面研究过一次SVI模型,但由于对python里面最优化函数理解的不透,对外层使用了是穷举法,这个方法显然是不好的,只是让我初次了解这个模型的性质。最近部门一个实习生用matlab实现了一下这个模型,虽然拟合的结果并不是很好,但让我发现原来matlab里面的几个函数,如lsqlin就是二次非线性最优化,而fminsearch的函数,使用的就是Nelder-Mead Simplex方法。
实际上我以前虽然也用matlab做最优化,但是对里面的求解算法实在是没研究,反正直接用就好了。但前面自己用python摸索求解函数的时候,突然觉得自己根本不知道在python里面用什么函数去求解自己要解的最优化方程。
最初以为scipy里面应该有对应的最优化函数,后来发现matlab里面基本的lsqlin在scipy里面就没有实现,scipy最接近的是lsq_liner,但这个函数只能接受上下限,无法接受不等式方程约束,后来发现用凸优化报cvxopt才有希望。但是这个包里面也没有lsqlin函数,不过知识点到这里,搜索一下lsqlin+python关键词就能找到一个俄罗斯人贡献的用python实现的lsqlin代码了。
在这些知识点的基础上,我重新将自己的svi模型实现了一下。
首先是数据源格式
- 合约编码 行权日 行权日 剩余到期时间(日历日) 期权类型 标的收盘价 期权行权价 收盘中间价
- 10000887 20171227 2017-12-27 0.052082192 C 2.865 2.209 0.6624
- 10000888 20171227 2017-12-27 0.052082192 C 2.865 2.258 0.6135
- 10000889 20171227 2017-12-27 0.052082192 C 2.865 2.307 0.56465
- 10000890 20171227 2017-12-27 0.052082192 C 2.865 2.356 0.5158
- 10000891 20171227 2017-12-27 0.052082192 C 2.865 2.405 0.46705
- 10000892 20171227 2017-12-27 0.052082192 P 2.865 2.209 0.00015
- 10000893 20171227 2017-12-27 0.052082192 P 2.865 2.258 0.00015
- 10000894 20171227 2017-12-27 0.052082192 P 2.865 2.307 0.00015
- 10000895 20171227 2017-12-27 0.052082192 P 2.865 2.356 0.0002
- 10000896 20171227 2017-12-27 0.052082192 P 2.865 2.405 0.0003
- 10000897 20171227 2017-12-27 0.052082192 C 2.865 2.16 0.7117
其次是拟合代码
- #!/usr/bin/env python
- # encoding: utf-8
- """
- @version: ??
- @author: laofish
- @contact: laofish@outlook.com
- @site: http://www.laofish.com
- @file: fitSVI.py
- @time: 2018-09-07 23:48
- 拟合SVI曲线
- """
- import traceback
- from scipy.optimize import lsq_linear
- from scipy.optimize import fmin
- from lsqlin import *
- from derivaties_fun2 import *
- from scipy.optimize import least_squares
- # 中文和负号的正常显示
- import socket
- hostName = socket.gethostname()
- if hostName == 'laofish-home-PC':
- plt.rcParams['font.sans-serif'] = ['Yahei Mono'] # mat 2.1
- else:
- plt.rcParams['font.sans-serif'] = ['Microsoft Yahei Mono']
- plt.rcParams['axes.unicode_minus'] = False
- # 设置图形的显示风格
- plt.style.use('ggplot')
- def neicengOptimization(guess, myargs):
- '''内侧优化使用'''
- # SVI模型内层优化
- # m,sigma 为给定值,直接从参数里面得到
- # x 为自变量,即最优化问题里面的x
- # a d c 为系数,即最优化问题里面的c
- m, sigma = guess
- x = myargs[0]
- omg_i = myargs[1]
- # x = lsqlin(C,d,A,b) solves the linear system C*x = d
- # in the least-squares sense subject to A*x ≤ b, where C is m-by-n.
- yx = (x - m) / sigma
- zx = np.sqrt(yx ** 2 + 1)
- omega = max(omg_i)
- # xz = np.array([np.ones([len(x), 1]).T, yx, zx])
- xz = np.array([np.array(np.ones([len(x), 1]).T.tolist()[0]), yx, zx])
- # 实际上可能可以用cvxpy这个凸优化包的使用更为简单
- A = [[0, 0, -1], [0, 0, 1], [0, -1, -1], [0, 1, -1], [0, 1, 1], [0, -1, 1], [-1, 0, 0], [1, 0, 0]]
- # 约束边界b
- b = [0, 4 * sigma, 0, 0, 4 * sigma, 4 * sigma, 0, omega]
- # lsqlin(C, d, reg=0, A=None)
- xmatric = lsqlin(xz.T, omg_i, 0, np.array(A), np.array(b))
- acap = np.array(xmatric['x'])[0][0]
- d = np.array(xmatric['x'])[1][0]
- c = np.array(xmatric['x'])[2][0]
- # [acap, d, c]=lsqlin(xz.T, omg_i, 0, np.array(A), np.array(b))
- # [acap,d,c] = lsqlin(xz, omg_i,A,b)
- a = acap
- b = c / sigma
- rho = d / c
- # 目标函数
- sigma2 = np.sum(np.array(acap + d * yx + c * zx - omg_i) ** 2)
- # return [a,b,rho,sigma2]
- return sigma2
- def neicengOptimization2(guess, myargs):
- '''返回a d c '''
- m, sigma = guess
- x = myargs[0]
- omg_i = myargs[1]
- yx = (x - m) / sigma
- zx = np.sqrt(yx ** 2 + 1)
- omega = max(omg_i)
- # xz = np.array([np.ones([len(x), 1]).T, yx, zx])
- xz = np.array([np.array(np.ones([len(x), 1]).T.tolist()[0]), yx, zx])
- A = [[0, 0, -1], [0, 0, 1], [0, -1, -1], [0, 1, -1], [0, 1, 1], [0, -1, 1], [-1, 0, 0], [1, 0, 0]]
- # 约束边界b
- b = [0, 4 * sigma, 0, 0, 4 * sigma, 4 * sigma, 0, omega]
- # lsqlin(C, d, reg=0, A=None)
- xmatric = lsqlin(xz.T, omg_i, 0, np.array(A), np.array(b))
- acap = np.array(xmatric['x'])[0][0]
- d = np.array(xmatric['x'])[1][0]
- c = np.array(xmatric['x'])[2][0]
- # [acap, d, c]=lsqlin(xz.T, omg_i, 0, np.array(A), np.array(b))
- # [acap,d,c] = lsqlin(xz, omg_i,A,b)
- a = acap
- b = c / sigma
- rho = d / c
- # 目标函数
- # sigma2 = np.sum(np.array(acap + d * yx + c * zx - omg_i) ** 2)
- return [a, b, rho]
- def getfitsiv(x, t2M, a_star, b_star, rho_star, m_star, sigma_star):
- '''生成拟合后的SVI波动率'''
- # 计算拟合值
- fit_omg = a_star + b_star * (rho_star * (x - m_star) + np.sqrt((x - m_star) ** 2 + sigma_star ** 2))
- fsigma = np.sqrt(fit_omg / t2M)
- return fsigma
- if __name__ == '__main__':
- optionData = pd.read_excel('rawOptionData.xlsx')
- strikeArr = pd.unique(optionData['期权行权价'])
- time2Mature = pd.unique(optionData['剩余到期时间(日历日)'])
- ulprice = pd.unique(optionData['标的收盘价'])
- r = 0.04
- S = ulprice[0]
- q = 0
- # 鉴于国内特色,只计算call
- oD = optionData
- vArr = []
- strikeArrE = []
- time2MatureE = []
- cData = pd.DataFrame()
- try:
- # 处理错误
- # step 1
- # 计算期权市场的隐含波动率
- for strike in strikeArr:
- for t2M in time2Mature:
- oprice = oD[(oD['期权行权价'] == strike) & (oD['期权类型'] == 'C')
- & (oD['剩余到期时间(日历日)'] == t2M)]['收盘中间价']
- if oprice.empty == False:
- cimpv = bsmImpVol(S, strike, t2M, r, q, oprice.tolist()[0], 'C')
- else:
- cimpv = 0
- # 这个就是隐含方差
- # total implied variance
- v = cimpv ** 2 * t2M
- vArr.append(v)
- strikeArrE.append(strike)
- time2MatureE.append(t2M)
- cData['K'] = strikeArrE
- cData['t2M'] = time2MatureE
- cData['v'] = vArr
- # 去掉v等于0的元素
- cData2 = cData[cData['v'] != 0]
- # 论文中没提到的是,这里应该一个到期时间一个到期时间的循环
- for t2M in cData2['t2M'].unique():
- # 扣除所需数据
- thisData = cData2[cData2['t2M'] == t2M]
- x = np.log(thisData['K'] / (S * np.exp(r * t2M)))
- omg_i = thisData['v'].values
- # %para为m和sigma的初值
- m = 0.1
- sigma = 0.2
- myargs = np.array([x, omg_i]) # 需要传进去的参数
- # arg_out= fmin(neicengOptimization, para)
- # neicengOptimization(m, sigma, myargs)
- # arg_out = fmin(neicengOptimization, x0=[m, sigma],args=(myargs,))
- guess = [m, sigma]
- arg_out = fmin(neicengOptimization, guess, args=(myargs,))
- # m sigma
- m_star, sigma_star = arg_out
- # 获取a,b,rho
- a_star, b_star, rho_star = neicengOptimization2(arg_out.tolist(), myargs)
- # ===================================
- # 绘制拟合和真是曲线
- k1 = thisData['K'].tolist()
- # 换成隐含波动率比较
- tsigma = np.sqrt(omg_i / t2M)
- fig = plt.figure()
- ax1 = fig.add_subplot(111) # 在图表1中创建子图1
- ax1.scatter(k1, tsigma, color='.25', label='Value')
- # 生成拟合后的曲线
- # xaxis = np.linspace(min(k1), max(k1), 100)
- xaxis = np.linspace(min(k1), max(k1), 100)
- # np.log(thisData['K'] / (S * np.exp(r * t2M)))
- # 行权价转化为实际计算用x
- xbx=[np.log(x / (S * np.exp(r * t2M))) for x in xaxis]
- yaxis = [getfitsiv(xk, t2M, a_star, b_star, rho_star, m_star, sigma_star) for xk in xbx]
- # ax1.plot(xaxis.tolist(), yaxis, color='r--',label='fitValue')
- ax1.plot(xaxis.tolist(), yaxis, 'r--', label='fitValue')
- # 去除图形顶部边界和右边界的刻度
- # ax1.tick_params(top='off', right='off') mat 2.1
- ax1.tick_params(top=False, right=False)
- ax1.set_title('svi模型拟合对比')
- ax1.legend()
- plt.savefig('fitsvi_t2m=' + str(round(t2M, 3)) + '.png')
- plt.close()
- # plt.show()
- # 检测是否在要求阈值内
- print(cData)
- except: # 处理异常
- traceback.print_exc()
- pass
- print(optionData)
最后展示一下拟合的结果
请问python对应的lsqlin代码方便分享吗?或者相应的网页链接。搜索lsqlin+python没有对应结果,谢谢
请问你找到了吗 我在import的时候找不到lsqlin这个库
http://maggotroot.blogspot.ch/2013/11/constrained-linear-least-squares-in.html
就是这个
您好,下面这两条语句无法通过,应该是没有这两个相应的文件。您上面发的链接我没法打开,请问您可以分享一下这两个文件吗?感谢!
from lsqlin import *
from derivaties_fun2 import *
你好
文中提到鉴于国内情况,只计算call ,国内股指期权出现 深度实值put 隐含波动率偏低有什么比较深的原因么 ?