├── README.md └── autocall4.6优化计算单列delta函数.py /README.md: -------------------------------------------------------------------------------- 1 | # snowball_option 2 | This porject is for recording my study path in snowball option pricing and its delta hedging 3 | -------------------------------------------------------------------------------- /autocall4.6优化计算单列delta函数.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 8 14:21:29 2021 4 | 5 | @author: Mr.P 6 | """ 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import matplotlib.pyplot as plt 11 | from time import * 12 | from joblib import Parallel, delayed 13 | 14 | mc_para = {} 15 | mc_para['r'] = 0.05 16 | mc_para['vol'] = 0.23 17 | mc_para['T'] = 1 18 | mc_para['N'] = 250 19 | mc_para['simulations'] = 1000 20 | mc_para['dt'] = mc_para['T']/mc_para['N'] 21 | mc_para['mu'] = 0 22 | 23 | basic_para = {} 24 | basic_para['s0'] = 10000 25 | basic_para['ki'] = basic_para['s0']*0.8 26 | basic_para['ko'] = basic_para['s0']*1 27 | basic_para['coupon_rate'] = 0.2 #敲出票息(年化) 28 | basic_para['dividend'] = 0.2 #红利票息(年化) 29 | basic_para['period'] = 40 #两个月观察一次,一个月20个交易日 30 | basic_para['observe_point'] = [k for k in range(40,mc_para['N'],basic_para['period'])] 31 | basic_para['observe_point_copy'] = [k for k in range(40,mc_para['N'],basic_para['period'])] 32 | basic_para['calender'] = [k for k in range(mc_para['N'])] 33 | basic_para['buy_cost'] = 0.0005 #delta对冲买入现货手续费 34 | basic_para['sell_cost'] = 0.0009 #delta对冲卖出现货手续费 35 | basic_para['F'] = 10000 #面值 36 | basic_para['init_net_value'] = 100000000 #计算对冲收益时初始账户净值 37 | basic_para['capital_cost_rate'] = 0.05 #资金使用成本--百分比 38 | basic_para['trade_cost_rate'] = 0.00015 #交易成本--绝对值 39 | basic_para['path_count'] = 0 40 | np.random.seed(10) 41 | 42 | class AutoCall(): 43 | def __init__(self,basic_para,mc_para): 44 | self.mc_parameter = mc_para #蒙特卡洛参数 45 | self.basic_parameter = basic_para #基础参数 46 | self.stock_path_1 = pd.DataFrame() #用于保存股票价格路径的dataframe 47 | self.stock_path_2 = pd.DataFrame() #用于保存股票价格路径的dataframe 48 | self.cum_rtn_1 = pd.DataFrame() #用于保存累计收益率路径的dataframe 49 | self.cum_rtn_2 = pd.DataFrame() #用于保存累计收益率路径的dataframe 50 | self.cum_rtn_1_copy = pd.DataFrame() #用于保存累计收益率路径的dataframe 51 | self.cum_rtn_2_copy = pd.DataFrame() #用于保存累计收益率路径的dataframe 52 | self.random_r_cum_1 = np.zeros([self.mc_parameter['N'],self.mc_parameter['simulations']]) 53 | self.random_r_cum_2 = np.zeros([self.mc_parameter['N'],self.mc_parameter['simulations']]) 54 | self.return_lst = [] 55 | self.ob_copy = [] 56 | self.ob_copy.extend(self.basic_parameter['observe_point']) 57 | def monte_carlo(self): 58 | N = self.mc_parameter['N'] 59 | simulations = self.mc_parameter['simulations'] 60 | vol = self.mc_parameter['vol'] 61 | dt = self.mc_parameter['dt'] 62 | mu = self.mc_parameter['mu'] 63 | random_r = np.random.normal(0, 1, (N, simulations)) 64 | panduan_up = (0.1 - mu * dt) / (vol * np.sqrt(dt)) # 将收益率序列用涨跌幅规范 65 | panduan_low = (-0.1 - mu * dt) / (vol * np.sqrt(dt)) 66 | random_r[np.where(random_r > panduan_up)] = panduan_up 67 | random_r[np.where(random_r < panduan_low)] = panduan_low 68 | random_r_cum_1 = np.cumprod(1 + mu * dt + vol * np.sqrt(dt) * random_r, axis=0) 69 | random_r_cum_2 = np.cumprod(1 + mu * dt + vol * np.sqrt(dt) * -random_r, axis=0) 70 | #加上第一日收益率 71 | head = np.array([[1]*simulations]) 72 | random_r_cum_1 = np.vstack((head,random_r_cum_1)) 73 | random_r_cum_2 = np.vstack((head,random_r_cum_2)) 74 | self.random_r_cum_1 = random_r_cum_1 75 | self.random_r_cum_2 = random_r_cum_2 76 | df1 = pd.DataFrame(random_r_cum_1) 77 | df2 = pd.DataFrame(random_r_cum_2) 78 | self.cum_rtn_1 = df1.copy(deep=True) 79 | self.cum_rtn_2 = df2.copy(deep=True) 80 | self.cum_rtn_1_copy = df1.copy(deep=True) 81 | self.cum_rtn_2_copy = df2.copy(deep=True) 82 | def get_stock_path(self,s0): 83 | self.stock_path_1 = s0*self.cum_rtn_1 84 | self.stock_path_2 = s0*self.cum_rtn_2 85 | 86 | def spath_expect_payoff(self,spath): 87 | ''' 88 | 参数准备 89 | ''' 90 | s0 = basic_para['s0'] 91 | r = self.mc_parameter['r'] 92 | N = self.mc_parameter['N'] 93 | dt = self.mc_parameter['dt'] 94 | dividend = self.basic_parameter['dividend'] 95 | coupon_rate = self.basic_parameter['coupon_rate'] 96 | T = self.mc_parameter['T'] 97 | F = self.basic_parameter['F'] 98 | discount_factor = np.exp(-r*N*dt) 99 | simulations = self.mc_parameter['simulations'] 100 | ki = self.basic_parameter['ki'] 101 | ko = self.basic_parameter['ko'] 102 | ####################################################################### 103 | ob = self.basic_parameter['observe_point'] 104 | spath_value = spath.values 105 | path_ind = spath.index.tolist() 106 | # print(type(path_value)) 107 | path_col = [i for i in range(simulations)] 108 | # ob_point_price = spath_value[ob] 109 | ob_point_price = spath.loc[ob].values 110 | # print(ob) 111 | if len(ob) != 0: 112 | ####################################################################### 113 | #spath_expect_payoff要加一个判断ob_point是不是空的功能,为了配合get_se_deltas() 114 | #计算最后一个观察日至到期日之间的收益 115 | #最后一个观察日至到期日之间,肯定不会有敲出了,故在else情况中ever_out_payoff设置为0 116 | ####################################################################### 117 | ever_out = list(np.where(np.max(ob_point_price,axis=0)>=ko)[0]) 118 | # path_ob_point = pd.DataFrame(spath_value).iloc[ob,ever_out] 119 | # path_ob_point = pd.DataFrame(spath_value).loc[ob,ever_out] 120 | path_ob_point = spath.loc[ob,ever_out] 121 | path_ob_point = path_ob_point > ko 122 | first_out_date = path_ob_point.idxmax() 123 | out_discount_factor = np.exp(-r*first_out_date*dt) 124 | payoff_ever_out = (1+first_out_date/N*coupon_rate)*F*out_discount_factor 125 | payoff_ever_out = payoff_ever_out.sum() 126 | 127 | ever_in = np.where(np.min(spath_value,axis=0)<=ki)[0] 128 | ever_io = list(set(ever_in)&set(ever_out)) 129 | only_in = list(set(ever_in)^set(ever_io)) 130 | payoff_only_in = np.exp(-r*T)*spath_value[-1,only_in].sum() 131 | noi = list(set(path_col)^set(ever_out)^set(only_in)) 132 | payoff_noi = len(noi)*(1+dividend)*F*np.exp(-r*T) 133 | payoff_expect = (payoff_ever_out + payoff_noi + payoff_only_in)/simulations 134 | else: 135 | payoff_ever_out = 0 136 | ever_out = [] 137 | ever_in = np.where(np.min(spath_value,axis=0)<=ki)[0] 138 | ever_io = list(set(ever_in)&set(ever_out)) 139 | only_in = list(set(ever_in)^set(ever_io)) 140 | payoff_only_in = np.exp(-r*T)*spath_value[-1,only_in].sum() 141 | noi = list(set(path_col)^set(ever_out)^set(only_in)) 142 | payoff_noi = len(noi)*(1+dividend)*F*np.exp(-r*T) 143 | payoff_expect = (payoff_ever_out + payoff_noi + payoff_only_in)/simulations 144 | return {'expect_payoff':payoff_expect} 145 | 146 | def customer_expect_payoff(self): 147 | sp_1 = self.stock_path_1 148 | ep_1 = self.spath_expect_payoff(sp_1)['expect_payoff'] 149 | sp_2 = self.stock_path_2 150 | ep_2 = self.spath_expect_payoff(sp_2)['expect_payoff'] 151 | ####################################################################### 152 | expect_payoff = (ep_1 + ep_2)/2 153 | # print('ep_1:',ep_1,'ep_2:',ep_2,'expect_payoff',expect_payoff) 154 | return {'expect_payoff':expect_payoff} 155 | 156 | 157 | def get_delta(self,s,ds): 158 | su = s+ds 159 | self.get_stock_path(su) 160 | pup = self.customer_expect_payoff()['expect_payoff'] 161 | sd = s-ds 162 | self.get_stock_path(sd) 163 | pdown = self.customer_expect_payoff()['expect_payoff'] 164 | delta = (pup-pdown)/(2*ds) 165 | return {'delta':delta,'su':su,'sd':sd,'pup':pup,'pdown':pdown} 166 | 167 | def get_gamma(self,s,ds): 168 | self.get_stock_path(s) 169 | p = self.customer_expect_payoff()['expect_payoff'] 170 | su = s+ds 171 | self.get_stock_path(su) 172 | pup = self.customer_expect_payoff()['expect_payoff'] 173 | sd = s-ds 174 | self.get_stock_path(sd) 175 | pdown = self.customer_expect_payoff()['expect_payoff'] 176 | gamma = (pup+pdown-2*p)/(ds**2) 177 | return {'gamma':gamma,'s':s,'su':su,'sd':sd,'p':p,'pup':pup,'pdown':pdown} 178 | def get_se_deltas(self,se,ds): 179 | deltas_lst = [] 180 | ob_point = self.ob_copy 181 | #由于每次调用,会删改self.basic_parameter['observe_point'],所以每次调用前要重置观察点列表 182 | if len(self.basic_parameter['observe_point'])!=len(self.ob_copy): 183 | self.basic_parameter['observe_point'].clear() 184 | self.basic_parameter['observe_point'].extend(self.ob_copy) 185 | for i in range(len(se)): 186 | sprice = se[i] 187 | np.random.seed(10) 188 | # self.monte_carlo() 189 | self.cum_rtn_1 = self.cum_rtn_1_copy.copy(deep=True) 190 | self.cum_rtn_2 = self.cum_rtn_2_copy.copy(deep=True) 191 | #最后一个观察日后,到期日之前,此时self.basic_parameter['observe_point']已经空了,但还要继续计算delta 192 | if len(self.basic_parameter['observe_point']) !=0: 193 | if i == self.basic_parameter['observe_point'][0] and sprice < self.basic_parameter['ko']: 194 | self.basic_parameter['observe_point'].pop(0) 195 | # print(self.basic_parameter['observe_point']) 196 | #第五天则从第五天开始截取收益率矩阵,股价矩阵也对等的只有245天的数据 197 | self.cum_rtn_1 = self.cum_rtn_1.iloc[i:,:] 198 | self.cum_rtn_2 = self.cum_rtn_2.iloc[i:,:] 199 | tic1 = time() 200 | delta = self.get_delta(sprice,ds)['delta'] 201 | tic2 = time() 202 | # print('计算单日delta耗时:',tic2-tic1) 203 | # print('delta%d=%.4f计算完毕'%(i,delta)) 204 | deltas_lst.append(delta) 205 | #i%40==0 和 i == self.basic_parameter['observe_point'][0]都能用来判断是否是敲出观察日 206 | #如果观察日敲出了,后续delta全部赋值为0不用再计算了,第一日不是观察日所以要加上i!=0 207 | #最后一个观察日后,到期日之前,此时self.basic_parameter['observe_point']已经空了,但还要继续计算delta 208 | if len(self.basic_parameter['observe_point']) !=0: 209 | #如果i是观察日并且i这天敲出了后面就不用再计算了 210 | if i in self.basic_parameter['observe_point'] and sprice > self.basic_parameter['ko'] and i!=0: 211 | remain_deltas = [0 for i in range(len(se)-len(deltas_lst))] 212 | deltas_lst += remain_deltas 213 | # ob_ind = ob_point.index(i) 214 | # ob_day = int(ob_ind) 215 | # ob_day += 1 216 | # print('第%d个观察日敲出了,后续deltas为0'%ob_day) 217 | break 218 | print('单路径delta计算完毕') 219 | return deltas_lst 220 | 221 | def get_df_deltas(self,df,ds): 222 | deltas_dic = {} 223 | for i in df.columns.tolist(): 224 | tick1 = time() 225 | se = df[i] 226 | temp = self.get_se_deltas(se,ds) 227 | deltas_dic[i]=temp 228 | ith = int(i) 229 | ith += 1 230 | tick2 = time() 231 | time_cost = tick2 - tick1 232 | print('第%d条路径delta计算完毕,耗时:%.6f'%(ith,time_cost)) 233 | deltas_df = pd.DataFrame(deltas_dic) 234 | return deltas_df 235 | 236 | def cal_se_hedge(self,stock_se,delta_se): 237 | ''' 238 | 存数据所需list初始化 239 | ''' 240 | stock_lst = stock_se.values.tolist() 241 | delta_lst = delta_se.values.tolist() 242 | net_value_lst = [] #净值 243 | delta_worth_lst = [] #delta市值 244 | stock_num_lst = [] # 对冲所需股票数 245 | trade_cost_lst = [] #交易费用 246 | trade_chg_lst = [] #交易净流出 247 | stock_value_lst = [] #股票价值 248 | cash_lst = [] #现金 249 | present_worth_lst = [] 250 | capital_use = [] 251 | ''' 252 | 第一日参数计算 253 | ''' 254 | init_net_value = self.basic_parameter['init_net_value'] 255 | trade_cost_rate = self.basic_parameter['trade_cost_rate'] 256 | init_present_worth = init_net_value 257 | init_delta_worth = delta_lst[0]*init_net_value 258 | init_stock_num = init_delta_worth/stock_lst[0] 259 | init_trade_cost = np.abs(init_stock_num*stock_lst[0]*trade_cost_rate) 260 | init_trade_chg = init_stock_num*stock_lst[0] + init_trade_cost 261 | init_stock_value = init_stock_num*stock_lst[0] 262 | init_cash = init_net_value - init_trade_chg 263 | init_net_value = init_cash + init_stock_value 264 | init_capital_use = init_stock_num*stock_lst[0] + init_trade_cost 265 | ''' 266 | 第一日参数加入列表 267 | ''' 268 | net_value_lst.append(init_net_value) 269 | present_worth_lst.append(init_present_worth) 270 | delta_worth_lst.append(init_delta_worth) 271 | stock_num_lst.append(init_stock_num) 272 | trade_cost_lst.append(init_trade_cost) 273 | trade_chg_lst.append(init_trade_chg) 274 | stock_value_lst.append(init_stock_value) 275 | cash_lst.append(init_cash) 276 | capital_use.append(init_capital_use) 277 | ''' 278 | 第i日参数计算 279 | ''' 280 | for i in range(1,251): 281 | present_worth_temp = net_value_lst[i-1] + stock_num_lst[i-1]*(stock_lst[i]-stock_lst[i-1]) 282 | delta_worth_temp = delta_lst[i]*present_worth_temp 283 | stock_num_temp = delta_worth_temp/stock_lst[i] 284 | trade_cost_temp =np.abs( (stock_num_temp - stock_num_lst[i-1])*stock_lst[i]*trade_cost_rate) 285 | trade_chg_temp = (stock_num_temp - stock_num_lst[i-1])*stock_lst[i] + trade_cost_temp 286 | stock_value_temp = stock_num_temp*stock_lst[i] 287 | cash_temp = cash_lst[i-1] - trade_chg_temp 288 | net_value_temp = cash_temp + stock_value_temp 289 | capital_use_temp = capital_use[i-1] + trade_cost_temp + (stock_num_temp-stock_num_lst[i-1])*stock_lst[i] 290 | 291 | net_value_lst.append(net_value_temp) 292 | present_worth_lst.append(present_worth_temp) 293 | delta_worth_lst.append(delta_worth_temp) 294 | stock_num_lst.append(stock_num_temp) 295 | trade_cost_lst.append(trade_cost_temp) 296 | trade_chg_lst.append(trade_chg_temp) 297 | stock_value_lst.append(stock_value_temp) 298 | cash_lst.append(cash_temp) 299 | capital_use.append(capital_use_temp) 300 | 301 | res_dic = { 302 | 'stock_lst':stock_lst, 303 | 'delta_lst':delta_lst, 304 | 'net_value_lst':net_value_lst, 305 | 'present_worth_lst':present_worth_lst, 306 | 'delta_worth_lst':delta_worth_lst, 307 | 'stock_num_lst':stock_num_lst, 308 | 'trade_cost_lst':trade_cost_lst, 309 | 'trade_chg_lst':trade_chg_lst, 310 | 'cash_lst':cash_lst, 311 | 'stock_value_lst':stock_value_lst, 312 | 'capital_use':capital_use 313 | } 314 | res_df = pd.DataFrame(res_dic) 315 | capital_cost_rate = self.basic_parameter['capital_cost_rate'] 316 | res_df['capital_use_cost'] = res_df['capital_use']*capital_cost_rate/365 317 | payoff = (res_df['net_value_lst'].iloc[-1] - res_df['capital_use_cost'].iloc[-1]) 318 | return {'payoff':payoff,'res_df':res_df} 319 | 320 | def cal_df_hedge(self,stock_df,deltas_df): 321 | payoffs = [] 322 | for i in stock_df.columns.tolist(): 323 | se_payoff = self.cal_se_hedge(stock_df.loc[:,i],deltas_df.loc[:,i])['payoff'] 324 | payoffs.append(se_payoff) 325 | payoffs = pd.Series(payoffs) 326 | return payoffs 327 | 328 | t1 = time() 329 | atc = AutoCall(basic_para,mc_para) 330 | t2 = time() 331 | atc.monte_carlo() 332 | t3 = time() 333 | s0 = atc.basic_parameter['s0'] 334 | t4 = time() 335 | atc.get_stock_path(s0) 336 | t5 = time() 337 | cum_rtn_1 = atc.cum_rtn_1 338 | cum_rtn_2 = atc.cum_rtn_2 339 | t6 = time() 340 | spath_1 = atc.stock_path_1 341 | spath_2 = atc.stock_path_2 342 | t7 = time() 343 | customer_expect_payoff = atc.customer_expect_payoff() 344 | t8 = time() 345 | delta0 = atc.get_delta(6520,100) 346 | t9 = time() 347 | se = spath_1.iloc[:,2] 348 | deltas_lst = atc.get_se_deltas(se,100) 349 | #final_observe_point = atc.basic_parameter['observe_point'] 350 | #final_observe_point = atc.ob_copy 351 | #ob_copy = atc.ob_copy 352 | t10 = time() 353 | #deltas_df0 = atc.get_df_deltas(spath_1,100) 354 | t11 = time() 355 | #deltas_df1 = atc.get_df_deltas(spath_1,100) 356 | t12 = time() 357 | print('蒙特卡洛模拟次数:',mc_para['simulations']) 358 | print('构建对象耗时:',t2-t1) 359 | print('蒙特卡洛耗时:',t3-t2) 360 | print('取单个参数耗时:',t4-t3) 361 | print('获取股票路径耗时:',t5-t4) 362 | print('获取收益率矩阵耗时:',t6-t5) 363 | print('获取股票矩阵耗时:',t7-t6) 364 | print('获取客户收益耗时:',t8-t7) 365 | print('计算delta耗时:',t9-t8) 366 | print('计算单路径deltas耗时:',t10-t9) 367 | print('第一次计算所有路径deltas耗时',t11-t10) 368 | print('第二次计算所有路径deltas耗时',t12-t11) 369 | print('总耗时:',t12-t1) 370 | 371 | # 372 | #t13 = time() 373 | #deltas_lst_2 = Parallel(n_jobs=4)(delayed(atc.get_se_deltas)(spath_1[i],100) for i in spath_1.columns.tolist()) 374 | #deltas_df2 = pd.DataFrame(deltas_lst_2) 375 | #deltas_df2 = deltas_df2.T 376 | #t14 = time() 377 | #print('第一次并行计算所有列deltas耗时:',t14-t13) 378 | #deltas_lst_3 = Parallel(n_jobs=4)(delayed(atc.get_se_deltas)(spath_1[i],100) for i in spath_1.columns.tolist()) 379 | #deltas_df3 = pd.DataFrame(deltas_lst_3) 380 | #deltas_df3 = deltas_df3.T 381 | #t15 = time() 382 | #print('第二次并行计算所有列deltas耗时:',t15-t14) 383 | # 384 | ##deltas_df = pd.read_csv('deltas_cal_2.csv') 385 | ##Astock_path = pd.read_csv('spath_1_cal_2.csv') 386 | #deltas_df = deltas_df2 387 | #stock_path = spath_1 388 | #t16 = time() 389 | #hedge_res_0 = atc.cal_df_hedge(stock_path,deltas_df) 390 | #t17 = time() 391 | #print('第一次无并行计算所有列hedge_payoff耗时:',t17-t16) 392 | #hedge_res_1 = Parallel(n_jobs=9)(delayed(atc.cal_se_hedge)(stock_path[i],deltas_df[i]) for i in stock_path.columns.tolist()) 393 | #t18 = time() 394 | #print('第二次并行计算所有列hedge_payoff耗时:',t18-t17) 395 | 396 | --------------------------------------------------------------------------------