蒙特卡洛模拟几何布朗运动作为期权定价基础

背景

        在本学院期权、期货与衍生产品课程中,老师介绍了维纳过程、伊藤引理与几何布朗运动等,我颇感兴趣,又恰好辅修了计算机,于是自主完成了基于Python的几何布朗运动的动态蒙特卡洛模拟及最新时点数据的简单统计,利用matplotlib库完成了其可视化,为了使这一程序有更大的应用空间,同时利用tkinter库构建了对应的UI界面以合法可控调用函数,并将该程序的简单使用教程放入其中,程序所有内容采用英文。

具体工作
1.几何布朗运动的动态蒙特卡洛模拟

        在与老师交流后,我决定使用解析形式与离散形式两种模式进行过程模拟,二者公式如下:

离散的(Discrete):

S (t+\Delta t) = S(t) \cdot (1 + \mu \cdot \Delta t + \sigma \cdot \varepsilon \cdot \sqrt{\Delta t}) 

解析的(Analytic):

S(t+\Delta t) = S(t) \cdot \exp ((\mu - \frac{1}{2} \cdot {​{ \sigma}^{2}}) \cdot \Delta t + \sigma \cdot \varepsilon \cdot \sqrt{\Delta t})

        为了实现动态可视化,我采取循环清除画布并在添加新数据点后重绘图线的方式实现,实现了下图效果:

        从过程取样与最新时点统计中均可观察到对数正态分布的特征。

        本部分基本代码如下,基本要点已全部注释:

def Simulation(S0 = 100, parameter_type = 0, miu = 0.15, sigma = 0.3, step = 5, year_type = 0, T = 100, concurrency = 100, mode = 0, fix_y = 0):
    #parameter_type: 0-yearly, 1-daily
    #year_type: 0-trading, 1-natural
    #step: days
    #T: days
    #mode: 0-both, 1-Discrete, 2-Analytic

    #Discrete and Analytic
    if mode == 0:
        #create 4 figures for line graph and histogram
        fig, axes = plt.subplots(2, 2, figsize=(16, 9))
        fig.suptitle('Monte Calro Simulation of Geometric Brownian Motion')

        #parameters
        t = 0
        if parameter_type == 0:
            if year_type == 0:
                total_days = 242
            elif year_type == 1:
                total_days = 365
        elif parameter_type == 1:
            total_days = 1
        dt = step / total_days

        #create concurrent lines and histogram data
        x = []
        concurrent_lines1 = [[] for i in range(concurrency)]
        concurrent_lines2 = [[] for i in range(concurrency)]
        histogram_data1 = []
        histogram_data2 = []

        #create color list
        color_list = random.choices(list(mcolors.XKCD_COLORS.keys()), k = concurrency)

        #circulation
        while (t < T + step):
            #clean figures
            axes[0, 0].cla()
            axes[0, 1].cla()
            axes[1, 0].cla()
            axes[1, 1].cla()

            #add the value range limitation of the axes
            axes[0, 0].set_xlim(0, T)
            axes[1, 0].set_xlim(0, T)
            if fix_y != 0:
                axes[0, 0].set_ylim(0, fix_y)
                axes[1, 0].set_ylim(0, fix_y)

            #update concurrent lines
            if t == 0:
                x.append(t)
                for i in range(concurrency):
                    concurrent_lines1[i].append(S0)
                    concurrent_lines2[i].append(S0)
            else:
                x.append(t)
                for i in range(concurrency):
                    epsilon1 = random.gauss(0, 1)
                    epsilon2 = random.gauss(0, 1)
                    concurrent_lines1[i].append(concurrent_lines1[i][-1] * (1 + miu * dt + sigma * epsilon1 * math.sqrt(dt)))
                    concurrent_lines2[i].append(concurrent_lines2[i][-1] * math.exp((miu - sigma ** 2 / 2) * dt + sigma * epsilon2 * math.sqrt(dt)))

            #update histogram data
            histogram_data1 = []
            histogram_data2 = []
            for i in range(concurrency):
                histogram_data1.append(concurrent_lines1[i][-1])
                histogram_data2.append(concurrent_lines2[i][-1])

            #draw line graph
            for i in range(concurrency):
                axes[0, 0].plot(x, concurrent_lines1[i], color = mcolors.XKCD_COLORS[color_list[i]], linewidth = 1, linestyle = "-")
                axes[1, 0].plot(x, concurrent_lines2[i], color = mcolors.XKCD_COLORS[color_list[i]], linewidth = 1, linestyle = "-")

            #draw histogram
            if fix_y == 0:
                axes[0, 1].hist(histogram_data1, bins = concurrency)
                axes[1, 1].hist(histogram_data2, bins = concurrency)
            else:
                axes[0, 1].hist(histogram_data1, bins = concurrency, range = (0, fix_y))
                axes[1, 1].hist(histogram_data2, bins = concurrency, range = (0, fix_y))

            #add title
            axes[0, 0].set_title("Discrete: the whole process and the latest point-in-time data", x = 0.5, y = 1)
            mean1 = np.mean(histogram_data1)
            std1 = np.std(histogram_data1)
            max_data1 = max(histogram_data1)
            min_data1 = min(histogram_data1)
            axes[0, 1].set_title(" mean: %.4f, std: %.4f, max: %.4f, min: %.4f"%(mean1, std1, max_data1, min_data1), x = 0.5, y = 1)
            axes[1, 0].set_title("Analytic: the whole process and the latest point-in-time data", x = 0.5, y = 1)
            mean2 = np.mean(histogram_data2)
            std2 = np.std(histogram_data2)
            max_data2 = max(histogram_data2)
            min_data2 = min(histogram_data2)
            axes[1, 1].set_title(" mean: %.4f, std: %.4f, max: %.4f, min: %.4f"%(mean2, std2, max_data2, min_data2), x = 0.5, y = 1)

            #pause for a short time
            plt.pause(0.001)

            #change t
            t += step
2.UI界面构建

       使用tkinter库进行UI界面构建,采取grid函数进行界面布局,实现了对Simulation函数中所有参数的控制,并添加了Simulation按钮来实现对Simulation函数的可控调用。同时,为了让本程序更加用户可用,添加了Tutorial按钮来打开对本程序各个参数的介绍,实现了下图效果:

        本部分基本代码如下:

def sgui():
    #create gui
    window1 = tk.Tk()
    window1.title('Monte Calro Simulation of Geometric Brownian Motion')

    #frame-1: introduction
    lb = tk.Label(window1, text = 'This is a simple application to perform Monte Carlo simulations of geometric Brownian motion based on python.', font = ("微软雅黑", 12))
    lb.grid(column = 0, columnspan = 3, row = 0, sticky = 'nswe', padx = 5, pady = 15)

    #frame0: button to tutorial
    bt2 = tk.Button(window1, text = 'Tutorial', width = 10, command = show_tutorial, font = ("微软雅黑", 12))
    bt2.grid(column = 3, row = 0, sticky = 'nswe', padx = 5, pady = 15)

    #frame1: parameters
    label1 = tk.Label(window1, text = 'Parameters', font = ("微软雅黑", 15), anchor = 'e')
    label1.grid(column = 0, row = 1, sticky = 'nswe', padx = 5, pady = 5)

    #frame2: S0
    label2 = tk.Label(window1, text = 'S0: ', font = ("微软雅黑", 12), anchor = 'e')
    label2.grid(column = 0, row = 2, sticky='nswe', padx = 5, pady = 5)
    text2 = tk.Entry(window1)
    text2.grid(column = 1, row = 2, padx = 5, pady = 5)

    #frame3: parameter_type
    label3 = tk.Label(window1, text = 'parameter_type: ', font = ("微软雅黑", 12), anchor = 'e')
    label3.grid(column = 0, row = 3, sticky = 'nswe', padx = 5, pady = 5)
    checkVar3_1 = tk.StringVar(value="0")
    checkVar3_0 = tk.StringVar(value="1")
    check_button3_1 = tk.Checkbutton(window1, text = 'daily', variable = checkVar3_1, font = ("微软雅黑", 12))
    check_button3_0 = tk.Checkbutton(window1, text = 'yearly', variable = checkVar3_0, font = ("微软雅黑", 12))
    check_button3_1.grid(column = 2, row = 3, sticky = 'nswe', padx = 5, pady = 5)
    check_button3_0.grid(column = 1, row = 3, sticky = 'nswe', padx = 5, pady = 5)

    #frame4: miu
    label4 = tk.Label(window1, text = 'miu: ', font = ("微软雅黑", 12), anchor = 'e')
    label4.grid(column = 0, row = 4, sticky = 'nswe', padx = 5, pady = 5)
    text4 = tk.Entry(window1)
    text4.grid(column = 1, row = 4, padx = 5, pady = 5)

    #frame5: sigma
    label5 = tk.Label(window1, text = 'sigma: ', font = ("微软雅黑", 12), anchor = 'e')
    label5.grid(column = 0, row = 5, sticky = 'nswe', padx = 5, pady = 5)
    text5 = tk.Entry(window1)
    text5.grid(column = 1, row = 5, padx = 5, pady = 5)

    #frame6: step
    label6 = tk.Label(window1, text = 'step: ', font = ("微软雅黑", 12), anchor = 'e')
    label6.grid(column = 0, row = 6, sticky = 'nswe', padx = 5, pady = 5)
    text6 = tk.Entry(window1)
    text6.grid(column = 1, row = 6, padx = 5, pady = 5)
    label6_1 = tk.Label(window1, text = 'days', font = ("微软雅黑", 12), anchor = 'w')
    label6_1.grid(column = 2, row = 6, sticky = 'nswe', padx = 5, pady = 5)

    #frame7: year_type
    label7 = tk.Label(window1, text = 'year_type: ', font = ("微软雅黑", 12), anchor = 'e')
    label7.grid(column = 0, row = 7, sticky = 'nswe', padx = 5, pady = 5)
    checkVar7_1 = tk.StringVar(value="0")
    checkVar7_0 = tk.StringVar(value="1")
    check_button7_1 = tk.Checkbutton(window1, text = 'calendar', variable = checkVar7_1, font = ("微软雅黑", 12))
    check_button7_0 = tk.Checkbutton(window1, text = 'trading', variable = checkVar7_0, font = ("微软雅黑", 12))
    check_button7_1.grid(column = 2, row = 7, sticky = 'nswe', padx = 5, pady = 5)
    check_button7_0.grid(column = 1, row = 7, sticky = 'nswe', padx = 5, pady = 5)

    #frame8: T
    label8 = tk.Label(window1, text = 'T: ', font = ("微软雅黑", 12), anchor = 'e')
    label8.grid(column = 0, row = 8, sticky = 'nswe', padx = 5, pady = 5)
    text8 = tk.Entry(window1)
    text8.grid(column = 1, row = 8, padx = 5, pady = 5)
    label8_1 = tk.Label(window1, text = 'days', font = ("微软雅黑", 12), anchor = 'w')
    label8_1.grid(column = 2, row = 8, sticky = 'nswe', padx = 5, pady = 5)

    #frame9: concurrency
    label9 = tk.Label(window1, text = 'concurrency: ', font = ("微软雅黑", 12), anchor = 'e')
    label9.grid(column = 0, row = 9, sticky = 'nswe', padx = 5, pady = 5)
    text9 = tk.Entry(window1)
    text9.grid(column = 1, row = 9, padx = 5, pady = 5)

    #frame10: mode
    label10 = tk.Label(window1, text = 'mode: ', font = ("微软雅黑", 12), anchor = 'e')
    label10.grid(column = 0, row = 10, sticky = 'nswe', padx = 5, pady = 5)
    radio10_value = tk.IntVar()
    radio_button10_2 = tk.Radiobutton(window1, text = 'Analytic', variable = radio10_value, value = 2, font = ("微软雅黑", 12))
    radio_button10_1 = tk.Radiobutton(window1, text = 'Discrete', variable = radio10_value, value = 1, font = ("微软雅黑", 12))
    radio_button10_0 = tk.Radiobutton(window1, text = 'Discrete and Analytic', variable = radio10_value, value = 0, font = ("微软雅黑", 12))
    radio_button10_2.grid(column = 3, row = 10, sticky = 'nswe', padx = 5, ipadx = 30, pady = 5)
    radio_button10_1.grid(column = 2, row = 10, sticky = 'nswe', padx = 5, pady = 5)
    radio_button10_0.grid(column = 1, row = 10, sticky = 'nswe', padx = 5, pady = 5)

    #frame11: fix_y
    label11 = tk.Label(window1, text = 'fix_y: ', font = ("微软雅黑", 12), anchor = 'e')
    label11.grid(column = 0, row = 11, sticky = 'nswe', padx = 5, pady = 5)
    checkVar11 = tk.StringVar(value="1")
    check_button11 = tk.Checkbutton(window1, text = 'flexible', variable = checkVar11, font = ("微软雅黑", 12))
    check_button11.grid(column = 1, row = 11, sticky = 'nswe', padx = 5, pady = 5)
    text11 = tk.Entry(window1)
    text11.grid(column = 2, row = 11, padx = 5, pady = 5)

    #frame12: button to simulation
    button12 = tk.Button(window1, text = 'Simulation', width = 10, command = lambda: \
    get_parameters(text2, checkVar3_0, checkVar3_1, text4, text5, text6, \
    checkVar7_0, checkVar7_1, text8, text9, radio10_value, checkVar11, text11), font = ("微软雅黑", 12))
    button12.grid(column = 1, columnspan = 2, row = 12, sticky = 'nswe', padx = 5, pady = 5)
    
    tk.mainloop()
3.Tutorial函数实现教程

        本部分主要为教程文本内容,基本代码如下:

def show_tutorial():
    tk.messagebox.showinfo(title = 'Tutorial', \
message = 'This is a simple application to perform Monte Carlo simulations of geometric Brownian motion based on python. \n\n\
Please read the following text carefully to learn how to use it. \n\n\
Here are important parameters that you could use to control the simulation processes, the default values are from textbook\'s example: \n\n\
1.S0: The initial price of the asset which should be a floating-point number or a integer. The default value is 100.0. \n\n\
2.parameter_type: The type of value you will enter for the \'miu\' and \'sigma\'——yearly or daily. The default value is \'yearly\'. \n\n\
3.miu: The mean of the asset\'s change ratio which should be a floating-point number or a integer. The default value is 0.15. \n\n\
4.sigma: The standard deviation of the asset\'s change ratio which should be a positive floating-point number or a positive integer. The default value is 0.30. \n\n\
5.step: The time interval between two records in samples which should be a positive floating-point number or a positive integer, in days. The default value is 5.0. \n\n\
6.year_type : The type of measures on how many days a year have——trading days or calendar days. The default value is \'trading\'. \n\n\
7.T: The total time for the entire simulation process which should be a positive floating-point number or a positive integer, in days. The default value is 100.0. \n\n\
8.concurrency: The number of process samples per simulation which should be a positive integer. The default value is 100. \n\n\
9.mode:The form of geometric Brownian motion according to the following formulas——Discrete and Analytic, Discrete, or Analytic. The default value is \'Discrete and Analytic\'.\n\
Discrete: S(t+dt) = S(t) * (1 + miu * dt + sigma * epsilon * dt **(1/2))\n\
Analytic: S(t+dt) = S(t) * exp((miu - (1/2) * sigma ** 2) * dt + sigma * epsilon * dt **(1/2))\n\n\
10.fix_y: Whether the range of asset prices displayed is fixed, if fixed——the range is (0, fix_y), fix_y should be a positive floating-point number or a positive integer, else——flexible. The default value is \'flexible\'.\n\n\
If you are sure that you have read the above text and understand how to use this application, please feel free to use it. \n\n\
However, if you still have questions about how to use it, or are curious about its code, or want to experience more features, \
you could contact me(Xie Yuhang) by any way that you already know or via email \'xieyh37@mail2.sysu.edu.cn\', or just try it first.')
4.get_parameter函数实现Simulation函数合法调用

        通过try except结构进行非法输入发现并向用户提示,在保证所有参数合法后传入并调用Simulation函数。

def get_parameters(text2, checkVar3_0, checkVar3_1, text4, text5, text6, \
    checkVar7_0, checkVar7_1, text8, text9, radio10_value, radio11_value, text11):

    #get S0
    string2 = text2.get()
    try:
        if string2 == '':
            S0 = 100.0
        else:
            S0 = float(string2)
    except:
        S0 = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'S0\' is invalid.')

    #get parameter_type
    int3_0 = int(checkVar3_0.get())
    int3_1 = int(checkVar3_1.get())
    if int3_0 + int3_1 == 1:
        if int3_0 == 1:
            parameter_type = 0
        else:
            parameter_type = 1
    else:
        parameter_type = 'wrong'
        tk.messagebox.showerror(message = 'Only 1 option could be checked for \'parameter_type\'.')

    #get miu
    string4 = text4.get()
    try:
        if string4 == '':
            miu = 0.15
        else:
            miu = float(string4)
    except:
        miu = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'miu\' is invalid.')

    #get sigma
    string5 = text5.get()
    try:
        if string5 == '':
            sigma = 0.30
        else:
            sigma = float(string5)
            if sigma < 0:
                sigma = 'wrong'
                tk.messagebox.showerror(message = 'The input of \'sigma\' is invalid.')
    except:
        sigma = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'sigma\' is invalid.')

    #get step
    string6 = text6.get()
    try:
        if string6 == '':
            step = 5.0
        else:
            step = float(string6)
            if step < 0:
                step = 'wrong'
                tk.messagebox.showerror(message = 'The input of \'step\' is invalid.')
    except:
        step = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'step\' is invalid.')

    #get year_type
    int7_0 = int(checkVar7_0.get())
    int7_1 = int(checkVar7_1.get())
    if int7_0 + int7_1 == 1:
        if int7_0 == 1:
            year_type = 0
        else:
            year_type = 1
    elif parameter_type == 1:
        year_type = 0
    else:
        year_type = 'wrong'
        tk.messagebox.showerror(message = 'Only 1 option could be checked for \'year_type\'.')

    #get T
    string8 = text8.get()
    try:
        if string8 == '':
            T = 100.0
        else:
            T = float(string8)
            if T < 0:
                T = 'wrong'
                tk.messagebox.showerror(message = 'The input of \'T\' is invalid.')
    except:
        T = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'T\' is invalid.')

    #get concurrency
    string9 = text9.get()
    try:
        if string9 == '':
            concurrency = 100
        else:
            concurrency = int(string9)
            if concurrency <= 0:
                concurrency = 'wrong'
                tk.messagebox.showerror(message = 'The input of \'concurrency\' is invalid.')
    except:
        concurrency = 'wrong'
        tk.messagebox.showerror(message = 'The input of \'concurrency\' is invalid.')
        
    #get mode
    int10 = int(radio10_value.get())
    mode = int10

    #get fix_y
    int11_0 = int(radio11_value.get())
    string11_1 = text11.get()
    if int11_0 == 1:
        fix_y = 0
    else:
        fix_y = float(string11_1)
        if fix_y < 0:
            fix_y = 'wrong'
            tk.messagebox.showerror(message = 'The input of \'fix_y\' is invalid.')

    #Simulation
    try:
        Simulation(S0, parameter_type, miu, sigma, step, year_type, T, concurrency, mode, fix_y)
    except:
        pass

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值