请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3364737 新帖:1

波动率微笑图

量化客发表于:5 月 23 日 15:01回复(1)

第一部分,封装了期权BS公式的接口(大部分网上抄来的,自己给集成并验证了下),提供了简单的测试代码。

第二部分,计算隐含波动率,并画出微笑图。
其中需要自定义的参数有
1:期权最后交易日,2:无风险利率(一般默认)
3:历史波动率

波动率计算如下:
1、计算对数收益率:R_i = log(p_i / p_i-1)
2、计算对数收益率序列的标准差std
3、计算年波动率 = std * sqrt(252)
网上搜到的这个方法算下来和很多期权软件得到的差异比较大,所以暂时只能手工录入

BS公式API¶

from math import log,sqrt,exp
from scipy import stats
import matplotlib.pyplot as plt
class option():
    #参数分别表示
    #标的价,行权价,无风险利率,到期天数,波动率,期权最新价,call_put为0表示认购,为1表示认沽
    def __init__(self,s,k,r,t,sigma,close,call_put='call'):
        self.s = s
        self.k = k
        self.r = r
        self.T = t/365
        self.sigma = sigma
        self.close = close
        self.call_put = call_put.lower()
        self.d1 = (log(self.s / self.k) + (self.r + 1 / 2 * self.sigma ** 2) * self.T) / (self.sigma * sqrt(self.T))
    #BS定价公式,返回期权理论价
    def call(self):
        '''
        st,k,r,T,sigma(T以年为单位,天数应该除以365)
        '''
        d1 = self.d1
        d2 = d1 - self.sigma * sqrt(self.T)
        call = self.s * stats.norm.cdf(d1, 0.0, 1.0) - self.k * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0, 1.0)
        return call
    def put(self):
        '''
        st,k,r,T,sigma(T以年为单位,天数应该除以365)
        '''
        d1 = self.d1
        d2 = d1 - self.sigma * sqrt(self.T)
        put = self.k * exp(-self.r * self.T) * stats.norm.cdf(-1 * d2) - 1 * self.s * stats.norm.cdf(-1 * d1)
        return put
    #获得delta
    def delta(self):
        '''
        n默认为1看涨期权的delta
        n为-1为看跌期权的delta
        '''
        if self.call_put == 'call':
            n = 1
        else:
            n = -1
        d1 = self.d1
        delta = n * stats.norm.cdf(n * d1)
        return delta

    # 获得gamma
    def gamma(self):
        d1 = self.d1
        gamma = stats.norm.pdf(d1) / (self.s * self.sigma * sqrt(self.T))
        return gamma

    # 获得theta
    def theta(self):
        '''
        n默认为1看涨期权的delta
        n为-1为看跌期权的delta
        '''
        if self.call_put == 'call':
            n = 1
        else:
            n = -1
        d1 = self.d1
        d2 = d1 - self.sigma * sqrt(self.T)
        theta = -1 * (self.s * stats.norm.pdf(d1) * self.sigma) / (2 * sqrt(self.T)) - n * self.r * self.k * exp(-self.r * self.T) * stats.norm.cdf(n * d2)
        return theta

    # 获得veag
    def vega(self):
        d1 = self.d1
        vega = self.s * sqrt(self.T) * stats.norm.pdf(d1)
        return vega
    #牛顿法迭代求隐含波动率
    def imp_vol_newton(self, sigma_est=1, it=100):
        if self.call_put == 'call':
            for i in range(it):
                d1 = (log(self.s / self.k) + (self.r + 1 / 2 * sigma_est ** 2) * self.T) / (sigma_est * sqrt(self.T))
                d2 = d1 - sigma_est * sqrt(self.T)
                call = self.s * stats.norm.cdf(d1, 0.0, 1.0) - self.k * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0,1.0)
                vega = self.s * sqrt(self.T) * stats.norm.pdf(d1)
                sigma_est -= (call - self.close) /vega
            return sigma_est
        else:
            for i in range(it):
                d1 = (log(self.s / self.k) + (self.r + 1 / 2 * sigma_est ** 2) * self.T) / (sigma_est * sqrt(self.T))
                d2 = d1 - sigma_est * sqrt(self.T)
                put = self.k * exp(-self.r * self.T) * stats.norm.cdf(-1 * d2) - 1 * self.s * stats.norm.cdf(-1 * d1)
                vega = self.s * sqrt(self.T) * stats.norm.pdf(d1)
                sigma_est -= (put - self.close) /vega
            return sigma_est

    # 二分法求隐含波动率
    def imp_vol_dichotomy(self):
        c_est = 0
        top = 3  # 波动率上限
        floor = 0  # 波动率下限
        sigma = (floor + top) / 2  # 波动率初始值
        if self.call_put == 'call':
            while abs(self.close - c_est) > 1e-8:
                d1 = (log(self.s / self.k) + (self.r + 1 / 2 * sigma ** 2) * self.T) / (sigma * sqrt(self.T))
                d2 = d1 - sigma * sqrt(self.T)
                call = self.s * stats.norm.cdf(d1, 0.0, 1.0) - self.k * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0,1.0)
                c_est = call
                # 根据价格判断波动率是被低估还是高估,并对波动率做修正
                if self.close - c_est > 0:  # f(x)>0
                    floor = sigma
                    sigma = (sigma + top) / 2
                else:
                    top = sigma
                    sigma = (sigma + floor) / 2
            return sigma
        else:
            while abs(self.close - c_est) > 1e-8:
                d1 = (log(self.s / self.k) + (self.r + 1 / 2 * sigma ** 2) * self.T) / (sigma * sqrt(self.T))
                d2 = d1 - sigma * sqrt(self.T)
                put = self.k * exp(-self.r * self.T) * stats.norm.cdf(-1 * d2) - 1 * self.s * stats.norm.cdf(-1 * d1)
                c_est = put
                # 根据价格判断波动率是被低估还是高估,并对波动率做修正
                if self.close - c_est > 0:  # f(x)>0
                    floor = sigma
                    sigma = (sigma + top) / 2
                else:
                    top = sigma
                    sigma = (sigma + floor) / 2
            return sigma
parameter = {}
parameter['标的价格'] = 2.724
parameter['行权价'] = 2.7
parameter['无风险利率'] = 0.044
parameter['到期天数'] = 218
parameter['历史波动率'] = 0.2668
parameter['期权最新价'] = 0.2236
parameter['call_put'] = 'call'


p1 = option(parameter['标的价格'],parameter['行权价'],parameter['无风险利率'],parameter['到期天数'],parameter['历史波动率'],parameter['期权最新价'],parameter['call_put'])
print(p1.imp_vol_newton(),p1.imp_vol_dichotomy())
0.20927908375262202 0.20927904546260834

获取期权数据¶

import datetime
today = str(datetime.datetime.now().year)+'-'+str(datetime.datetime.now().month)+'-'+str(datetime.datetime.now().day)
stop_date = '2019-6-26'
date1 = datetime.datetime.strptime(today,'%Y-%m-%d')
date2 = datetime.datetime.strptime(stop_date,'%Y-%m-%d')
diff = date2 - date1
#获取期权实时数据
from jqdata import *
df_CO = opt.run_query(query(opt.OPT_DAILY_PREOPEN.code,
                         opt.OPT_DAILY_PREOPEN.exercise_price,
                         opt.OPT_DAILY_PREOPEN.pre_close,
                         opt.OPT_DAILY_PREOPEN.contract_type,
                         opt.OPT_DAILY_PREOPEN.pre_close_underlying).
                   filter(opt.OPT_DAILY_PREOPEN.date==today,
                          opt.OPT_DAILY_PREOPEN.exercise_date==stop_date,
                          opt.OPT_DAILY_PREOPEN.contract_type=='CO').
                      order_by(opt.OPT_DAILY_PREOPEN.exercise_price))
df_PO = opt.run_query(query(opt.OPT_DAILY_PREOPEN.code,
                         opt.OPT_DAILY_PREOPEN.exercise_price,
                         opt.OPT_DAILY_PREOPEN.pre_close,
                         opt.OPT_DAILY_PREOPEN.contract_type,
                         opt.OPT_DAILY_PREOPEN.pre_close_underlying).
                   filter(opt.OPT_DAILY_PREOPEN.date==today,
                          opt.OPT_DAILY_PREOPEN.exercise_date==stop_date,
                          opt.OPT_DAILY_PREOPEN.contract_type=='PO').
                      order_by(opt.OPT_DAILY_PREOPEN.exercise_price))

认购合约¶

df_CO.head()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
code exercise_price pre_close contract_type pre_close_underlying
0 10001671.XSHG 2.050 0.6645 CO 2.709
1 10001645.XSHG 2.100 0.6136 CO 2.709
2 10001637.XSHG 2.150 0.5649 CO 2.709
3 10001629.XSHG 2.200 0.5138 CO 2.709
4 10001521.XSHG 2.205 0.5099 CO 2.709
call_imp_vol = []
for i in range(len(df_CO)):
    parameter = {}
    parameter['标的价格'] = df_CO.iloc[i,:].pre_close_underlying
    parameter['行权价'] = df_CO.iloc[i,:].exercise_price
    parameter['无风险利率'] = 0.044
    parameter['到期天数'] = diff.days
    parameter['历史波动率'] = 0.2668
    parameter['期权最新价'] = df_CO.iloc[i,:].pre_close
    parameter['call_put'] = 'call'
    p1 = option(parameter['标的价格'],parameter['行权价'],
                parameter['无风险利率'],parameter['到期天数'],
                parameter['历史波动率'],parameter['期权最新价'],
                parameter['call_put'])
    call_imp_vol.append(p1.imp_vol_newton())
df_CO['imp_vol'] = call_imp_vol
df_CO.dropna(inplace=True)
df_CO.head()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
code exercise_price pre_close contract_type pre_close_underlying imp_vol
14 10001601.XSHG 2.450 0.2723 CO 2.709 0.206647
15 10001505.XSHG 2.500 0.2227 CO 2.709 0.176049
16 10001602.XSHG 2.500 0.2219 CO 2.709 0.166751
17 10001506.XSHG 2.549 0.1841 CO 2.709 0.210203
18 10001603.XSHG 2.550 0.1819 CO 2.709 0.202963

认沽合约¶

put_imp_vol = []
for i in range(len(df_PO)):
    parameter = {}
    parameter['标的价格'] = df_PO.iloc[i,:].pre_close_underlying
    parameter['行权价'] = df_PO.iloc[i,:].exercise_price
    parameter['无风险利率'] = 0.044
    parameter['到期天数'] = diff.days
    parameter['历史波动率'] = 0.2668
    parameter['期权最新价'] = df_PO.iloc[i,:].pre_close
    parameter['call_put'] = 'put'
    p1 = option(parameter['标的价格'],parameter['行权价'],
                parameter['无风险利率'],parameter['到期天数'],
                parameter['历史波动率'],parameter['期权最新价'],
                parameter['call_put'])
    put_imp_vol.append(p1.imp_vol_newton())
df_PO['imp_vol'] = put_imp_vol
df_PO.dropna(inplace=True)
df_PO.head()
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
code exercise_price pre_close contract_type pre_close_underlying imp_vol
0 10001672.XSHG 2.050 0.0012 PO 2.709 0.409649
1 10001646.XSHG 2.100 0.0015 PO 2.709 0.390386
2 10001638.XSHG 2.150 0.0013 PO 2.709 0.351936
3 10001630.XSHG 2.200 0.0014 PO 2.709 0.325023
4 10001522.XSHG 2.205 0.0014 PO 2.709 0.321990

画微笑图¶

plt.figure(figsize=[20,5])
plt.plot(df_CO.exercise_price,df_CO.imp_vol)
plt.plot(df_PO.exercise_price,df_PO.imp_vol)
my_x_ticks = np.arange(2,3.5,0.05)
plt.xticks(my_x_ticks)
plt.legend(labels=['call','put'],loc='best')
<matplotlib.legend.Legend at 0x7f6ce2bedfd0>
 

全部回复

0/140

量化课程

    移动端课程