50ETF期权隐含波动率曲面

用新浪财经的50ETF期权数据画出的隐含波动率曲面和Vega,能根据行情变化动态更新。 效果如下:

50ETF期权隐含波动率曲面
没有考虑分红调整过的合约。因各月份的期权合约数量不一致, 在画曲面时,对合约数量少的月份的数据用2次曲线拟合, 用拟合值填补空缺。
以下是python代码,其中获取一部分期权数据的代码见我的另一篇博客
一份完整的代码在我的github上https://github.com/sfl666/50ETF_option

# python3

from time import sleep
from threading import Thread

from requests import get, exceptions
from numpy import polyfit, polyval, meshgrid, array
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from mpl_toolkits.mplot3d import Axes3D

import sina_op_api

COLORS = ['blue', 'yellow', 'lime', 'red', 'purple', 'slategray', 'tomato', 'orange', 'darkred', 'aqua']
global_lines = [[], [], [], []]
ELEV = 30


def requests_get(all_codes):
    url = "http://hq.sinajs.cn/list={codes}".format(codes=all_codes)
    while True:
        try:
            data = get(url).content.decode('gbk').strip().split('\n')
            break
        except (exceptions.ConnectionError, exceptions.ConnectTimeout) as e:
            print('连接出错,10秒后重试')
            print(e)
            sleep(10)
    return [i.split(',') for i in data]


def get_codes():
    while True:
        try:
            dates = sorted(sina_op_api.get_op_dates())
            up, down = [], []
            for date in dates:
                up_codes, down_codes = sina_op_api.get_op_codes(date)
                up.append(['CON_SO_' + i for i in up_codes])
                down.append(['CON_SO_' + i for i in down_codes])
            all_codes = ','.join([','.join(i) for i in up] + [','.join(i) for i in down])
            data = requests_get(all_codes)
            not_a_codes = [i[0][11:26] for i in data if not i[0].endswith('A')]  # 不考虑分红的
            for i in range(len(up)):
                up[i] = [j for j in up[i] if j in not_a_codes]
                down[i] = [j for j in down[i] if j in not_a_codes]
            break
        except (exceptions.ConnectionError, exceptions.ConnectTimeout) as e:
            print('连接出错,10秒后重试')
            print(e)
            sleep(10)
    return up, down, ','.join(not_a_codes), dates


def get_data(up, down, all_codes):
    x, y, vega = [], [], []
    for line in requests_get(all_codes):
        y.append(float(line[9]))
        vega.append(float(line[8]))
        x.append(float(line[13]))
    up_x, up_y, down_x, down_y, up_vega, down_vega = [], [], [], [], [], []
    b = 0
    for i in up:
        len_i = len(i)
        up_x.append(x[b:b + len_i])
        up_y.append(y[b:b + len_i])
        up_vega.append(vega[b:b + len_i])
        b += len_i
    for i in down:
        len_i = len(i)
        down_x.append(x[b:b + len_i])
        down_y.append(y[b:b + len_i])
        down_vega.append(vega[b:b + len_i])
        b += len_i
    return up_x, up_y, up_vega, down_x, down_y, down_vega


def fit(up_x, up_y, down_x, down_y):
    xx = set()
    for i in up_x:
        xx |= set(i)
    xx = sorted(xx)
    up_y2, down_y2 = [], []
    for i in range(len(up_x)):
        if xx == up_x[i]:
            up_y2.append(up_y[i])
        else:
            up_y2.append(polyval(polyfit(up_x[i], up_y[i], 2), xx))
        if xx == down_x[i]:
            down_y2.append(down_y[i])
        else:
            down_y2.append(polyval(polyfit(down_x[i], down_y[i], 2), xx))
    return xx, up_y2, down_y2


def update(up, down, all_codes, x, y, yy, surf1, ax1, surf2, ax2, axs):
    azim = 15
    while True:
        sleep(3)  # 每隔3秒刷新一次
        up_x, up_y, up_vega, down_x, down_y, down_vega = get_data(up, down, all_codes)
        xx, up_y2, down_y2 = fit(up_x, up_y, down_x, down_y)
        surf1.remove()
        azim += 15
        if azim > 360:
            azim = 0
        ax1.view_init(ELEV, azim)
        surf1 = ax1.plot_surface(x, y, array(up_y2) * 100.0, rstride=1, cstride=1, cmap='rainbow')
        surf2.remove()
        ax2.view_init(ELEV, azim)
        surf2 = ax2.plot_surface(x, y, array(down_y2) * 100.0, rstride=1, cstride=1, cmap='rainbow')
        for i in yy:
            axs[0].lines.remove(global_lines[0][i])
            axs[1].lines.remove(global_lines[1][i])
            axs[2].lines.remove(global_lines[2][i])
            axs[3].lines.remove(global_lines[3][i])
        for i in yy:
            global_lines[i] = []
        for i in yy:
            global_lines[0].append(axs[0].plot(up_x[i], array(up_y[i]) * 100.0, COLORS[i])[0])
            global_lines[1].append(axs[1].plot(down_x[i], array(up_vega[i]), COLORS[i])[0])
            global_lines[2].append(axs[2].plot(down_x[i], array(down_y[i]) * 100.0, COLORS[i])[0])
            global_lines[3].append(axs[3].plot(down_x[i], array(down_vega[i]), COLORS[i])[0])
        plt.draw()


def main():
    up, down, all_codes, dates = get_codes()
    dates_label = ',,'.join(dates).split(',')
    axs = []
    up_x, up_y, up_vega, down_x, down_y, down_vega = get_data(up, down, all_codes)
    xx, up_y2, down_y2 = fit(up_x, up_y, down_x, down_y)
    yy = list(range(len(up_y2)))
    x, y = meshgrid(xx, yy)
    fig = plt.figure(figsize=(13, 4.5))
    gs = gridspec.GridSpec(2, 6, figure=fig)
    ax1 = fig.add_subplot(gs[:, :2], projection='3d')
    ax1.view_init(ELEV, 0)
    surf1 = ax1.plot_surface(x, y, array(up_y2) * 100.0, rstride=1, cstride=1, cmap='rainbow')
    ax1.set_yticklabels(dates_label)
    ax1.set_xlabel('strike price')
    ax1.set_ylabel('expiration date')
    ax1.set_zlabel('implied volatility(%)')
    ax1.set_title('Call Option')
    ax = fig.add_subplot(gs[:1, 2:3])
    axs.append(ax)
    for i in yy:
        line, = ax.plot(up_x[i], array(up_y[i]) * 100.0, COLORS[i])
        global_lines[0].append(line)
    ax.set_xlabel('strike price')
    ax.set_ylabel('implied volatility(%)')
    ax.legend(dates, fontsize='xx-small')
    ax = fig.add_subplot(gs[1:2, 2:3])
    axs.append(ax)
    for i in yy:
        line, = ax.plot(up_x[i], up_vega[i], COLORS[i])
        global_lines[1].append(line)
    ax.set_xlabel('strike price')
    ax.set_ylabel('vega')
    ax.legend(dates, fontsize='xx-small')
    # ----------------------------------------------------------------------
    ax2 = fig.add_subplot(gs[:, 3:5], projection='3d')
    ax2.view_init(ELEV, 0)
    surf2 = ax2.plot_surface(x, y, array(down_y2) * 100.0, rstride=1, cstride=1, cmap='rainbow')
    ax2.set_yticklabels(dates_label)
    ax2.set_xlabel('strike price')
    ax2.set_ylabel('expiration date')
    ax2.set_zlabel('implied volatility(%)')
    ax2.set_title('Put Option')
    ax = fig.add_subplot(gs[:1, 5:6])
    axs.append(ax)
    for i in yy:
        line, = ax.plot(down_x[i], array(down_y[i]) * 100.0, COLORS[i])
        global_lines[2].append(line)
    ax.set_xlabel('strike price')
    ax.set_ylabel('implied volatility(%)')
    ax.legend(dates, fontsize='xx-small')
    ax = fig.add_subplot(gs[1:2, 5:6])
    axs.append(ax)
    for i in yy:
        line, = ax.plot(down_x[i], down_vega[i], COLORS[i])
        global_lines[3].append(line)
    ax.set_xlabel('strike price')
    ax.set_ylabel('vega')
    ax.legend(dates, fontsize='xx-small')
    plt.tight_layout()
    thread = Thread(target=update, args=(up, down, all_codes, x, y, yy, surf1, ax1, surf2, ax2, axs))
    thread.setDaemon(True)
    thread.start()
    plt.show()


if __name__ == '__main__':
    main()