背景
在本学院期权、期货与衍生产品课程中,老师介绍了维纳过程、伊藤引理与几何布朗运动等,我颇感兴趣,又恰好辅修了计算机,于是自主完成了基于Python的几何布朗运动的动态蒙特卡洛模拟及最新时点数据的简单统计,利用matplotlib库完成了其可视化,为了使这一程序有更大的应用空间,同时利用tkinter库构建了对应的UI界面以合法可控调用函数,并将该程序的简单使用教程放入其中,程序所有内容采用英文。
具体工作
1.几何布朗运动的动态蒙特卡洛模拟
在与老师交流后,我决定使用解析形式与离散形式两种模式进行过程模拟,二者公式如下:
离散的(Discrete):
解析的(Analytic):
为了实现动态可视化,我采取循环清除画布并在添加新数据点后重绘图线的方式实现,实现了下图效果:
从过程取样与最新时点统计中均可观察到对数正态分布的特征。
本部分基本代码如下,基本要点已全部注释:
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