Showing
3 changed files
with
1252 additions
and
15 deletions
... | @@ -4,6 +4,17 @@ import numpy as np | ... | @@ -4,6 +4,17 @@ import numpy as np |
4 | import FinanceDataReader as fdr | 4 | import FinanceDataReader as fdr |
5 | from scipy.optimize import minimize | 5 | from scipy.optimize import minimize |
6 | import json | 6 | import json |
7 | +from datetime import date | ||
8 | +import math | ||
9 | +import itertools as it | ||
10 | +import operator | ||
11 | +from datetime import datetime | ||
12 | +from scipy import stats | ||
13 | +from scipy.stats import norm | ||
14 | +from dateutil import rrule | ||
15 | +from calendar import monthrange | ||
16 | +from dateutil.relativedelta import relativedelta | ||
17 | +from ast import literal_eval | ||
7 | 18 | ||
8 | #소숫점 표현 | 19 | #소숫점 표현 |
9 | pd.options.display.float_format = '{:.3f}'.format | 20 | pd.options.display.float_format = '{:.3f}'.format |
... | @@ -101,3 +112,471 @@ class c_Models: | ... | @@ -101,3 +112,471 @@ class c_Models: |
101 | rp = minimize(RP_objective, w0, constraints=constraints, bounds = bd, method='SLSQP') | 112 | rp = minimize(RP_objective, w0, constraints=constraints, bounds = bd, method='SLSQP') |
102 | result = dict(zip(self.asset_name, np.round(rp.x,3))) | 113 | result = dict(zip(self.asset_name, np.round(rp.x,3))) |
103 | return result #, RC(self.cov, rp.x) | 114 | return result #, RC(self.cov, rp.x) |
115 | + | ||
116 | + def plotting(self): | ||
117 | + wt_gmv = np.asarray(list(self.gmv_opt().values())) | ||
118 | + wt_ms = np.asarray(list(self.ms_opt().values())) | ||
119 | + wt_rp = np.asarray(list(self.rp_opt().values())) | ||
120 | + | ||
121 | + ret_gmv = np.dot(wt_gmv, self.mu) | ||
122 | + ret_ms = np.dot(wt_ms, self.mu) | ||
123 | + ret_rp = np.dot(wt_rp, self.mu) | ||
124 | + vol_gmv = np.sqrt(np.dot(wt_gmv.T, np.dot(self.cov, wt_gmv))) | ||
125 | + vol_ms = np.sqrt(np.dot(wt_ms.T, np.dot(self.cov, wt_ms))) | ||
126 | + vol_rp = np.sqrt(np.dot(wt_rp.T, np.dot(self.cov, wt_rp))) | ||
127 | + | ||
128 | + wt_gmv = wt_gmv.tolist() | ||
129 | + wt_ms = wt_ms.tolist() | ||
130 | + wt_rp = wt_rp.tolist() | ||
131 | + | ||
132 | + user_ret = np.dot(self.assets_w, self.mu) | ||
133 | + user_risk = np.sqrt(np.dot(self.assets_w, np.dot(self.cov, self.assets_w))) | ||
134 | + | ||
135 | + weights = {'gmv': wt_gmv, "ms" : wt_ms, "rp": wt_rp} | ||
136 | + | ||
137 | + #rec_rs = recommended_asset() | ||
138 | + | ||
139 | + trets = np.linspace(ret_gmv, max(self.mu), 30) # 30개 짜르기 | ||
140 | + tvols = [] | ||
141 | + | ||
142 | + efpoints = dict() | ||
143 | + for i, tret in enumerate(trets): #이 개별 return마다 최소 risk 찾기 | ||
144 | + n_assets = len(self.data.columns) | ||
145 | + w0 = np.ones(n_assets) / n_assets | ||
146 | + fun = lambda w: np.dot(w.T ,np.dot(self.cov, w)) | ||
147 | + constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, | ||
148 | + {'type': 'ineq', 'fun': lambda x: np.dot(x, self.mu) - tret}] | ||
149 | + #{'type': 'ineq', 'fun': lambda x: x}] | ||
150 | + bd = ((0,1),) * n_assets | ||
151 | + | ||
152 | + minvol = minimize(fun, w0, method='SLSQP',bounds = bd, constraints=constraints) | ||
153 | + tvols.append(np.sqrt(np.dot(minvol.x, np.dot(self.cov, minvol.x)))) | ||
154 | + | ||
155 | + pnumber = '{}point'.format(i+1) | ||
156 | + efpoints[pnumber] = minvol.x.tolist() | ||
157 | + | ||
158 | + if self.data.shape[0] <= 1: | ||
159 | + error = '기간에러' | ||
160 | + return error,1,1 | ||
161 | + else: | ||
162 | + ret_vol = {"GMV": [vol_gmv, ret_gmv],"MaxSharp": [vol_ms, ret_ms],"RiskParity": [vol_rp, ret_rp], "Trets" : trets.tolist(), "Tvols": tvols, "User" : [user_risk,user_ret]} #, "Recommended" : rec_rs} | ||
163 | + return ret_vol, json.dumps(efpoints), json.dumps(weights) | ||
164 | + | ||
165 | + | ||
166 | +class back_test: | ||
167 | + # 단순 일별수익률의 평균을 *365하여 연간수익률을 산출 | ||
168 | + def __init__(self): | ||
169 | + self.test = 0 | ||
170 | + | ||
171 | + def Arithmetic_Mean_Annual(self,ret): | ||
172 | + month_return = np.mean(ret) | ||
173 | + return (month_return*252) | ||
174 | + | ||
175 | + # 기간중 투자했을때 하락할 수 있는 비율 | ||
176 | + def dd(self,ret): | ||
177 | + cum_ret = (1 + ret).cumprod() | ||
178 | + max_drawdown = 0 | ||
179 | + max_ret = 1 | ||
180 | + dd_list = [] | ||
181 | + c = 0 | ||
182 | + for ix_ret in cum_ret.values: | ||
183 | + if max_ret < ix_ret: | ||
184 | + max_ret = ix_ret | ||
185 | + dd_list.append((ix_ret - max_ret) / max_ret) | ||
186 | + c= c+1 | ||
187 | + return dd_list | ||
188 | + | ||
189 | + # 기간중 투자했을때 최고로 많이 하락할 수 있는 비율 | ||
190 | + def mdd(self,ret): | ||
191 | + | ||
192 | + cum_ret = (1 + ret).cumprod() | ||
193 | + max_drawdown = 0 | ||
194 | + max_ret = 1 | ||
195 | + for ix_ret in cum_ret.values: | ||
196 | + if max_drawdown > (ix_ret - max_ret) / max_ret: | ||
197 | + max_drawdown = (ix_ret - max_ret) / max_ret | ||
198 | + if max_ret < ix_ret: | ||
199 | + max_ret = ix_ret | ||
200 | + | ||
201 | + return abs(max_drawdown) | ||
202 | + | ||
203 | + # 포트폴리오 수익률에서 무위험 수익률을 제한 후 이를 포트폴리오의 표준편차로 나눠 산출한 값, 즉 위험대비 얼마나 수익이 좋은지의 척도 | ||
204 | + def sharpe_ratio(self,ret, rf=0.008, num_of_date=252): | ||
205 | + | ||
206 | + return ((np.mean(ret - (rf / num_of_date))) / (np.std(ret))) * np.sqrt(num_of_date) | ||
207 | + | ||
208 | + # 설정한 confidence level에 따른(95%) 확률로 발생할 수 있는 손실액의 최대 액수 | ||
209 | + def value_at_risk(self,ret, para_or_hist="para", confidence_level=0.95): | ||
210 | + | ||
211 | + vol = np.std(ret) | ||
212 | + if para_or_hist == "para": | ||
213 | + VaR = np.mean(ret) - vol * norm.ppf(confidence_level) | ||
214 | + else: | ||
215 | + print('error') | ||
216 | + | ||
217 | + return VaR | ||
218 | + | ||
219 | + # 전체 투자기간에서 상승한 ( ret > 0 ) 기간의 비율 | ||
220 | + def winning_rate(self,ret): | ||
221 | + var_winning_rate = np.sum(ret > 0) / len(ret) | ||
222 | + return var_winning_rate | ||
223 | + | ||
224 | + # 상승한날의 평균상승값을 하락한날의 평균하락값으로 나눈 비율 | ||
225 | + def profit_loss_ratio(self,ret): | ||
226 | + | ||
227 | + if np.sum(ret > 0) == 0: | ||
228 | + var_profit_loss_ratio = 0 | ||
229 | + elif np.sum(ret < 0) == 0: | ||
230 | + var_profit_loss_ratio = np.inf | ||
231 | + else: | ||
232 | + win_mean = np.mean(ret[ret > 0]) | ||
233 | + loss_mean = np.mean(ret[ret < 0]) | ||
234 | + var_profit_loss_ratio = win_mean / loss_mean | ||
235 | + return abs(var_profit_loss_ratio) | ||
236 | + | ||
237 | + # 데이터 취합하는 코드 | ||
238 | + #임시로 5가지 데이터 예시를 활용해 코드작성 | ||
239 | + # 선택한 종목의 이름과 비중, 투자기간을 input 값으로 받음 | ||
240 | + | ||
241 | + def backtest_data(self, assets,weight,start_data_1, end_data_1,start_amount,rebalancing_month, interval, opt_option): | ||
242 | + # input으로 받는 assetnames 입력 | ||
243 | + a = assets | ||
244 | + stock_num = len(a) | ||
245 | + # input으로 받는 assetweights 입력 | ||
246 | + rebal_month = int(rebalancing_month) | ||
247 | + # input으로 받는 rebalancing_month를 입력 | ||
248 | + # 나타내는 데이터 간격을 표시 | ||
249 | + | ||
250 | + # weight 간격 | ||
251 | + b = list(map(float, weight)) | ||
252 | + | ||
253 | + | ||
254 | + # input으로 받는 from_period와 to_period 입력 | ||
255 | + stock_return = pd.date_range(start=start_data_1, end=end_data_1) | ||
256 | + stock_return = pd.DataFrame(stock_return) | ||
257 | + stock_return.columns = ['Date'] | ||
258 | + | ||
259 | + stocks = pd.read_csv('stockcodename.csv', index_col=0) | ||
260 | + symbol = '' | ||
261 | + asset_name = assets[:] | ||
262 | + for k in range(len(assets)): | ||
263 | + for i in enumerate(stocks.Name): | ||
264 | + if i[1] == assets[k]: | ||
265 | + assets[k] = (stocks.iloc[i[0]].Symbol) | ||
266 | + break | ||
267 | + | ||
268 | + # input으로 받는 from_period와 to_period 입력 | ||
269 | + stock_return = pd.date_range(start=start_data_1, end=end_data_1) | ||
270 | + stock_return = pd.DataFrame(stock_return) | ||
271 | + stock_return.columns = ['Date'] | ||
272 | + | ||
273 | + | ||
274 | + for asset in assets: #total_list: | ||
275 | + tmp = fdr.DataReader(asset,start_data_1,end_data_1) | ||
276 | + tmp.insert(1,"Date",tmp.index.copy(),True) | ||
277 | + tmp = tmp[['Date','Change']] | ||
278 | + tmp.columns = ['Date',asset] | ||
279 | + tmp = tmp.reset_index(drop=True) | ||
280 | + stock_return = pd.merge(stock_return,tmp,how='inner', on='Date') | ||
281 | + | ||
282 | + stock_return = stock_return.dropna(axis=0) | ||
283 | + | ||
284 | + #print(stock_return) | ||
285 | + if opt_option == 'basic' : | ||
286 | + | ||
287 | + # 투자비중으로 이루어진 dataframe 만들기 | ||
288 | + | ||
289 | + start_datetime = stock_return.iloc[0,0] | ||
290 | + end_datetime = stock_return.iloc[-1,0] | ||
291 | + diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=start_datetime, until=end_datetime)) | ||
292 | + month_gap = len(diff_months_list) | ||
293 | + rebal_roof = month_gap//rebal_month | ||
294 | + rebal_weight = pd.DataFrame() | ||
295 | + | ||
296 | + for i in range(rebal_roof+1): | ||
297 | + # 데이터로부터 리밸런싱기간만큼 가져오기 | ||
298 | + filtered_df =stock_return.loc[stock_return["Date"].between(start_datetime, | ||
299 | + start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))] | ||
300 | + # 리밸런싱 기간의 누적수익률 산출 | ||
301 | + for j in range(stock_num): | ||
302 | + filtered_df.iloc[:,j+1] = (1 + filtered_df.iloc[:,j+1]).cumprod() | ||
303 | + # 해당 누적수익률에 initial 투자비중을 곱해준다 | ||
304 | + for j in range(stock_num): | ||
305 | + filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]*float(b[j]) | ||
306 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
307 | + filtered_df['total_value'] = filtered_df.sum(axis=1) | ||
308 | + for j in range(stock_num): | ||
309 | + filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]/filtered_df['total_value'] | ||
310 | + | ||
311 | + rebal_weight = pd.concat([rebal_weight,filtered_df]) | ||
312 | + start_datetime = start_datetime + relativedelta(months=rebal_month) | ||
313 | + | ||
314 | + #final_day = monthrange(start_datetime.year, start_datetime.month) | ||
315 | + | ||
316 | + stock_weight = rebal_weight.iloc[:,:-1] | ||
317 | + #print(stock_weight) | ||
318 | + ''' | ||
319 | + stock_weight = stock_return.Date | ||
320 | + stock_weight = pd.DataFrame(stock_weight) | ||
321 | + c = 0 | ||
322 | + for stockweight in b: | ||
323 | + stock_weight[a[c]] = float(stockweight) | ||
324 | + c = c + 1 | ||
325 | + #print(stock_weight) | ||
326 | + ''' | ||
327 | + else : | ||
328 | + # 포트폴리오 최적화 코드를 통한 리벨런싱 이중 리스트 weight 산출 | ||
329 | + # 1. 입력 받은 start ~ end 날짜를 리밸런싱 기간으로 쪼개기 | ||
330 | + opt_start_datetime = stock_return.iloc[0,0] | ||
331 | + opt_end_datetime = stock_return.iloc[-1,0] | ||
332 | + opt_diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=opt_start_datetime, until=opt_end_datetime)) | ||
333 | + opt_month_gap = len(opt_diff_months_list) | ||
334 | + opt_rebal_roof = opt_month_gap//rebal_month | ||
335 | + opt_rebal_weight = pd.DataFrame() | ||
336 | + #opt_array = [[0]*stock_num]*(opt_rebal_roof+1) | ||
337 | + | ||
338 | + for i in range(opt_rebal_roof+1): | ||
339 | + opt_df = stock_return.loc[stock_return["Date"].between(opt_start_datetime,opt_start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))] | ||
340 | + # 최적화 코드에서 기간마다의 가중치를 가져온다 | ||
341 | + c_m = c_Models(a,b,opt_df.iat[0,0]- relativedelta(months=3),opt_df.iat[-1,0]) | ||
342 | + ret_vol, efpoints, weights = c_m.plotting() | ||
343 | + weights = literal_eval(weights) | ||
344 | + weights = weights.get(opt_option) | ||
345 | + ##print(weights) | ||
346 | + # 리밸런싱 기간의 누적수익률 산출 | ||
347 | + for j in range(stock_num): | ||
348 | + opt_df.iloc[:,j+1] = (1 + opt_df.iloc[:,j+1]).cumprod() | ||
349 | + # 해당 누적수익률에 initial 투자비중을 곱해준다 | ||
350 | + for j in range(stock_num): | ||
351 | + opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]*float(weights[j]) | ||
352 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
353 | + opt_df['total_value'] = opt_df.sum(axis=1) | ||
354 | + for j in range(stock_num): | ||
355 | + opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]/opt_df['total_value'] | ||
356 | + | ||
357 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
358 | + #print(opt_df) | ||
359 | + opt_rebal_weight = pd.concat([opt_rebal_weight,opt_df]) | ||
360 | + opt_start_datetime = opt_start_datetime + relativedelta(months=rebal_month) | ||
361 | + #리밸런싱으로 start 기간이 고객이 원하는 end 기간보다 커지게 되면 종료 | ||
362 | + if opt_start_datetime > stock_return.iloc[-1,0]: # i가 100일 때 | ||
363 | + break | ||
364 | + stock_weight = opt_rebal_weight.iloc[:,:-1] | ||
365 | + ##print(stock_weight) | ||
366 | + # 수익률 데이터와 투자비중을 곱한 하나의 데이터 생성 | ||
367 | + pfo_return = stock_weight.Date | ||
368 | + pfo_return = pd.DataFrame(pfo_return) | ||
369 | + # weight 와 return의 날짜 맞춰주기 | ||
370 | + #pfo_return = pfo_return[0:len(stock_weight)] | ||
371 | + pfo_return = pd.merge(pfo_return, stock_return, left_on='Date', right_on='Date', how='left') | ||
372 | + pfo_return['mean_return'] = 0 | ||
373 | + ##print(pfo_return) | ||
374 | + for i in range(0,len(pfo_return)): | ||
375 | + return_result = list(pfo_return.iloc[i,1:1+stock_num]) | ||
376 | + return_weight = list(stock_weight.iloc[i,1:1+stock_num]) | ||
377 | + pfo_return.iloc[i,1+stock_num] = np.dot(return_result,return_weight) | ||
378 | + #rint(pfo_return) | ||
379 | + pfo_return['acc_return'] = [x+1 for x in pfo_return['mean_return']] | ||
380 | + pfo_return['acc_return'] = list(it.accumulate(pfo_return['acc_return'], operator.mul)) | ||
381 | + pfo_return['acc_return'] = [x-1 for x in pfo_return['acc_return']] | ||
382 | + pfo_return['final_balance'] = float(start_amount) + float(start_amount)*pfo_return['acc_return'] | ||
383 | + pfo_return['Drawdown_list'] = back_test.dd(input,pfo_return['mean_return']) | ||
384 | + pfo_return = pfo_return.set_index('Date') | ||
385 | + #print(pfo_return) | ||
386 | + | ||
387 | + | ||
388 | + ### 벤치마크 데이터 로드 및 전처리 | ||
389 | + | ||
390 | + tiker_list = ['KS11','US500'] | ||
391 | + bench_list = [fdr.DataReader(ticker, start_data_1, end_data_1)['Change'] for ticker in tiker_list] | ||
392 | + bench = pd.concat(bench_list, axis=1) | ||
393 | + bench.columns = ['KOSPI', 'S&P500'] | ||
394 | + bench['KOSPI'] = bench['KOSPI'].fillna(0) | ||
395 | + bench['S&P500'] = bench['S&P500'].fillna(0) | ||
396 | + #bench = bench.dropna() | ||
397 | + | ||
398 | + # 벤치마크 누적수익률, DD 값 | ||
399 | + | ||
400 | + bench['KOSPI_acc'] = [x+1 for x in bench['KOSPI']] | ||
401 | + bench['KOSPI_acc'] = list(it.accumulate(bench['KOSPI_acc'], operator.mul)) | ||
402 | + bench['KOSPI_acc'] = [x-1 for x in bench['KOSPI_acc']] | ||
403 | + bench['KOSPI_balance'] = float(start_amount) + float(start_amount)*bench['KOSPI_acc'] | ||
404 | + bench['KOSPI_Drawdown'] = back_test.dd(input,bench['KOSPI']) | ||
405 | + bench['S&P500_acc'] = [x+1 for x in bench['S&P500']] | ||
406 | + bench['S&P500_acc'] = list(it.accumulate(bench['S&P500_acc'], operator.mul)) | ||
407 | + bench['S&P500_acc'] = [x-1 for x in bench['S&P500_acc']] | ||
408 | + bench['S&P500_balance'] = float(start_amount) + float(start_amount)*bench['S&P500_acc'] | ||
409 | + bench['S&P500_Drawdown'] = back_test.dd(input,bench['S&P500']) | ||
410 | + | ||
411 | + if interval == 'monthly' or interval == 'weekly' : | ||
412 | + if interval == 'monthly' : | ||
413 | + inter = 'M' | ||
414 | + if interval == 'weekly' : | ||
415 | + inter = 'W' | ||
416 | + pfo_return_interval = pfo_return.resample(inter).last() | ||
417 | + pfo_return_first = pd.DataFrame(pfo_return.iloc[0]).transpose() | ||
418 | + pfo_return_interval = pd.concat([pfo_return_first, pfo_return_interval]) | ||
419 | + pfo_return_interval['mean_return'] = pfo_return_interval['final_balance'].pct_change() | ||
420 | + pfo_return_interval = pfo_return_interval.dropna() | ||
421 | + | ||
422 | + # 월별 간격으로 만들어주기, 여기서는 return과 value만 monthly로 산출함 나머지값은 daily | ||
423 | + bench_interval = bench.resample(inter).last() | ||
424 | + #bench_ex['KOSPI'] = bench_ex['final_balance'].pct_change() | ||
425 | + bench_first = pd.DataFrame(bench.iloc[0]).transpose() | ||
426 | + bench_interval = pd.concat([bench_first, bench_interval]) | ||
427 | + bench_interval['KOSPI'] = bench_interval['KOSPI_balance'].pct_change() | ||
428 | + bench_interval['S&P500'] = bench_interval['S&P500_balance'].pct_change() | ||
429 | + bench_interval = bench_interval.dropna() | ||
430 | + | ||
431 | + # 날짜타입 열로 만들기 및 str 타입으로 전처리 | ||
432 | + pfo_return = pfo_return.rename_axis('Date').reset_index() | ||
433 | + pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date | ||
434 | + pfo_return['Date'] = list(map(str, pfo_return['Date'])) | ||
435 | + | ||
436 | + pfo_return_interval = pfo_return_interval.rename_axis('Date').reset_index() | ||
437 | + pfo_return_interval['Date'] = pd.to_datetime(pfo_return_interval['Date'], format='%d/%m/%Y').dt.date | ||
438 | + pfo_return_interval['Date'] = list(map(str, pfo_return_interval['Date'])) | ||
439 | + | ||
440 | + bench = bench.rename_axis('Date').reset_index() | ||
441 | + bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date | ||
442 | + bench['Date'] = list(map(str, bench['Date'])) | ||
443 | + | ||
444 | + bench_interval = bench_interval.rename_axis('Date').reset_index() | ||
445 | + bench_interval['Date'] = pd.to_datetime(bench_interval['Date'], format='%d/%m/%Y').dt.date | ||
446 | + bench_interval['Date'] = list(map(str, bench_interval['Date'])) | ||
447 | + | ||
448 | + backtest_return = { | ||
449 | + 'pfo_return': [ | ||
450 | + { | ||
451 | + 'Date': list(pfo_return_interval['Date']), | ||
452 | + 'mean_return': list(pfo_return_interval['mean_return']), | ||
453 | + 'acc_return ratio': list(pfo_return_interval['acc_return']), | ||
454 | + 'final_balance': list(pfo_return_interval['final_balance']), | ||
455 | + 'Drawdown_list' : list(pfo_return_interval['Drawdown_list']) | ||
456 | + } | ||
457 | + ], | ||
458 | + 'bench': [ | ||
459 | + { | ||
460 | + 'Date': list(bench_interval['Date']), | ||
461 | + 'KOSPI_return': list(bench_interval['KOSPI']), | ||
462 | + 'S&P500_return': list(bench_interval['S&P500']), | ||
463 | + 'KOSPI_acc_return': list(bench_interval['KOSPI_acc']), | ||
464 | + 'KOSPI_balance' : list(bench_interval['KOSPI_balance']), | ||
465 | + 'KOSPI_Drawdown': list(bench_interval['KOSPI_Drawdown']), | ||
466 | + 'S&P500_acc_return': list(bench_interval['S&P500_acc']), | ||
467 | + 'S&P500_balance' : list(bench_interval['S&P500_balance']), | ||
468 | + 'S&P500_Drawdown': list(bench_interval['S&P500_Drawdown']) | ||
469 | + } | ||
470 | + ], | ||
471 | + 'indicator': [ | ||
472 | + { | ||
473 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']), | ||
474 | + 'Std': pfo_return['mean_return'].std() * np.sqrt(365), | ||
475 | + 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']), | ||
476 | + 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']), | ||
477 | + 'MDD': back_test.mdd(input,pfo_return['mean_return']), | ||
478 | + 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']), | ||
479 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return']) | ||
480 | + } | ||
481 | + ], | ||
482 | + 'KOSPI_indicator': [ | ||
483 | + { | ||
484 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']), | ||
485 | + 'Std': bench['KOSPI'].std() * np.sqrt(365), | ||
486 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']), | ||
487 | + 'VaR': back_test.value_at_risk(input,bench['KOSPI']), | ||
488 | + 'MDD': back_test.mdd(input,bench['KOSPI']), | ||
489 | + 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']), | ||
490 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI']) | ||
491 | + } | ||
492 | + ], | ||
493 | + 'S&P500_indicator': [ | ||
494 | + { | ||
495 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']), | ||
496 | + 'Std': bench['S&P500'].std() * np.sqrt(365), | ||
497 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']), | ||
498 | + 'VaR': back_test.value_at_risk(input,bench['S&P500']), | ||
499 | + 'MDD': back_test.mdd(input,bench['S&P500']), | ||
500 | + 'Winning ratio': back_test.winning_rate(input,bench['S&P500']), | ||
501 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500']) | ||
502 | + } | ||
503 | + ] | ||
504 | + } | ||
505 | + | ||
506 | + else : | ||
507 | + # 날짜타입 열로 만들기 및 str 타입으로 전처리 | ||
508 | + pfo_return = pfo_return.rename_axis('Date').reset_index() | ||
509 | + pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date | ||
510 | + pfo_return['Date'] = list(map(str, pfo_return['Date'])) | ||
511 | + | ||
512 | + bench = bench.rename_axis('Date').reset_index() | ||
513 | + bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date | ||
514 | + bench['Date'] = list(map(str, bench['Date'])) | ||
515 | + backtest_return = { | ||
516 | + 'pfo_return': [ | ||
517 | + { | ||
518 | + 'Date': list(pfo_return['Date']), | ||
519 | + 'mean_return': list(pfo_return['mean_return']), | ||
520 | + 'acc_return ratio': list(pfo_return['acc_return']), | ||
521 | + 'final_balance': list(pfo_return['final_balance']), | ||
522 | + 'Drawdown_list' : list(pfo_return['Drawdown_list']) | ||
523 | + } | ||
524 | + ], | ||
525 | + 'bench': [ | ||
526 | + { | ||
527 | + 'Date': list(bench['Date']), | ||
528 | + 'KOSPI_return': list(bench['KOSPI']), | ||
529 | + 'S&P500_return': list(bench['S&P500']), | ||
530 | + 'KOSPI_acc_return': list(bench['KOSPI_acc']), | ||
531 | + 'KOSPI_balance' : list(bench['KOSPI_balance']), | ||
532 | + 'KOSPI_Drawdown': list(bench['KOSPI_Drawdown']), | ||
533 | + 'S&P500_acc_return': list(bench['S&P500_acc']), | ||
534 | + 'S&P500_balance' : list(bench['S&P500_balance']), | ||
535 | + 'S&P500_Drawdown': list(bench['S&P500_Drawdown']) | ||
536 | + } | ||
537 | + ], | ||
538 | + 'indicator': [ | ||
539 | + { | ||
540 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']), | ||
541 | + 'Std': pfo_return['mean_return'].std() * np.sqrt(365), | ||
542 | + 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']), | ||
543 | + 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']), | ||
544 | + 'MDD': back_test.mdd(input,pfo_return['mean_return']), | ||
545 | + 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']), | ||
546 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return']) | ||
547 | + } | ||
548 | + ], | ||
549 | + 'KOSPI_indicator': [ | ||
550 | + { | ||
551 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']), | ||
552 | + 'Std': bench['KOSPI'].std() * np.sqrt(365), | ||
553 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']), | ||
554 | + 'VaR': back_test.value_at_risk(input,bench['KOSPI']), | ||
555 | + 'MDD': back_test.mdd(input,bench['KOSPI']), | ||
556 | + 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']), | ||
557 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI']) | ||
558 | + } | ||
559 | + ], | ||
560 | + 'S&P500_indicator': [ | ||
561 | + { | ||
562 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']), | ||
563 | + 'Std': bench['S&P500'].std() * np.sqrt(365), | ||
564 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']), | ||
565 | + 'VaR': back_test.value_at_risk(input,bench['S&P500']), | ||
566 | + 'MDD': back_test.mdd(input,bench['S&P500']), | ||
567 | + 'Winning ratio': back_test.winning_rate(input,bench['S&P500']), | ||
568 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500']) | ||
569 | + } | ||
570 | + ] | ||
571 | + } | ||
572 | + | ||
573 | + return backtest_return | ||
574 | + | ||
575 | + | ||
576 | + | ||
577 | +print(back_test().backtest_data(['삼성전자','LG전자'],[0.9,0.1],'2010-01-01', '2021-01-01',10000000,3, 'monthly', 'gmv')['pfo_return'].mean_return) | ||
578 | + | ||
579 | + | ||
580 | + | ||
581 | + | ||
582 | + | ... | ... |
This diff could not be displayed because it is too large.
... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
2 | "cells": [ | 2 | "cells": [ |
3 | { | 3 | { |
4 | "cell_type": "code", | 4 | "cell_type": "code", |
5 | - "execution_count": 2, | 5 | + "execution_count": 1, |
6 | "metadata": {}, | 6 | "metadata": {}, |
7 | "outputs": [], | 7 | "outputs": [], |
8 | "source": [ | 8 | "source": [ |
... | @@ -13,7 +13,7 @@ | ... | @@ -13,7 +13,7 @@ |
13 | }, | 13 | }, |
14 | { | 14 | { |
15 | "cell_type": "code", | 15 | "cell_type": "code", |
16 | - "execution_count": 41, | 16 | + "execution_count": 2, |
17 | "metadata": {}, | 17 | "metadata": {}, |
18 | "outputs": [], | 18 | "outputs": [], |
19 | "source": [ | 19 | "source": [ |
... | @@ -42,14 +42,14 @@ | ... | @@ -42,14 +42,14 @@ |
42 | }, | 42 | }, |
43 | { | 43 | { |
44 | "cell_type": "code", | 44 | "cell_type": "code", |
45 | - "execution_count": 42, | 45 | + "execution_count": 4, |
46 | "metadata": {}, | 46 | "metadata": {}, |
47 | "outputs": [ | 47 | "outputs": [ |
48 | { | 48 | { |
49 | "name": "stdout", | 49 | "name": "stdout", |
50 | "output_type": "stream", | 50 | "output_type": "stream", |
51 | "text": [ | 51 | "text": [ |
52 | - "{'현재가': 79700, '거래랑': 13358073, '전일 대비 수익률:': -0.004993757802746579}\n" | 52 | + "{'현재가': 82800, '거래랑': 29341312, '전일 대비 수익률:': 0.024752475247524774}\n" |
53 | ] | 53 | ] |
54 | } | 54 | } |
55 | ], | 55 | ], |
... | @@ -59,7 +59,7 @@ | ... | @@ -59,7 +59,7 @@ |
59 | }, | 59 | }, |
60 | { | 60 | { |
61 | "cell_type": "code", | 61 | "cell_type": "code", |
62 | - "execution_count": 6, | 62 | + "execution_count": 5, |
63 | "metadata": {}, | 63 | "metadata": {}, |
64 | "outputs": [], | 64 | "outputs": [], |
65 | "source": [ | 65 | "source": [ |
... | @@ -69,7 +69,7 @@ | ... | @@ -69,7 +69,7 @@ |
69 | }, | 69 | }, |
70 | { | 70 | { |
71 | "cell_type": "code", | 71 | "cell_type": "code", |
72 | - "execution_count": 123, | 72 | + "execution_count": 3, |
73 | "metadata": {}, | 73 | "metadata": {}, |
74 | "outputs": [], | 74 | "outputs": [], |
75 | "source": [ | 75 | "source": [ |
... | @@ -79,6 +79,17 @@ | ... | @@ -79,6 +79,17 @@ |
79 | "import FinanceDataReader as fdr\n", | 79 | "import FinanceDataReader as fdr\n", |
80 | "from scipy.optimize import minimize\n", | 80 | "from scipy.optimize import minimize\n", |
81 | "import json\n", | 81 | "import json\n", |
82 | + "from datetime import date\n", | ||
83 | + "import math\n", | ||
84 | + "import itertools as it\n", | ||
85 | + "import operator\n", | ||
86 | + "from datetime import datetime\n", | ||
87 | + "from scipy import stats\n", | ||
88 | + "from scipy.stats import norm\n", | ||
89 | + "from dateutil import rrule\n", | ||
90 | + "from calendar import monthrange\n", | ||
91 | + "from dateutil.relativedelta import relativedelta\n", | ||
92 | + "from ast import literal_eval\n", | ||
82 | "\n", | 93 | "\n", |
83 | "#소숫점 표현\n", | 94 | "#소숫점 표현\n", |
84 | "pd.options.display.float_format = '{:.3f}'.format\n", | 95 | "pd.options.display.float_format = '{:.3f}'.format\n", |
... | @@ -175,33 +186,567 @@ | ... | @@ -175,33 +186,567 @@ |
175 | "\n", | 186 | "\n", |
176 | " rp = minimize(RP_objective, w0, constraints=constraints, bounds = bd, method='SLSQP')\n", | 187 | " rp = minimize(RP_objective, w0, constraints=constraints, bounds = bd, method='SLSQP')\n", |
177 | " result = dict(zip(self.asset_name, np.round(rp.x,3)))\n", | 188 | " result = dict(zip(self.asset_name, np.round(rp.x,3)))\n", |
178 | - " return result #, RC(self.cov, rp.x)" | 189 | + " return result #, RC(self.cov, rp.x)\n", |
190 | + "\n", | ||
191 | + " def plotting(self):\n", | ||
192 | + " wt_gmv = np.asarray(list(self.gmv_opt().values()))\n", | ||
193 | + " wt_ms = np.asarray(list(self.ms_opt().values()))\n", | ||
194 | + " wt_rp = np.asarray(list(self.rp_opt().values()))\n", | ||
195 | + " \n", | ||
196 | + " ret_gmv = np.dot(wt_gmv, self.mu)\n", | ||
197 | + " ret_ms = np.dot(wt_ms, self.mu)\n", | ||
198 | + " ret_rp = np.dot(wt_rp, self.mu)\n", | ||
199 | + " vol_gmv = np.sqrt(np.dot(wt_gmv.T, np.dot(self.cov, wt_gmv)))\n", | ||
200 | + " vol_ms = np.sqrt(np.dot(wt_ms.T, np.dot(self.cov, wt_ms)))\n", | ||
201 | + " vol_rp = np.sqrt(np.dot(wt_rp.T, np.dot(self.cov, wt_rp)))\n", | ||
202 | + " \n", | ||
203 | + " wt_gmv = wt_gmv.tolist()\n", | ||
204 | + " wt_ms = wt_ms.tolist()\n", | ||
205 | + " wt_rp = wt_rp.tolist()\n", | ||
206 | + " \n", | ||
207 | + " user_ret = np.dot(self.assets_w, self.mu)\n", | ||
208 | + " user_risk = np.sqrt(np.dot(self.assets_w, np.dot(self.cov, self.assets_w)))\n", | ||
209 | + "\n", | ||
210 | + " weights = {'gmv': wt_gmv, \"ms\" : wt_ms, \"rp\": wt_rp}\n", | ||
211 | + " \n", | ||
212 | + " #rec_rs = recommended_asset()\n", | ||
213 | + "\n", | ||
214 | + " trets = np.linspace(ret_gmv, max(self.mu), 30) # 30개 짜르기 \n", | ||
215 | + " tvols = []\n", | ||
216 | + " \n", | ||
217 | + " efpoints = dict()\n", | ||
218 | + " for i, tret in enumerate(trets): #이 개별 return마다 최소 risk 찾기\n", | ||
219 | + " n_assets = len(self.data.columns)\n", | ||
220 | + " w0 = np.ones(n_assets) / n_assets\n", | ||
221 | + " fun = lambda w: np.dot(w.T ,np.dot(self.cov, w))\n", | ||
222 | + " constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1},\n", | ||
223 | + " {'type': 'ineq', 'fun': lambda x: np.dot(x, self.mu) - tret}]\n", | ||
224 | + " #{'type': 'ineq', 'fun': lambda x: x}]\n", | ||
225 | + " bd = ((0,1),) * n_assets\n", | ||
226 | + "\n", | ||
227 | + " minvol = minimize(fun, w0, method='SLSQP',bounds = bd, constraints=constraints)\n", | ||
228 | + " tvols.append(np.sqrt(np.dot(minvol.x, np.dot(self.cov, minvol.x))))\n", | ||
229 | + " \n", | ||
230 | + " pnumber = '{}point'.format(i+1)\n", | ||
231 | + " efpoints[pnumber] = minvol.x.tolist()\n", | ||
232 | + " \n", | ||
233 | + " if self.data.shape[0] <= 1:\n", | ||
234 | + " error = '기간에러'\n", | ||
235 | + " return error,1,1\n", | ||
236 | + " else:\n", | ||
237 | + " ret_vol = {\"GMV\": [vol_gmv, ret_gmv],\"MaxSharp\": [vol_ms, ret_ms],\"RiskParity\": [vol_rp, ret_rp], \"Trets\" : trets.tolist(), \"Tvols\": tvols, \"User\" : [user_risk,user_ret]} #, \"Recommended\" : rec_rs} \n", | ||
238 | + " return ret_vol, json.dumps(efpoints), json.dumps(weights)" | ||
179 | ] | 239 | ] |
180 | }, | 240 | }, |
181 | { | 241 | { |
182 | "cell_type": "code", | 242 | "cell_type": "code", |
183 | - "execution_count": 122, | 243 | + "execution_count": 8, |
184 | "metadata": {}, | 244 | "metadata": {}, |
245 | + "outputs": [], | ||
246 | + "source": [ | ||
247 | + "class back_test:\n", | ||
248 | + " # 단순 일별수익률의 평균을 *365하여 연간수익률을 산출\n", | ||
249 | + " def __init__(self):\n", | ||
250 | + " self.test = 0\n", | ||
251 | + " \n", | ||
252 | + " def Arithmetic_Mean_Annual(self,ret):\n", | ||
253 | + " month_return = np.mean(ret)\n", | ||
254 | + " return (month_return*252)\n", | ||
255 | + "\n", | ||
256 | + " # 기간중 투자했을때 하락할 수 있는 비율\n", | ||
257 | + " def dd(self,ret):\n", | ||
258 | + " cum_ret = (1 + ret).cumprod()\n", | ||
259 | + " max_drawdown = 0\n", | ||
260 | + " max_ret = 1\n", | ||
261 | + " dd_list = []\n", | ||
262 | + " c = 0\n", | ||
263 | + " for ix_ret in cum_ret.values:\n", | ||
264 | + " if max_ret < ix_ret:\n", | ||
265 | + " max_ret = ix_ret\n", | ||
266 | + " dd_list.append((ix_ret - max_ret) / max_ret) \n", | ||
267 | + " c= c+1\n", | ||
268 | + " return dd_list\n", | ||
269 | + " \n", | ||
270 | + " # 기간중 투자했을때 최고로 많이 하락할 수 있는 비율\n", | ||
271 | + " def mdd(self,ret):\n", | ||
272 | + " \n", | ||
273 | + " cum_ret = (1 + ret).cumprod()\n", | ||
274 | + " max_drawdown = 0\n", | ||
275 | + " max_ret = 1\n", | ||
276 | + " for ix_ret in cum_ret.values:\n", | ||
277 | + " if max_drawdown > (ix_ret - max_ret) / max_ret:\n", | ||
278 | + " max_drawdown = (ix_ret - max_ret) / max_ret\n", | ||
279 | + " if max_ret < ix_ret:\n", | ||
280 | + " max_ret = ix_ret\n", | ||
281 | + "\n", | ||
282 | + " return abs(max_drawdown)\n", | ||
283 | + "\n", | ||
284 | + " # 포트폴리오 수익률에서 무위험 수익률을 제한 후 이를 포트폴리오의 표준편차로 나눠 산출한 값, 즉 위험대비 얼마나 수익이 좋은지의 척도\n", | ||
285 | + " def sharpe_ratio(self,ret, rf=0.008, num_of_date=252):\n", | ||
286 | + " \n", | ||
287 | + " return ((np.mean(ret - (rf / num_of_date))) / (np.std(ret))) * np.sqrt(num_of_date)\n", | ||
288 | + " \n", | ||
289 | + " # 설정한 confidence level에 따른(95%) 확률로 발생할 수 있는 손실액의 최대 액수\n", | ||
290 | + " def value_at_risk(self,ret, para_or_hist=\"para\", confidence_level=0.95):\n", | ||
291 | + " \n", | ||
292 | + " vol = np.std(ret)\n", | ||
293 | + " if para_or_hist == \"para\":\n", | ||
294 | + " VaR = np.mean(ret) - vol * norm.ppf(confidence_level)\n", | ||
295 | + " else:\n", | ||
296 | + " print('error')\n", | ||
297 | + "\n", | ||
298 | + " return VaR\n", | ||
299 | + " \n", | ||
300 | + " # 전체 투자기간에서 상승한 ( ret > 0 ) 기간의 비율\n", | ||
301 | + " def winning_rate(self,ret):\n", | ||
302 | + " var_winning_rate = np.sum(ret > 0) / len(ret)\n", | ||
303 | + " return var_winning_rate \n", | ||
304 | + " \n", | ||
305 | + " # 상승한날의 평균상승값을 하락한날의 평균하락값으로 나눈 비율\n", | ||
306 | + " def profit_loss_ratio(self,ret):\n", | ||
307 | + "\n", | ||
308 | + " if np.sum(ret > 0) == 0:\n", | ||
309 | + " var_profit_loss_ratio = 0\n", | ||
310 | + " elif np.sum(ret < 0) == 0:\n", | ||
311 | + " var_profit_loss_ratio = np.inf\n", | ||
312 | + " else:\n", | ||
313 | + " win_mean = np.mean(ret[ret > 0])\n", | ||
314 | + " loss_mean = np.mean(ret[ret < 0])\n", | ||
315 | + " var_profit_loss_ratio = win_mean / loss_mean\n", | ||
316 | + " return abs(var_profit_loss_ratio)\n", | ||
317 | + "\n", | ||
318 | + " # 데이터 취합하는 코드 \n", | ||
319 | + " #임시로 5가지 데이터 예시를 활용해 코드작성\n", | ||
320 | + " # 선택한 종목의 이름과 비중, 투자기간을 input 값으로 받음 \n", | ||
321 | + " \n", | ||
322 | + " def backtest_data(self, assets,weight,start_data_1, end_data_1,start_amount,rebalancing_month, interval, opt_option):\n", | ||
323 | + " # input으로 받는 assetnames 입력\n", | ||
324 | + " a = assets\n", | ||
325 | + " stock_num = len(a)\n", | ||
326 | + " # input으로 받는 assetweights 입력\n", | ||
327 | + " rebal_month = int(rebalancing_month)\n", | ||
328 | + " # input으로 받는 rebalancing_month를 입력\n", | ||
329 | + " # 나타내는 데이터 간격을 표시\n", | ||
330 | + "\n", | ||
331 | + " # weight 간격 \n", | ||
332 | + " b = list(map(float, weight))\n", | ||
333 | + " \n", | ||
334 | + "\n", | ||
335 | + " # input으로 받는 from_period와 to_period 입력\n", | ||
336 | + " stock_return = pd.date_range(start=start_data_1, end=end_data_1)\n", | ||
337 | + " stock_return = pd.DataFrame(stock_return)\n", | ||
338 | + " stock_return.columns = ['Date']\n", | ||
339 | + "\n", | ||
340 | + " stocks = pd.read_csv('stockcodename.csv', index_col=0)\n", | ||
341 | + " symbol = ''\n", | ||
342 | + " asset_name = assets[:]\n", | ||
343 | + " for k in range(len(assets)):\n", | ||
344 | + " for i in enumerate(stocks.Name):\n", | ||
345 | + " if i[1] == assets[k]:\n", | ||
346 | + " assets[k] = (stocks.iloc[i[0]].Symbol)\n", | ||
347 | + " break\n", | ||
348 | + " \n", | ||
349 | + " # input으로 받는 from_period와 to_period 입력\n", | ||
350 | + " stock_return = pd.date_range(start=start_data_1, end=end_data_1)\n", | ||
351 | + " stock_return = pd.DataFrame(stock_return)\n", | ||
352 | + " stock_return.columns = ['Date']\n", | ||
353 | + " \n", | ||
354 | + "\n", | ||
355 | + " for asset in assets: #total_list:\n", | ||
356 | + " tmp = fdr.DataReader(asset,start_data_1,end_data_1)\n", | ||
357 | + " tmp.insert(1,\"Date\",tmp.index.copy(),True)\n", | ||
358 | + " tmp = tmp[['Date','Change']]\n", | ||
359 | + " tmp.columns = ['Date',asset]\n", | ||
360 | + " tmp = tmp.reset_index(drop=True)\n", | ||
361 | + " stock_return = pd.merge(stock_return,tmp,how='inner', on='Date')\n", | ||
362 | + "\n", | ||
363 | + " stock_return = stock_return.dropna(axis=0)\n", | ||
364 | + "\n", | ||
365 | + " #print(stock_return)\n", | ||
366 | + " if opt_option == 'basic' :\n", | ||
367 | + "\n", | ||
368 | + " # 투자비중으로 이루어진 dataframe 만들기\n", | ||
369 | + "\n", | ||
370 | + " start_datetime = stock_return.iloc[0,0]\n", | ||
371 | + " end_datetime = stock_return.iloc[-1,0]\n", | ||
372 | + " diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=start_datetime, until=end_datetime))\n", | ||
373 | + " month_gap = len(diff_months_list)\n", | ||
374 | + " rebal_roof = month_gap//rebal_month\n", | ||
375 | + " rebal_weight = pd.DataFrame()\n", | ||
376 | + "\n", | ||
377 | + " for i in range(rebal_roof+1):\n", | ||
378 | + " # 데이터로부터 리밸런싱기간만큼 가져오기\n", | ||
379 | + " filtered_df =stock_return.loc[stock_return[\"Date\"].between(start_datetime, \n", | ||
380 | + " start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))]\n", | ||
381 | + " # 리밸런싱 기간의 누적수익률 산출\n", | ||
382 | + " for j in range(stock_num):\n", | ||
383 | + " filtered_df.iloc[:,j+1] = (1 + filtered_df.iloc[:,j+1]).cumprod()\n", | ||
384 | + " # 해당 누적수익률에 initial 투자비중을 곱해준다 \n", | ||
385 | + " for j in range(stock_num):\n", | ||
386 | + " filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]*float(b[j])\n", | ||
387 | + " # 이후 각각의 종목의 비중을 계산해서 산출한다\n", | ||
388 | + " filtered_df['total_value'] = filtered_df.sum(axis=1)\n", | ||
389 | + " for j in range(stock_num):\n", | ||
390 | + " filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]/filtered_df['total_value']\n", | ||
391 | + "\n", | ||
392 | + " rebal_weight = pd.concat([rebal_weight,filtered_df])\n", | ||
393 | + " start_datetime = start_datetime + relativedelta(months=rebal_month)\n", | ||
394 | + "\n", | ||
395 | + " #final_day = monthrange(start_datetime.year, start_datetime.month)\n", | ||
396 | + "\n", | ||
397 | + " stock_weight = rebal_weight.iloc[:,:-1]\n", | ||
398 | + " #print(stock_weight)\n", | ||
399 | + " '''\n", | ||
400 | + " stock_weight = stock_return.Date\n", | ||
401 | + " stock_weight = pd.DataFrame(stock_weight)\n", | ||
402 | + " c = 0\n", | ||
403 | + " for stockweight in b:\n", | ||
404 | + " stock_weight[a[c]] = float(stockweight)\n", | ||
405 | + " c = c + 1\n", | ||
406 | + " #print(stock_weight)\n", | ||
407 | + " '''\n", | ||
408 | + " else :\n", | ||
409 | + " # 포트폴리오 최적화 코드를 통한 리벨런싱 이중 리스트 weight 산출\n", | ||
410 | + " # 1. 입력 받은 start ~ end 날짜를 리밸런싱 기간으로 쪼개기 \n", | ||
411 | + " opt_start_datetime = stock_return.iloc[0,0]\n", | ||
412 | + " opt_end_datetime = stock_return.iloc[-1,0]\n", | ||
413 | + " opt_diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=opt_start_datetime, until=opt_end_datetime))\n", | ||
414 | + " opt_month_gap = len(opt_diff_months_list)\n", | ||
415 | + " opt_rebal_roof = opt_month_gap//rebal_month\n", | ||
416 | + " opt_rebal_weight = pd.DataFrame()\n", | ||
417 | + " #opt_array = [[0]*stock_num]*(opt_rebal_roof+1)\n", | ||
418 | + "\n", | ||
419 | + " for i in range(opt_rebal_roof+1):\n", | ||
420 | + " opt_df = stock_return.loc[stock_return[\"Date\"].between(opt_start_datetime,opt_start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))]\n", | ||
421 | + " # 최적화 코드에서 기간마다의 가중치를 가져온다\n", | ||
422 | + " c_m = c_Models(a,b,opt_df.iat[0,0]- relativedelta(months=3),opt_df.iat[-1,0])\n", | ||
423 | + " ret_vol, efpoints, weights = c_m.plotting()\n", | ||
424 | + " weights = literal_eval(weights)\n", | ||
425 | + " weights = weights.get(opt_option)\n", | ||
426 | + " ##print(weights)\n", | ||
427 | + " # 리밸런싱 기간의 누적수익률 산출\n", | ||
428 | + " for j in range(stock_num):\n", | ||
429 | + " opt_df.iloc[:,j+1] = (1 + opt_df.iloc[:,j+1]).cumprod()\n", | ||
430 | + " # 해당 누적수익률에 initial 투자비중을 곱해준다 \n", | ||
431 | + " for j in range(stock_num):\n", | ||
432 | + " opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]*float(weights[j])\n", | ||
433 | + " # 이후 각각의 종목의 비중을 계산해서 산출한다\n", | ||
434 | + " opt_df['total_value'] = opt_df.sum(axis=1)\n", | ||
435 | + " for j in range(stock_num):\n", | ||
436 | + " opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]/opt_df['total_value']\n", | ||
437 | + "\n", | ||
438 | + " # 이후 각각의 종목의 비중을 계산해서 산출한다\n", | ||
439 | + " #print(opt_df)\n", | ||
440 | + " opt_rebal_weight = pd.concat([opt_rebal_weight,opt_df])\n", | ||
441 | + " opt_start_datetime = opt_start_datetime + relativedelta(months=rebal_month)\n", | ||
442 | + " #리밸런싱으로 start 기간이 고객이 원하는 end 기간보다 커지게 되면 종료 \n", | ||
443 | + " if opt_start_datetime > stock_return.iloc[-1,0]: # i가 100일 때\n", | ||
444 | + " break \n", | ||
445 | + " stock_weight = opt_rebal_weight.iloc[:,:-1]\n", | ||
446 | + " ##print(stock_weight)\n", | ||
447 | + " # 수익률 데이터와 투자비중을 곱한 하나의 데이터 생성 \n", | ||
448 | + " pfo_return = stock_weight.Date\n", | ||
449 | + " pfo_return = pd.DataFrame(pfo_return)\n", | ||
450 | + " # weight 와 return의 날짜 맞춰주기 \n", | ||
451 | + " #pfo_return = pfo_return[0:len(stock_weight)]\n", | ||
452 | + " pfo_return = pd.merge(pfo_return, stock_return, left_on='Date', right_on='Date', how='left')\n", | ||
453 | + " pfo_return['mean_return'] = 0\n", | ||
454 | + " ##print(pfo_return)\n", | ||
455 | + " for i in range(0,len(pfo_return)):\n", | ||
456 | + " return_result = list(pfo_return.iloc[i,1:1+stock_num])\n", | ||
457 | + " return_weight = list(stock_weight.iloc[i,1:1+stock_num])\n", | ||
458 | + " pfo_return.iloc[i,1+stock_num] = np.dot(return_result,return_weight)\n", | ||
459 | + " #rint(pfo_return)\n", | ||
460 | + " pfo_return['acc_return'] = [x+1 for x in pfo_return['mean_return']]\n", | ||
461 | + " pfo_return['acc_return'] = list(it.accumulate(pfo_return['acc_return'], operator.mul))\n", | ||
462 | + " pfo_return['acc_return'] = [x-1 for x in pfo_return['acc_return']]\n", | ||
463 | + " pfo_return['final_balance'] = float(start_amount) + float(start_amount)*pfo_return['acc_return']\n", | ||
464 | + " pfo_return['Drawdown_list'] = back_test.dd(input,pfo_return['mean_return'])\n", | ||
465 | + " pfo_return = pfo_return.set_index('Date') \n", | ||
466 | + " #print(pfo_return)\n", | ||
467 | + " \n", | ||
468 | + " \n", | ||
469 | + " ### 벤치마크 데이터 로드 및 전처리\n", | ||
470 | + " \n", | ||
471 | + " tiker_list = ['KS11','US500'] \n", | ||
472 | + " bench_list = [fdr.DataReader(ticker, start_data_1, end_data_1)['Change'] for ticker in tiker_list]\n", | ||
473 | + " bench = pd.concat(bench_list, axis=1)\n", | ||
474 | + " bench.columns = ['KOSPI', 'S&P500']\n", | ||
475 | + " bench['KOSPI'] = bench['KOSPI'].fillna(0)\n", | ||
476 | + " bench['S&P500'] = bench['S&P500'].fillna(0)\n", | ||
477 | + " #bench = bench.dropna()\n", | ||
478 | + " \n", | ||
479 | + " # 벤치마크 누적수익률, DD 값 \n", | ||
480 | + " \n", | ||
481 | + " bench['KOSPI_acc'] = [x+1 for x in bench['KOSPI']]\n", | ||
482 | + " bench['KOSPI_acc'] = list(it.accumulate(bench['KOSPI_acc'], operator.mul))\n", | ||
483 | + " bench['KOSPI_acc'] = [x-1 for x in bench['KOSPI_acc']]\n", | ||
484 | + " bench['KOSPI_balance'] = float(start_amount) + float(start_amount)*bench['KOSPI_acc']\n", | ||
485 | + " bench['KOSPI_Drawdown'] = back_test.dd(input,bench['KOSPI'])\n", | ||
486 | + " bench['S&P500_acc'] = [x+1 for x in bench['S&P500']]\n", | ||
487 | + " bench['S&P500_acc'] = list(it.accumulate(bench['S&P500_acc'], operator.mul))\n", | ||
488 | + " bench['S&P500_acc'] = [x-1 for x in bench['S&P500_acc']]\n", | ||
489 | + " bench['S&P500_balance'] = float(start_amount) + float(start_amount)*bench['S&P500_acc']\n", | ||
490 | + " bench['S&P500_Drawdown'] = back_test.dd(input,bench['S&P500'])\n", | ||
491 | + " \n", | ||
492 | + " if interval == 'monthly' or interval == 'weekly' :\n", | ||
493 | + " if interval == 'monthly' :\n", | ||
494 | + " inter = 'M'\n", | ||
495 | + " if interval == 'weekly' :\n", | ||
496 | + " inter = 'W'\n", | ||
497 | + " pfo_return_interval = pfo_return.resample(inter).last()\n", | ||
498 | + " pfo_return_first = pd.DataFrame(pfo_return.iloc[0]).transpose()\n", | ||
499 | + " pfo_return_interval = pd.concat([pfo_return_first, pfo_return_interval])\n", | ||
500 | + " pfo_return_interval['mean_return'] = pfo_return_interval['final_balance'].pct_change()\n", | ||
501 | + " pfo_return_interval = pfo_return_interval.dropna()\n", | ||
502 | + " \n", | ||
503 | + " # 월별 간격으로 만들어주기, 여기서는 return과 value만 monthly로 산출함 나머지값은 daily\n", | ||
504 | + " bench_interval = bench.resample(inter).last()\n", | ||
505 | + " #bench_ex['KOSPI'] = bench_ex['final_balance'].pct_change()\n", | ||
506 | + " bench_first = pd.DataFrame(bench.iloc[0]).transpose()\n", | ||
507 | + " bench_interval = pd.concat([bench_first, bench_interval])\n", | ||
508 | + " bench_interval['KOSPI'] = bench_interval['KOSPI_balance'].pct_change()\n", | ||
509 | + " bench_interval['S&P500'] = bench_interval['S&P500_balance'].pct_change()\n", | ||
510 | + " bench_interval = bench_interval.dropna()\n", | ||
511 | + " \n", | ||
512 | + " # 날짜타입 열로 만들기 및 str 타입으로 전처리 \n", | ||
513 | + " pfo_return = pfo_return.rename_axis('Date').reset_index()\n", | ||
514 | + " pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date\n", | ||
515 | + " pfo_return['Date'] = list(map(str, pfo_return['Date']))\n", | ||
516 | + " \n", | ||
517 | + " pfo_return_interval = pfo_return_interval.rename_axis('Date').reset_index()\n", | ||
518 | + " pfo_return_interval['Date'] = pd.to_datetime(pfo_return_interval['Date'], format='%d/%m/%Y').dt.date\n", | ||
519 | + " pfo_return_interval['Date'] = list(map(str, pfo_return_interval['Date']))\n", | ||
520 | + " \n", | ||
521 | + " bench = bench.rename_axis('Date').reset_index()\n", | ||
522 | + " bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date\n", | ||
523 | + " bench['Date'] = list(map(str, bench['Date'])) \n", | ||
524 | + " \n", | ||
525 | + " bench_interval = bench_interval.rename_axis('Date').reset_index()\n", | ||
526 | + " bench_interval['Date'] = pd.to_datetime(bench_interval['Date'], format='%d/%m/%Y').dt.date\n", | ||
527 | + " bench_interval['Date'] = list(map(str, bench_interval['Date'])) \n", | ||
528 | + " \n", | ||
529 | + " backtest_return = {\n", | ||
530 | + " 'pfo_return': [\n", | ||
531 | + " {\n", | ||
532 | + " 'Date': list(pfo_return_interval['Date']),\n", | ||
533 | + " 'mean_return': list(pfo_return_interval['mean_return']), \n", | ||
534 | + " 'acc_return ratio': list(pfo_return_interval['acc_return']),\n", | ||
535 | + " 'final_balance': list(pfo_return_interval['final_balance']),\n", | ||
536 | + " 'Drawdown_list' : list(pfo_return_interval['Drawdown_list'])\n", | ||
537 | + " }\n", | ||
538 | + " ], \n", | ||
539 | + " 'bench': [\n", | ||
540 | + " {\n", | ||
541 | + " 'Date': list(bench_interval['Date']),\n", | ||
542 | + " 'KOSPI_return': list(bench_interval['KOSPI']), \n", | ||
543 | + " 'S&P500_return': list(bench_interval['S&P500']),\n", | ||
544 | + " 'KOSPI_acc_return': list(bench_interval['KOSPI_acc']),\n", | ||
545 | + " 'KOSPI_balance' : list(bench_interval['KOSPI_balance']), \n", | ||
546 | + " 'KOSPI_Drawdown': list(bench_interval['KOSPI_Drawdown']),\n", | ||
547 | + " 'S&P500_acc_return': list(bench_interval['S&P500_acc']),\n", | ||
548 | + " 'S&P500_balance' : list(bench_interval['S&P500_balance']), \n", | ||
549 | + " 'S&P500_Drawdown': list(bench_interval['S&P500_Drawdown'])\n", | ||
550 | + " }\n", | ||
551 | + " ], \n", | ||
552 | + " 'indicator': [\n", | ||
553 | + " {\n", | ||
554 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']),\n", | ||
555 | + " 'Std': pfo_return['mean_return'].std() * np.sqrt(365), \n", | ||
556 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']),\n", | ||
557 | + " 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']),\n", | ||
558 | + " 'MDD': back_test.mdd(input,pfo_return['mean_return']),\n", | ||
559 | + " 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']),\n", | ||
560 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return'])\n", | ||
561 | + " }\n", | ||
562 | + " ], \n", | ||
563 | + " 'KOSPI_indicator': [\n", | ||
564 | + " {\n", | ||
565 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']),\n", | ||
566 | + " 'Std': bench['KOSPI'].std() * np.sqrt(365), \n", | ||
567 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']),\n", | ||
568 | + " 'VaR': back_test.value_at_risk(input,bench['KOSPI']),\n", | ||
569 | + " 'MDD': back_test.mdd(input,bench['KOSPI']),\n", | ||
570 | + " 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']),\n", | ||
571 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI'])\n", | ||
572 | + " }\n", | ||
573 | + " ], \n", | ||
574 | + " 'S&P500_indicator': [\n", | ||
575 | + " {\n", | ||
576 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']),\n", | ||
577 | + " 'Std': bench['S&P500'].std() * np.sqrt(365), \n", | ||
578 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']),\n", | ||
579 | + " 'VaR': back_test.value_at_risk(input,bench['S&P500']),\n", | ||
580 | + " 'MDD': back_test.mdd(input,bench['S&P500']),\n", | ||
581 | + " 'Winning ratio': back_test.winning_rate(input,bench['S&P500']),\n", | ||
582 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500'])\n", | ||
583 | + " }\n", | ||
584 | + " ]\n", | ||
585 | + " } \n", | ||
586 | + " \n", | ||
587 | + " else :\n", | ||
588 | + " # 날짜타입 열로 만들기 및 str 타입으로 전처리 \n", | ||
589 | + " pfo_return = pfo_return.rename_axis('Date').reset_index()\n", | ||
590 | + " pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date\n", | ||
591 | + " pfo_return['Date'] = list(map(str, pfo_return['Date']))\n", | ||
592 | + " \n", | ||
593 | + " bench = bench.rename_axis('Date').reset_index()\n", | ||
594 | + " bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date\n", | ||
595 | + " bench['Date'] = list(map(str, bench['Date']))\n", | ||
596 | + " backtest_return = {\n", | ||
597 | + " 'pfo_return': [\n", | ||
598 | + " {\n", | ||
599 | + " 'Date': list(pfo_return['Date']),\n", | ||
600 | + " 'mean_return': list(pfo_return['mean_return']), \n", | ||
601 | + " 'acc_return ratio': list(pfo_return['acc_return']),\n", | ||
602 | + " 'final_balance': list(pfo_return['final_balance']),\n", | ||
603 | + " 'Drawdown_list' : list(pfo_return['Drawdown_list'])\n", | ||
604 | + " }\n", | ||
605 | + " ], \n", | ||
606 | + " 'bench': [\n", | ||
607 | + " {\n", | ||
608 | + " 'Date': list(bench['Date']),\n", | ||
609 | + " 'KOSPI_return': list(bench['KOSPI']), \n", | ||
610 | + " 'S&P500_return': list(bench['S&P500']),\n", | ||
611 | + " 'KOSPI_acc_return': list(bench['KOSPI_acc']),\n", | ||
612 | + " 'KOSPI_balance' : list(bench['KOSPI_balance']), \n", | ||
613 | + " 'KOSPI_Drawdown': list(bench['KOSPI_Drawdown']),\n", | ||
614 | + " 'S&P500_acc_return': list(bench['S&P500_acc']),\n", | ||
615 | + " 'S&P500_balance' : list(bench['S&P500_balance']), \n", | ||
616 | + " 'S&P500_Drawdown': list(bench['S&P500_Drawdown'])\n", | ||
617 | + " }\n", | ||
618 | + " ], \n", | ||
619 | + " 'indicator': [\n", | ||
620 | + " {\n", | ||
621 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']),\n", | ||
622 | + " 'Std': pfo_return['mean_return'].std() * np.sqrt(365), \n", | ||
623 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']),\n", | ||
624 | + " 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']),\n", | ||
625 | + " 'MDD': back_test.mdd(input,pfo_return['mean_return']),\n", | ||
626 | + " 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']),\n", | ||
627 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return'])\n", | ||
628 | + " }\n", | ||
629 | + " ], \n", | ||
630 | + " 'KOSPI_indicator': [\n", | ||
631 | + " {\n", | ||
632 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']),\n", | ||
633 | + " 'Std': bench['KOSPI'].std() * np.sqrt(365), \n", | ||
634 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']),\n", | ||
635 | + " 'VaR': back_test.value_at_risk(input,bench['KOSPI']),\n", | ||
636 | + " 'MDD': back_test.mdd(input,bench['KOSPI']),\n", | ||
637 | + " 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']),\n", | ||
638 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI'])\n", | ||
639 | + " }\n", | ||
640 | + " ], \n", | ||
641 | + " 'S&P500_indicator': [\n", | ||
642 | + " {\n", | ||
643 | + " 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']),\n", | ||
644 | + " 'Std': bench['S&P500'].std() * np.sqrt(365), \n", | ||
645 | + " 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']),\n", | ||
646 | + " 'VaR': back_test.value_at_risk(input,bench['S&P500']),\n", | ||
647 | + " 'MDD': back_test.mdd(input,bench['S&P500']),\n", | ||
648 | + " 'Winning ratio': back_test.winning_rate(input,bench['S&P500']),\n", | ||
649 | + " 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500'])\n", | ||
650 | + " }\n", | ||
651 | + " ]\n", | ||
652 | + " } \n", | ||
653 | + "\n", | ||
654 | + " \n", | ||
655 | + "\n", | ||
656 | + " return backtest_return" | ||
657 | + ] | ||
658 | + }, | ||
659 | + { | ||
660 | + "cell_type": "code", | ||
661 | + "execution_count": 5, | ||
662 | + "metadata": { | ||
663 | + "scrolled": true | ||
664 | + }, | ||
185 | "outputs": [ | 665 | "outputs": [ |
186 | { | 666 | { |
187 | "data": { | 667 | "data": { |
188 | "text/plain": [ | 668 | "text/plain": [ |
189 | - "{'삼성전자': 0.727, 'LG전자': 0.0, '카카오': 0.273}" | 669 | + "({'GMV': [0.1858389981988727, 0.21203948231342723],\n", |
670 | + " 'MaxSharp': [0.18671958740979067, 0.2139596912413942],\n", | ||
671 | + " 'RiskParity': [0.20795176890404793, 0.21559809699947152],\n", | ||
672 | + " 'Trets': [0.21203948231342723,\n", | ||
673 | + " 0.21294773988703294,\n", | ||
674 | + " 0.21385599746063863,\n", | ||
675 | + " 0.21476425503424434,\n", | ||
676 | + " 0.21567251260785003,\n", | ||
677 | + " 0.21658077018145575,\n", | ||
678 | + " 0.21748902775506146,\n", | ||
679 | + " 0.21839728532866715,\n", | ||
680 | + " 0.21930554290227286,\n", | ||
681 | + " 0.22021380047587857,\n", | ||
682 | + " 0.22112205804948426,\n", | ||
683 | + " 0.22203031562308997,\n", | ||
684 | + " 0.22293857319669566,\n", | ||
685 | + " 0.22384683077030137,\n", | ||
686 | + " 0.2247550883439071,\n", | ||
687 | + " 0.22566334591751278,\n", | ||
688 | + " 0.2265716034911185,\n", | ||
689 | + " 0.2274798610647242,\n", | ||
690 | + " 0.2283881186383299,\n", | ||
691 | + " 0.2292963762119356,\n", | ||
692 | + " 0.2302046337855413,\n", | ||
693 | + " 0.231112891359147,\n", | ||
694 | + " 0.23202114893275272,\n", | ||
695 | + " 0.2329294065063584,\n", | ||
696 | + " 0.23383766407996412,\n", | ||
697 | + " 0.23474592165356983,\n", | ||
698 | + " 0.23565417922717552,\n", | ||
699 | + " 0.23656243680078123,\n", | ||
700 | + " 0.23747069437438692,\n", | ||
701 | + " 0.23837895194799263],\n", | ||
702 | + " 'Tvols': [0.1858389981987725,\n", | ||
703 | + " 0.18603980681220794,\n", | ||
704 | + " 0.1866279462874506,\n", | ||
705 | + " 0.18759977333781017,\n", | ||
706 | + " 0.1889493691852574,\n", | ||
707 | + " 0.19066871063768298,\n", | ||
708 | + " 0.19274790351948154,\n", | ||
709 | + " 0.1951754484924986,\n", | ||
710 | + " 0.19793852894483158,\n", | ||
711 | + " 0.2010233088543836,\n", | ||
712 | + " 0.20441522611870286,\n", | ||
713 | + " 0.2080992618687252,\n", | ||
714 | + " 0.21206019173944504,\n", | ||
715 | + " 0.2162828032765981,\n", | ||
716 | + " 0.2207520817102024,\n", | ||
717 | + " 0.22545335668848557,\n", | ||
718 | + " 0.230372426146853,\n", | ||
719 | + " 0.2354956425317311,\n", | ||
720 | + " 0.24080997621842265,\n", | ||
721 | + " 0.24630305688942764,\n", | ||
722 | + " 0.25196319408160095,\n", | ||
723 | + " 0.25777938370335385,\n", | ||
724 | + " 0.2637413018896242,\n", | ||
725 | + " 0.2698392894833826,\n", | ||
726 | + " 0.2760643295664975,\n", | ||
727 | + " 0.2824080201417004,\n", | ||
728 | + " 0.288862545420281,\n", | ||
729 | + " 0.29542064013059877,\n", | ||
730 | + " 0.3020755588277587,\n", | ||
731 | + " 0.3088210356801474],\n", | ||
732 | + " 'User': [0.25113519989524385, 0.2169925302290805]},\n", | ||
733 | + " '{\"1point\": [0.7270000003851871, 0.0, 0.272999999614813], \"2point\": [0.701931034333944, 0.0, 0.29806896566605606], \"3point\": [0.6768620689166184, 0.0, 0.32313793108338174], \"4point\": [0.6517931118624203, 0.0, 0.3482068881375798], \"5point\": [0.6267241338618083, 0.0, 0.37327586613819175], \"6point\": [0.6016551680700105, 0.0, 0.3983448319299896], \"7point\": [0.5765862068839319, 1.0625181290357943e-17, 0.42341379311606814], \"8point\": [0.5515172410659429, 3.469446951953614e-17, 0.4484827589340571], \"9point\": [0.5264482750472359, 0.0, 0.47355172495276426], \"10point\": [0.501379313669539, 0.0, 0.49862068633046097], \"11point\": [0.4763103453392776, 2.0816681711721685e-17, 0.5236896546607225], \"12point\": [0.45124137745684034, 0.0, 0.5487586225431597], \"13point\": [0.426172412012964, 0.0, 0.573827587987036], \"14point\": [0.40110344933541525, 6.938893903907228e-18, 0.5988965506645848], \"15point\": [0.376034481762012, 2.0816681711721685e-17, 0.623965518237988], \"16point\": [0.3509655176307255, 0.0, 0.6490344823692745], \"17point\": [0.3258965525334883, 0.0, 0.6741034474665117], \"18point\": [0.300827586518259, 4.163336342344337e-17, 0.699172413481741], \"19point\": [0.2757586213143949, 4.163336342344337e-17, 0.724241378685605], \"20point\": [0.2506896557025046, 2.7755575615628914e-17, 0.7493103442974953], \"21point\": [0.22562069008962146, 3.122502256758253e-17, 0.7743793099103785], \"22point\": [0.2005517244760024, 0.0, 0.7994482755239977], \"23point\": [0.17548275872525812, 1.3877787807814457e-17, 0.8245172412747419], \"24point\": [0.15041379287609932, 0.0, 0.8495862071239008], \"25point\": [0.12534482753941983, 1.6046192152785466e-17, 0.8746551724605802], \"26point\": [0.10027586412659925, 1.1102230246251565e-16, 0.8997241358734006], \"27point\": [0.07520689805671478, 0.0, 0.9247931019432855], \"28point\": [0.050137931954542976, 0.0, 0.9498620680454571], \"29point\": [0.025068965852762182, 0.0, 0.9749310341472383], \"30point\": [0.0, 3.533653586407226e-08, 0.9999999646634645]}',\n", | ||
734 | + " '{\"gmv\": [0.727, 0.0, 0.273], \"ms\": [0.674, 0.0, 0.326], \"rp\": [0.443, 0.238, 0.319]}')" | ||
190 | ] | 735 | ] |
191 | }, | 736 | }, |
192 | - "execution_count": 122, | 737 | + "execution_count": 5, |
193 | "metadata": {}, | 738 | "metadata": {}, |
194 | "output_type": "execute_result" | 739 | "output_type": "execute_result" |
195 | } | 740 | } |
196 | ], | 741 | ], |
197 | "source": [ | 742 | "source": [ |
198 | "#gmv 포트폴리오 -> 해당 종목을 각각 몇 퍼센트로 투자해야 위험이 제일 적은가\n", | 743 | "#gmv 포트폴리오 -> 해당 종목을 각각 몇 퍼센트로 투자해야 위험이 제일 적은가\n", |
199 | - "c_Models(['삼성전자','LG전자','카카오'],[0,0,0],'2015-01-01','2021-04-01').gmv_opt()" | 744 | + "c_Models(['삼성전자','LG전자','카카오'],[0.2,0.5,0.3],'2015-01-01','2021-04-01').plotting()" |
200 | ] | 745 | ] |
201 | }, | 746 | }, |
202 | { | 747 | { |
203 | "cell_type": "code", | 748 | "cell_type": "code", |
204 | - "execution_count": 124, | 749 | + "execution_count": 6, |
205 | "metadata": {}, | 750 | "metadata": {}, |
206 | "outputs": [ | 751 | "outputs": [ |
207 | { | 752 | { |
... | @@ -210,7 +755,7 @@ | ... | @@ -210,7 +755,7 @@ |
210 | "{'삼성전자': 0.674, 'LG전자': 0.0, '카카오': 0.326}" | 755 | "{'삼성전자': 0.674, 'LG전자': 0.0, '카카오': 0.326}" |
211 | ] | 756 | ] |
212 | }, | 757 | }, |
213 | - "execution_count": 124, | 758 | + "execution_count": 6, |
214 | "metadata": {}, | 759 | "metadata": {}, |
215 | "output_type": "execute_result" | 760 | "output_type": "execute_result" |
216 | } | 761 | } |
... | @@ -222,7 +767,7 @@ | ... | @@ -222,7 +767,7 @@ |
222 | }, | 767 | }, |
223 | { | 768 | { |
224 | "cell_type": "code", | 769 | "cell_type": "code", |
225 | - "execution_count": 125, | 770 | + "execution_count": 39, |
226 | "metadata": {}, | 771 | "metadata": {}, |
227 | "outputs": [ | 772 | "outputs": [ |
228 | { | 773 | { |
... | @@ -231,7 +776,7 @@ | ... | @@ -231,7 +776,7 @@ |
231 | "{'삼성전자': 0.443, 'LG전자': 0.238, '카카오': 0.319}" | 776 | "{'삼성전자': 0.443, 'LG전자': 0.238, '카카오': 0.319}" |
232 | ] | 777 | ] |
233 | }, | 778 | }, |
234 | - "execution_count": 125, | 779 | + "execution_count": 39, |
235 | "metadata": {}, | 780 | "metadata": {}, |
236 | "output_type": "execute_result" | 781 | "output_type": "execute_result" |
237 | } | 782 | } |
... | @@ -240,6 +785,219 @@ | ... | @@ -240,6 +785,219 @@ |
240 | "#risk parity -> 포트폴리오에 대한 자산 위험 비중을 동일하게 조정, 즉 삼전,lg,카카오의 포트폴리오 위험 기여도를 0.33으로 하게 만드는 비중\n", | 785 | "#risk parity -> 포트폴리오에 대한 자산 위험 비중을 동일하게 조정, 즉 삼전,lg,카카오의 포트폴리오 위험 기여도를 0.33으로 하게 만드는 비중\n", |
241 | "c_Models(['삼성전자','LG전자','카카오'],[0,0,0],'2015-01-01','2021-04-01').rp_opt()" | 786 | "c_Models(['삼성전자','LG전자','카카오'],[0,0,0],'2015-01-01','2021-04-01').rp_opt()" |
242 | ] | 787 | ] |
788 | + }, | ||
789 | + { | ||
790 | + "cell_type": "code", | ||
791 | + "execution_count": null, | ||
792 | + "metadata": { | ||
793 | + "scrolled": true | ||
794 | + }, | ||
795 | + "outputs": [], | ||
796 | + "source": [ | ||
797 | + "#def backtest_data(self,assets,weight,start_data_1, end_data_1,start_amount,rebalancing_month, interval, opt_option):\n", | ||
798 | + "back_test().backtest_data(['삼성전자','LG전자'],[0.9,0.1],'2010-01-01', '2021-01-01',10000000,3, 'monthly', 'basic')" | ||
799 | + ] | ||
800 | + }, | ||
801 | + { | ||
802 | + "cell_type": "code", | ||
803 | + "execution_count": 185, | ||
804 | + "metadata": { | ||
805 | + "scrolled": true | ||
806 | + }, | ||
807 | + "outputs": [ | ||
808 | + { | ||
809 | + "data": { | ||
810 | + "text/html": [ | ||
811 | + "<div>\n", | ||
812 | + "<style scoped>\n", | ||
813 | + " .dataframe tbody tr th:only-of-type {\n", | ||
814 | + " vertical-align: middle;\n", | ||
815 | + " }\n", | ||
816 | + "\n", | ||
817 | + " .dataframe tbody tr th {\n", | ||
818 | + " vertical-align: top;\n", | ||
819 | + " }\n", | ||
820 | + "\n", | ||
821 | + " .dataframe thead th {\n", | ||
822 | + " text-align: right;\n", | ||
823 | + " }\n", | ||
824 | + "</style>\n", | ||
825 | + "<table border=\"1\" class=\"dataframe\">\n", | ||
826 | + " <thead>\n", | ||
827 | + " <tr style=\"text-align: right;\">\n", | ||
828 | + " <th></th>\n", | ||
829 | + " <th>Open</th>\n", | ||
830 | + " <th>High</th>\n", | ||
831 | + " <th>Low</th>\n", | ||
832 | + " <th>Close</th>\n", | ||
833 | + " <th>Volume</th>\n", | ||
834 | + " <th>Change</th>\n", | ||
835 | + " </tr>\n", | ||
836 | + " <tr>\n", | ||
837 | + " <th>Date</th>\n", | ||
838 | + " <th></th>\n", | ||
839 | + " <th></th>\n", | ||
840 | + " <th></th>\n", | ||
841 | + " <th></th>\n", | ||
842 | + " <th></th>\n", | ||
843 | + " <th></th>\n", | ||
844 | + " </tr>\n", | ||
845 | + " </thead>\n", | ||
846 | + " <tbody>\n", | ||
847 | + " <tr>\n", | ||
848 | + " <td>1997-06-02</td>\n", | ||
849 | + " <td>1215</td>\n", | ||
850 | + " <td>1222</td>\n", | ||
851 | + " <td>1179</td>\n", | ||
852 | + " <td>1190</td>\n", | ||
853 | + " <td>74990</td>\n", | ||
854 | + " <td>nan</td>\n", | ||
855 | + " </tr>\n", | ||
856 | + " <tr>\n", | ||
857 | + " <td>1997-06-03</td>\n", | ||
858 | + " <td>1190</td>\n", | ||
859 | + " <td>1195</td>\n", | ||
860 | + " <td>1174</td>\n", | ||
861 | + " <td>1176</td>\n", | ||
862 | + " <td>71360</td>\n", | ||
863 | + " <td>-0.012</td>\n", | ||
864 | + " </tr>\n", | ||
865 | + " <tr>\n", | ||
866 | + " <td>1997-06-04</td>\n", | ||
867 | + " <td>1161</td>\n", | ||
868 | + " <td>1197</td>\n", | ||
869 | + " <td>1161</td>\n", | ||
870 | + " <td>1197</td>\n", | ||
871 | + " <td>85220</td>\n", | ||
872 | + " <td>0.018</td>\n", | ||
873 | + " </tr>\n", | ||
874 | + " <tr>\n", | ||
875 | + " <td>1997-06-05</td>\n", | ||
876 | + " <td>1193</td>\n", | ||
877 | + " <td>1206</td>\n", | ||
878 | + " <td>1181</td>\n", | ||
879 | + " <td>1188</td>\n", | ||
880 | + " <td>81890</td>\n", | ||
881 | + " <td>-0.008</td>\n", | ||
882 | + " </tr>\n", | ||
883 | + " <tr>\n", | ||
884 | + " <td>1997-06-07</td>\n", | ||
885 | + " <td>1197</td>\n", | ||
886 | + " <td>1215</td>\n", | ||
887 | + " <td>1190</td>\n", | ||
888 | + " <td>1197</td>\n", | ||
889 | + " <td>32550</td>\n", | ||
890 | + " <td>0.008</td>\n", | ||
891 | + " </tr>\n", | ||
892 | + " <tr>\n", | ||
893 | + " <td>...</td>\n", | ||
894 | + " <td>...</td>\n", | ||
895 | + " <td>...</td>\n", | ||
896 | + " <td>...</td>\n", | ||
897 | + " <td>...</td>\n", | ||
898 | + " <td>...</td>\n", | ||
899 | + " <td>...</td>\n", | ||
900 | + " </tr>\n", | ||
901 | + " <tr>\n", | ||
902 | + " <td>2021-05-28</td>\n", | ||
903 | + " <td>79800</td>\n", | ||
904 | + " <td>80400</td>\n", | ||
905 | + " <td>79400</td>\n", | ||
906 | + " <td>80100</td>\n", | ||
907 | + " <td>12360199</td>\n", | ||
908 | + " <td>0.006</td>\n", | ||
909 | + " </tr>\n", | ||
910 | + " <tr>\n", | ||
911 | + " <td>2021-05-31</td>\n", | ||
912 | + " <td>80300</td>\n", | ||
913 | + " <td>80600</td>\n", | ||
914 | + " <td>79600</td>\n", | ||
915 | + " <td>80500</td>\n", | ||
916 | + " <td>13321324</td>\n", | ||
917 | + " <td>0.005</td>\n", | ||
918 | + " </tr>\n", | ||
919 | + " <tr>\n", | ||
920 | + " <td>2021-06-01</td>\n", | ||
921 | + " <td>80500</td>\n", | ||
922 | + " <td>81300</td>\n", | ||
923 | + " <td>80100</td>\n", | ||
924 | + " <td>80600</td>\n", | ||
925 | + " <td>14058401</td>\n", | ||
926 | + " <td>0.001</td>\n", | ||
927 | + " </tr>\n", | ||
928 | + " <tr>\n", | ||
929 | + " <td>2021-06-02</td>\n", | ||
930 | + " <td>80400</td>\n", | ||
931 | + " <td>81400</td>\n", | ||
932 | + " <td>80300</td>\n", | ||
933 | + " <td>80800</td>\n", | ||
934 | + " <td>16414644</td>\n", | ||
935 | + " <td>0.002</td>\n", | ||
936 | + " </tr>\n", | ||
937 | + " <tr>\n", | ||
938 | + " <td>2021-06-03</td>\n", | ||
939 | + " <td>81300</td>\n", | ||
940 | + " <td>83000</td>\n", | ||
941 | + " <td>81100</td>\n", | ||
942 | + " <td>82800</td>\n", | ||
943 | + " <td>29341312</td>\n", | ||
944 | + " <td>0.025</td>\n", | ||
945 | + " </tr>\n", | ||
946 | + " </tbody>\n", | ||
947 | + "</table>\n", | ||
948 | + "<p>6000 rows × 6 columns</p>\n", | ||
949 | + "</div>" | ||
950 | + ], | ||
951 | + "text/plain": [ | ||
952 | + " Open High Low Close Volume Change\n", | ||
953 | + "Date \n", | ||
954 | + "1997-06-02 1215 1222 1179 1190 74990 nan\n", | ||
955 | + "1997-06-03 1190 1195 1174 1176 71360 -0.012\n", | ||
956 | + "1997-06-04 1161 1197 1161 1197 85220 0.018\n", | ||
957 | + "1997-06-05 1193 1206 1181 1188 81890 -0.008\n", | ||
958 | + "1997-06-07 1197 1215 1190 1197 32550 0.008\n", | ||
959 | + "... ... ... ... ... ... ...\n", | ||
960 | + "2021-05-28 79800 80400 79400 80100 12360199 0.006\n", | ||
961 | + "2021-05-31 80300 80600 79600 80500 13321324 0.005\n", | ||
962 | + "2021-06-01 80500 81300 80100 80600 14058401 0.001\n", | ||
963 | + "2021-06-02 80400 81400 80300 80800 16414644 0.002\n", | ||
964 | + "2021-06-03 81300 83000 81100 82800 29341312 0.025\n", | ||
965 | + "\n", | ||
966 | + "[6000 rows x 6 columns]" | ||
967 | + ] | ||
968 | + }, | ||
969 | + "execution_count": 185, | ||
970 | + "metadata": {}, | ||
971 | + "output_type": "execute_result" | ||
972 | + } | ||
973 | + ], | ||
974 | + "source": [ | ||
975 | + "df = fdr.DataReader('005930')\n", | ||
976 | + "df" | ||
977 | + ] | ||
978 | + }, | ||
979 | + { | ||
980 | + "cell_type": "code", | ||
981 | + "execution_count": 192, | ||
982 | + "metadata": {}, | ||
983 | + "outputs": [ | ||
984 | + { | ||
985 | + "name": "stdout", | ||
986 | + "output_type": "stream", | ||
987 | + "text": [ | ||
988 | + "{\"gmv\": [0.727, 0.0, 0.273], \"ms\": [0.674, 0.0, 0.326], \"rp\": [0.443, 0.238, 0.319]}\n", | ||
989 | + "[0.674, 0.0, 0.326]\n" | ||
990 | + ] | ||
991 | + } | ||
992 | + ], | ||
993 | + "source": [ | ||
994 | + "c_m = c_Models(['삼성전자','LG전자','카카오'],[0,0,0],'2015-01-01','2021-04-01')\n", | ||
995 | + "ret_vol, efpoints, weights = c_m.plotting()\n", | ||
996 | + "print(weights)\n", | ||
997 | + "weights = literal_eval(weights)\n", | ||
998 | + "weights = weights.get('ms')\n", | ||
999 | + "print(weights)" | ||
1000 | + ] | ||
243 | } | 1001 | } |
244 | ], | 1002 | ], |
245 | "metadata": { | 1003 | "metadata": { | ... | ... |
-
Please register or login to post a comment