Showing
3 changed files
with
776 additions
and
13 deletions
... | @@ -12,6 +12,9 @@ const bodyParser = require('body-parser'); | ... | @@ -12,6 +12,9 @@ const bodyParser = require('body-parser'); |
12 | var app = express(); | 12 | var app = express(); |
13 | 13 | ||
14 | 14 | ||
15 | +var holder1 = ''; | ||
16 | +var holder2 = ''; | ||
17 | + | ||
15 | 18 | ||
16 | 19 | ||
17 | app.use(bodyParser.json()); | 20 | app.use(bodyParser.json()); |
... | @@ -25,20 +28,39 @@ app.post('/hook', function (req, res) { | ... | @@ -25,20 +28,39 @@ app.post('/hook', function (req, res) { |
25 | console.log('[request]', req.body); | 28 | console.log('[request]', req.body); |
26 | console.log('[request source] ', eventObj.source); | 29 | console.log('[request source] ', eventObj.source); |
27 | console.log('[request message]', eventObj.message); | 30 | console.log('[request message]', eventObj.message); |
28 | - | 31 | + if(eventObj.type == 'postback') |
32 | + { | ||
33 | + if(eventObj.postback.data == 'action=datetemp&selectId=1') | ||
34 | + { | ||
35 | + console.log("optimizer 실행") | ||
36 | + app.use('/simages', express.static(__dirname + '/src')); | ||
37 | + optimizer(eventObj.replyToken, holder1, holder2, eventObj.postback.params.date) | ||
38 | + app.use('/simages', express.static(__dirname + '/src')); | ||
39 | + } | ||
40 | + } | ||
41 | + else | ||
42 | + { | ||
43 | + if(eventObj.message.text.indexOf(' ') != -1) | ||
44 | + { | ||
45 | + date(eventObj.replyToken, eventObj.message.text) | ||
46 | + } | ||
47 | + else | ||
48 | + { | ||
29 | basicinform(eventObj.replyToken, eventObj.message.text) | 49 | basicinform(eventObj.replyToken, eventObj.message.text) |
50 | + } | ||
51 | + | ||
52 | + } | ||
53 | + | ||
30 | 54 | ||
31 | res.sendStatus(200); | 55 | res.sendStatus(200); |
32 | 56 | ||
33 | }); | 57 | }); |
34 | 58 | ||
35 | function basicinform(replyToken, message) { | 59 | function basicinform(replyToken, message) { |
36 | - | ||
37 | var pystring; | 60 | var pystring; |
38 | const spawn = require("child_process").spawn; | 61 | const spawn = require("child_process").spawn; |
39 | const process = spawn("python", ["basic.py", message]); | 62 | const process = spawn("python", ["basic.py", message]); |
40 | const Callback = (data) => { | 63 | const Callback = (data) => { |
41 | - console.log("Data :", data.toString()); | ||
42 | pystring = data.toString(); | 64 | pystring = data.toString(); |
43 | if(pystring[0] == '1') | 65 | if(pystring[0] == '1') |
44 | { | 66 | { |
... | @@ -113,11 +135,133 @@ function basicinform(replyToken, message) { | ... | @@ -113,11 +135,133 @@ function basicinform(replyToken, message) { |
113 | console.log(body) | 135 | console.log(body) |
114 | }); | 136 | }); |
115 | } | 137 | } |
138 | + }; | ||
139 | + process.stdout.on("data", Callback); | ||
140 | +} | ||
116 | 141 | ||
142 | +function optimizer(replyToken, stock1, stock2, sdate) { | ||
143 | + sdate = sdate.toString(); | ||
144 | + console.log(typeof(stock1), typeof(stock2), typeof(sdate)) | ||
145 | + console.log(stock1, stock2, sdate) | ||
146 | + const spawn = require("child_process").spawn; | ||
147 | + const process = spawn("python", ["optimizer.py", stock1, stock2, sdate]); | ||
148 | + const Callback = (data) => { | ||
149 | + console.log(stock1, stock2, sdate) | ||
150 | + request.post( | ||
151 | + { | ||
152 | + url: TARGET_URL, | ||
153 | + headers: { | ||
154 | + 'Authorization': `Bearer ${TOKEN}` | ||
155 | + }, | ||
156 | + json: { | ||
157 | + "replyToken":replyToken, | ||
158 | + "messages":[ | ||
159 | + { | ||
160 | + "type":"text", | ||
161 | + "text":'조회하신 ' + holder1 +', ' + holder2 + '의 백테스트 결과입니다.' | ||
162 | + }, | ||
163 | + { | ||
164 | + "type":"image", | ||
165 | + "originalContentUrl": "https://2017103989.oss2021.tk:23023/simages/test.png", | ||
166 | + "previewImageUrl": "https://2017103989.oss2021.tk:23023/simages/test.png" | ||
167 | + } | ||
168 | + ] | ||
169 | + } | ||
170 | + },(error, response, body) => { | ||
171 | + console.log(body) | ||
172 | + }); | ||
173 | + } | ||
174 | + process.stdout.on("data", Callback); | ||
175 | +} | ||
117 | 176 | ||
177 | +function date(replyToken, message) { | ||
178 | + var holder = message.split(' ') | ||
179 | + holder1 = holder[0] | ||
180 | + holder2 = holder[1] | ||
181 | + var today = new Date(); | ||
182 | + var year = today.getFullYear(); | ||
183 | + var month = today.getMonth() + 1; | ||
184 | + var date = today.getDate(); | ||
185 | + if(month < 10) | ||
186 | + { | ||
187 | + month = '0'+ month | ||
188 | + } | ||
189 | + if(date < 10) | ||
190 | + { | ||
191 | + date = '0'+ date | ||
192 | + } | ||
193 | + var stoday = year + '-' + month + '-' + date; | ||
118 | 194 | ||
195 | + | ||
196 | + const messageObject = { | ||
197 | + "type": "template", | ||
198 | + "altText": "this is a buttons template", | ||
199 | + "template": { | ||
200 | + "type": "buttons", | ||
201 | + "title": "조회하실 날짜를 선택하세요.", | ||
202 | + "text": "선택하신 날짜에서 현재(오늘)까지 조회됩니다.", | ||
203 | + "actions": [ | ||
204 | + { | ||
205 | + "type": "datetimepicker", | ||
206 | + "label": "날짜 선택", | ||
207 | + "mode": "date", | ||
208 | + "initial":"2020-01-01", | ||
209 | + "max":stoday, | ||
210 | + "min":"2010-01-01", | ||
211 | + "data": "action=datetemp&selectId=1" | ||
212 | + }, | ||
213 | + { | ||
214 | + "type": "postback", | ||
215 | + "label": "처음부터 다시할래요", | ||
216 | + "data": "action=cancel&selectId=2" | ||
217 | + }, | ||
218 | + ] | ||
219 | + } | ||
119 | }; | 220 | }; |
120 | - process.stdout.on("data", Callback); | 221 | + request.post( |
222 | + { | ||
223 | + url: TARGET_URL, | ||
224 | + headers: { | ||
225 | + 'Authorization': `Bearer ${TOKEN}` | ||
226 | + }, | ||
227 | + json: { | ||
228 | + "replyToken":replyToken, | ||
229 | + "messages":[ | ||
230 | + // { | ||
231 | + // "type":"text", | ||
232 | + // "text":'조회하실 날짜를 선택하세요. 선택하신 날짜에서 현재까지 조회됩니다.', | ||
233 | + // "quickReply": { | ||
234 | + // "items": [ | ||
235 | + // { | ||
236 | + // "type": "action", | ||
237 | + // "action": { | ||
238 | + // "type": "datetimepicker", | ||
239 | + // "label":"날짜 선택하기", | ||
240 | + // "data":"storeId=12345", | ||
241 | + // "mode":"date", | ||
242 | + // "initial":"2015-01-01", | ||
243 | + // "max":stoday, | ||
244 | + // "min":"2010-01-01" | ||
245 | + // } | ||
246 | + // } | ||
247 | + // ] | ||
248 | + // } | ||
249 | + // }, | ||
250 | + // { | ||
251 | + // "type":"text", | ||
252 | + // "text":req.body.postback.params | ||
253 | + // } | ||
254 | + messageObject | ||
255 | + ] | ||
256 | + | ||
257 | + | ||
258 | + } | ||
259 | + },(error, response, body) => { | ||
260 | + console.log(body) | ||
261 | + }); | ||
262 | + | ||
263 | + | ||
264 | + | ||
121 | } | 265 | } |
122 | 266 | ||
123 | try { | 267 | try { | ... | ... |
... | @@ -10,7 +10,8 @@ def get_matches(query, choices, limit=3): | ... | @@ -10,7 +10,8 @@ def get_matches(query, choices, limit=3): |
10 | 10 | ||
11 | 11 | ||
12 | def basicinform(input): | 12 | def basicinform(input): |
13 | - stocks = pd.read_csv('stockcodename.csv', names=['Symbol', 'Market', 'Name', 'Sector', 'Industry', 'ListingDate', 'SettleMonth', 'Represetitive', 'HomePage', 'Region'], index_col=0) | 13 | + stocks = pd.read_csv('stockcodename.csv', names=['Symbol', 'Market', 'Name' |
14 | + , 'Sector', 'Industry', 'ListingDate', 'SettleMonth', 'Represetitive', 'HomePage', 'Region'], index_col=0) | ||
14 | symbol = '' | 15 | symbol = '' |
15 | 16 | ||
16 | for i in enumerate(stocks.Name): | 17 | for i in enumerate(stocks.Name): |
... | @@ -23,8 +24,8 @@ def basicinform(input): | ... | @@ -23,8 +24,8 @@ def basicinform(input): |
23 | cand = '' | 24 | cand = '' |
24 | for i in fuzzy: | 25 | for i in fuzzy: |
25 | cand += i[0] | 26 | cand += i[0] |
26 | - cand += " " | 27 | + cand += "\n" |
27 | - cand += "중 찾는게 있으신가요? \n다시 입력해주세요." | 28 | + cand += "중 찾는게 있으신가요? 다시 입력해주세요." |
28 | return cand | 29 | return cand |
29 | 30 | ||
30 | df = fdr.DataReader(symbol) | 31 | df = fdr.DataReader(symbol) |
... | @@ -33,15 +34,19 @@ def basicinform(input): | ... | @@ -33,15 +34,19 @@ def basicinform(input): |
33 | price = df.Close.iloc[-1] | 34 | price = df.Close.iloc[-1] |
34 | ror = ror_df[-1] | 35 | ror = ror_df[-1] |
35 | 36 | ||
36 | - value = { | 37 | + ror = round(ror, 4) |
37 | - "현재가": price, | 38 | + ror = ror * 100 |
38 | - "거래랑": volume, | 39 | + value = '' |
39 | - "전일 대비 수익률:": ror | 40 | + value = "1현재가: " + str(price) + "원\n거래랑: " + str(volume) + "건\n전일대비: " + str(ror) + "%" |
40 | - } | 41 | + # value = { |
42 | + # "현재가": price, | ||
43 | + # "거래랑": volume, | ||
44 | + # "전일 대비 수익률:": ror | ||
45 | + # } | ||
41 | return value | 46 | return value |
42 | 47 | ||
43 | 48 | ||
44 | -# print(basicinform('신라호텔')) | 49 | +# print(basicinform('호텔신라')) |
45 | 50 | ||
46 | args = sys.argv | 51 | args = sys.argv |
47 | print(basicinform(args[1])) | 52 | print(basicinform(args[1])) | ... | ... |
server/optimizer.py
0 → 100644
1 | +import datetime | ||
2 | +import pandas as pd | ||
3 | +import numpy as np | ||
4 | +import FinanceDataReader as fdr | ||
5 | +from scipy.optimize import minimize | ||
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 | ||
18 | +from matplotlib import pyplot as plt | ||
19 | +import numpy as np | ||
20 | +import matplotlib.ticker as ticker | ||
21 | +import sys | ||
22 | + | ||
23 | + | ||
24 | +#소숫점 표현 | ||
25 | +pd.options.display.float_format = '{:.3f}'.format | ||
26 | +np.set_printoptions(precision=3, suppress=True) | ||
27 | + | ||
28 | +class c_Models: | ||
29 | + #Input 값으로, 자산 list, 사용자 포트폴리오 비중, 시작일, 마지막일 | ||
30 | + def __init__(self, assets, assets_w, start, end): | ||
31 | + self.result = None | ||
32 | + self.graph = None | ||
33 | + | ||
34 | + stocks = pd.read_csv('stockcodename.csv', index_col=0) | ||
35 | + symbol = '' | ||
36 | + self.asset_name = assets[:] | ||
37 | + for k in range(len(assets)): | ||
38 | + for i in enumerate(stocks.Name): | ||
39 | + if i[1] == assets[k]: | ||
40 | + assets[k] = (stocks.iloc[i[0]].Symbol) | ||
41 | + break | ||
42 | + | ||
43 | + data = pd.DataFrame() | ||
44 | + # 전체 자산 data들을 가지고 온 후, 정리함 | ||
45 | + | ||
46 | + for asset in assets: #total_list: | ||
47 | + tmp = fdr.DataReader(asset,start,end).Close | ||
48 | + if len(data) == 0 : | ||
49 | + data = tmp | ||
50 | + else: | ||
51 | + data = pd.concat([data,tmp], axis=1) | ||
52 | + | ||
53 | + data.columns = self.asset_name | ||
54 | + | ||
55 | + if data.isnull().values.any() == True: #불러온 data에 오류가 있다면 | ||
56 | + return "No Data",'' | ||
57 | + | ||
58 | + else: | ||
59 | + data = data.resample('M').mean() #일별 데이터를 월별 데이터로 만들어줌 | ||
60 | + data = data.pct_change() #월별 주가 데이터를 이용해 수익률 데이터로 변환 | ||
61 | + data.dropna(inplace=True) #결측치 제외(첫 row) | ||
62 | + | ||
63 | + self.data = data | ||
64 | + self.assets_w = assets_w | ||
65 | + self.mu = data.mean() * 12 | ||
66 | + self.cov = data.cov() * 12 | ||
67 | + | ||
68 | + #GMV 최적화 : 제약 조건은 비중합=1, 공매도 불가능 | ||
69 | + def gmv_opt(self): | ||
70 | + n_assets = len(self.data.columns) | ||
71 | + w0 = np.ones(n_assets) / n_assets | ||
72 | + fun = lambda w: np.dot(w.T, np.dot(self.cov, w)) | ||
73 | + constraints = ({'type':'eq', 'fun':lambda x: np.sum(x)-1}) | ||
74 | + bd = ((0,1),) * n_assets | ||
75 | + #cov = data.cov() * 12 | ||
76 | + gmv = minimize(fun, w0, method = 'SLSQP', constraints=constraints, bounds=bd) | ||
77 | + result = dict(zip(self.asset_name, np.round(gmv.x,3))) | ||
78 | + return result | ||
79 | + | ||
80 | + #Max Sharp ratio : risk free rate은 0.8%로 지정했고, | ||
81 | + def ms_opt(self): | ||
82 | + n_assets = len(self.data.columns) | ||
83 | + w0 = np.ones(n_assets) / n_assets | ||
84 | + fun = lambda w: -(np.dot(w.T, self.mu) - 0.008) / np.sqrt(np.dot(w.T, np.dot(self.cov, w))) | ||
85 | + bd = ((0,1),) * n_assets | ||
86 | + constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) | ||
87 | + maxsharp = minimize(fun, w0, method ='SLSQP', constraints=constraints, bounds=bd) | ||
88 | + result = dict(zip(self.asset_name, np.round(maxsharp.x,3))) | ||
89 | + return result | ||
90 | + | ||
91 | + def rp_opt(self): | ||
92 | + def RC(cov, w): | ||
93 | + pfo_std = np.sqrt(np.dot(w.T, np.dot(self.cov, w))) | ||
94 | + mrc = 1/pfo_std * (np.dot(self.cov, w)) | ||
95 | + rc = mrc * w | ||
96 | + rc = rc / rc.sum() | ||
97 | + return rc | ||
98 | + | ||
99 | + | ||
100 | + def RP_objective(x): | ||
101 | + pfo_std = np.sqrt(np.dot(x.T, np.dot(self.cov, x))) | ||
102 | + mrc = 1/pfo_std * (np.dot(self.cov, x)) | ||
103 | + rc = mrc * x | ||
104 | + rc = rc / rc.sum() | ||
105 | + | ||
106 | + a = np.reshape(rc, (len(rc),1)) | ||
107 | + differs = a - a.T | ||
108 | + objective = np.sum(np.square(differs)) | ||
109 | + | ||
110 | + return objective | ||
111 | + | ||
112 | + n_assets = len(self.data.columns) | ||
113 | + w0 = np.ones(n_assets) / n_assets | ||
114 | + constraints = [{'type':'eq', 'fun': lambda x: np.sum(x) -1}] | ||
115 | + bd = ((0,1),) * n_assets | ||
116 | + | ||
117 | + rp = minimize(RP_objective, w0, constraints=constraints, bounds = bd, method='SLSQP') | ||
118 | + result = dict(zip(self.asset_name, np.round(rp.x,3))) | ||
119 | + return result #, RC(self.cov, rp.x) | ||
120 | + | ||
121 | + def plotting(self): | ||
122 | + wt_gmv = np.asarray(list(self.gmv_opt().values())) | ||
123 | + wt_ms = np.asarray(list(self.ms_opt().values())) | ||
124 | + wt_rp = np.asarray(list(self.rp_opt().values())) | ||
125 | + | ||
126 | + ret_gmv = np.dot(wt_gmv, self.mu) | ||
127 | + ret_ms = np.dot(wt_ms, self.mu) | ||
128 | + ret_rp = np.dot(wt_rp, self.mu) | ||
129 | + vol_gmv = np.sqrt(np.dot(wt_gmv.T, np.dot(self.cov, wt_gmv))) | ||
130 | + vol_ms = np.sqrt(np.dot(wt_ms.T, np.dot(self.cov, wt_ms))) | ||
131 | + vol_rp = np.sqrt(np.dot(wt_rp.T, np.dot(self.cov, wt_rp))) | ||
132 | + | ||
133 | + wt_gmv = wt_gmv.tolist() | ||
134 | + wt_ms = wt_ms.tolist() | ||
135 | + wt_rp = wt_rp.tolist() | ||
136 | + | ||
137 | + user_ret = np.dot(self.assets_w, self.mu) | ||
138 | + user_risk = np.sqrt(np.dot(self.assets_w, np.dot(self.cov, self.assets_w))) | ||
139 | + | ||
140 | + weights = {'gmv': wt_gmv, "ms" : wt_ms, "rp": wt_rp} | ||
141 | + | ||
142 | + #rec_rs = recommended_asset() | ||
143 | + | ||
144 | + trets = np.linspace(ret_gmv, max(self.mu), 30) # 30개 짜르기 | ||
145 | + tvols = [] | ||
146 | + | ||
147 | + efpoints = dict() | ||
148 | + for i, tret in enumerate(trets): #이 개별 return마다 최소 risk 찾기 | ||
149 | + n_assets = len(self.data.columns) | ||
150 | + w0 = np.ones(n_assets) / n_assets | ||
151 | + fun = lambda w: np.dot(w.T ,np.dot(self.cov, w)) | ||
152 | + constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, | ||
153 | + {'type': 'ineq', 'fun': lambda x: np.dot(x, self.mu) - tret}] | ||
154 | + #{'type': 'ineq', 'fun': lambda x: x}] | ||
155 | + bd = ((0,1),) * n_assets | ||
156 | + | ||
157 | + minvol = minimize(fun, w0, method='SLSQP',bounds = bd, constraints=constraints) | ||
158 | + tvols.append(np.sqrt(np.dot(minvol.x, np.dot(self.cov, minvol.x)))) | ||
159 | + | ||
160 | + pnumber = '{}point'.format(i+1) | ||
161 | + efpoints[pnumber] = minvol.x.tolist() | ||
162 | + | ||
163 | + if self.data.shape[0] <= 1: | ||
164 | + error = '기간에러' | ||
165 | + return error,1,1 | ||
166 | + else: | ||
167 | + 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} | ||
168 | + return ret_vol, json.dumps(efpoints), json.dumps(weights) | ||
169 | + | ||
170 | + | ||
171 | +class back_test: | ||
172 | + # 단순 일별수익률의 평균을 *365하여 연간수익률을 산출 | ||
173 | + def __init__(self): | ||
174 | + self.test = 0 | ||
175 | + | ||
176 | + def Arithmetic_Mean_Annual(self,ret): | ||
177 | + month_return = np.mean(ret) | ||
178 | + return (month_return*252) | ||
179 | + | ||
180 | + # 기간중 투자했을때 하락할 수 있는 비율 | ||
181 | + def dd(self,ret): | ||
182 | + cum_ret = (1 + ret).cumprod() | ||
183 | + max_drawdown = 0 | ||
184 | + max_ret = 1 | ||
185 | + dd_list = [] | ||
186 | + c = 0 | ||
187 | + for ix_ret in cum_ret.values: | ||
188 | + if max_ret < ix_ret: | ||
189 | + max_ret = ix_ret | ||
190 | + dd_list.append((ix_ret - max_ret) / max_ret) | ||
191 | + c= c+1 | ||
192 | + return dd_list | ||
193 | + | ||
194 | + # 기간중 투자했을때 최고로 많이 하락할 수 있는 비율 | ||
195 | + def mdd(self,ret): | ||
196 | + | ||
197 | + cum_ret = (1 + ret).cumprod() | ||
198 | + max_drawdown = 0 | ||
199 | + max_ret = 1 | ||
200 | + for ix_ret in cum_ret.values: | ||
201 | + if max_drawdown > (ix_ret - max_ret) / max_ret: | ||
202 | + max_drawdown = (ix_ret - max_ret) / max_ret | ||
203 | + if max_ret < ix_ret: | ||
204 | + max_ret = ix_ret | ||
205 | + | ||
206 | + return abs(max_drawdown) | ||
207 | + | ||
208 | + # 포트폴리오 수익률에서 무위험 수익률을 제한 후 이를 포트폴리오의 표준편차로 나눠 산출한 값, 즉 위험대비 얼마나 수익이 좋은지의 척도 | ||
209 | + def sharpe_ratio(self,ret, rf=0.008, num_of_date=252): | ||
210 | + | ||
211 | + return ((np.mean(ret - (rf / num_of_date))) / (np.std(ret))) * np.sqrt(num_of_date) | ||
212 | + | ||
213 | + # 설정한 confidence level에 따른(95%) 확률로 발생할 수 있는 손실액의 최대 액수 | ||
214 | + def value_at_risk(self,ret, para_or_hist="para", confidence_level=0.95): | ||
215 | + | ||
216 | + vol = np.std(ret) | ||
217 | + if para_or_hist == "para": | ||
218 | + VaR = np.mean(ret) - vol * norm.ppf(confidence_level) | ||
219 | + else: | ||
220 | + print('error') | ||
221 | + | ||
222 | + return VaR | ||
223 | + | ||
224 | + # 전체 투자기간에서 상승한 ( ret > 0 ) 기간의 비율 | ||
225 | + def winning_rate(self,ret): | ||
226 | + var_winning_rate = np.sum(ret > 0) / len(ret) | ||
227 | + return var_winning_rate | ||
228 | + | ||
229 | + # 상승한날의 평균상승값을 하락한날의 평균하락값으로 나눈 비율 | ||
230 | + def profit_loss_ratio(self,ret): | ||
231 | + | ||
232 | + if np.sum(ret > 0) == 0: | ||
233 | + var_profit_loss_ratio = 0 | ||
234 | + elif np.sum(ret < 0) == 0: | ||
235 | + var_profit_loss_ratio = np.inf | ||
236 | + else: | ||
237 | + win_mean = np.mean(ret[ret > 0]) | ||
238 | + loss_mean = np.mean(ret[ret < 0]) | ||
239 | + var_profit_loss_ratio = win_mean / loss_mean | ||
240 | + return abs(var_profit_loss_ratio) | ||
241 | + | ||
242 | + # 데이터 취합하는 코드 | ||
243 | + #임시로 5가지 데이터 예시를 활용해 코드작성 | ||
244 | + # 선택한 종목의 이름과 비중, 투자기간을 input 값으로 받음 | ||
245 | + | ||
246 | + def backtest_data(self, assets,weight,start_data_1, end_data_1,start_amount,rebalancing_month, interval, opt_option): | ||
247 | + # input으로 받는 assetnames 입력 | ||
248 | + a = assets | ||
249 | + stock_num = len(a) | ||
250 | + # input으로 받는 assetweights 입력 | ||
251 | + rebal_month = int(rebalancing_month) | ||
252 | + # input으로 받는 rebalancing_month를 입력 | ||
253 | + # 나타내는 데이터 간격을 표시 | ||
254 | + | ||
255 | + # weight 간격 | ||
256 | + b = list(map(float, weight)) | ||
257 | + | ||
258 | + | ||
259 | + # input으로 받는 from_period와 to_period 입력 | ||
260 | + stock_return = pd.date_range(start=start_data_1, end=end_data_1) | ||
261 | + stock_return = pd.DataFrame(stock_return) | ||
262 | + stock_return.columns = ['Date'] | ||
263 | + | ||
264 | + stocks = pd.read_csv('stockcodename.csv', index_col=0) | ||
265 | + symbol = '' | ||
266 | + asset_name = assets[:] | ||
267 | + for k in range(len(assets)): | ||
268 | + for i in enumerate(stocks.Name): | ||
269 | + if i[1] == assets[k]: | ||
270 | + assets[k] = (stocks.iloc[i[0]].Symbol) | ||
271 | + break | ||
272 | + | ||
273 | + # input으로 받는 from_period와 to_period 입력 | ||
274 | + stock_return = pd.date_range(start=start_data_1, end=end_data_1) | ||
275 | + stock_return = pd.DataFrame(stock_return) | ||
276 | + stock_return.columns = ['Date'] | ||
277 | + | ||
278 | + | ||
279 | + for asset in assets: #total_list: | ||
280 | + tmp = fdr.DataReader(asset,start_data_1,end_data_1) | ||
281 | + tmp.insert(1,"Date",tmp.index.copy(),True) | ||
282 | + tmp = tmp[['Date','Change']] | ||
283 | + tmp.columns = ['Date',asset] | ||
284 | + tmp = tmp.reset_index(drop=True) | ||
285 | + stock_return = pd.merge(stock_return,tmp,how='inner', on='Date') | ||
286 | + | ||
287 | + stock_return = stock_return.dropna(axis=0) | ||
288 | + | ||
289 | + #print(stock_return) | ||
290 | + if opt_option == 'basic' : | ||
291 | + | ||
292 | + # 투자비중으로 이루어진 dataframe 만들기 | ||
293 | + | ||
294 | + start_datetime = stock_return.iloc[0,0] | ||
295 | + end_datetime = stock_return.iloc[-1,0] | ||
296 | + diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=start_datetime, until=end_datetime)) | ||
297 | + month_gap = len(diff_months_list) | ||
298 | + rebal_roof = month_gap//rebal_month | ||
299 | + rebal_weight = pd.DataFrame() | ||
300 | + | ||
301 | + for i in range(rebal_roof+1): | ||
302 | + # 데이터로부터 리밸런싱기간만큼 가져오기 | ||
303 | + filtered_df =stock_return.loc[stock_return["Date"].between(start_datetime, | ||
304 | + start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))] | ||
305 | + # 리밸런싱 기간의 누적수익률 산출 | ||
306 | + for j in range(stock_num): | ||
307 | + filtered_df.iloc[:,j+1] = (1 + filtered_df.iloc[:,j+1]).cumprod() | ||
308 | + # 해당 누적수익률에 initial 투자비중을 곱해준다 | ||
309 | + for j in range(stock_num): | ||
310 | + filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]*float(b[j]) | ||
311 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
312 | + filtered_df['total_value'] = filtered_df.sum(axis=1) | ||
313 | + for j in range(stock_num): | ||
314 | + filtered_df.iloc[:,j+1] = filtered_df.iloc[:,j+1]/filtered_df['total_value'] | ||
315 | + | ||
316 | + rebal_weight = pd.concat([rebal_weight,filtered_df]) | ||
317 | + start_datetime = start_datetime + relativedelta(months=rebal_month) | ||
318 | + | ||
319 | + #final_day = monthrange(start_datetime.year, start_datetime.month) | ||
320 | + | ||
321 | + stock_weight = rebal_weight.iloc[:,:-1] | ||
322 | + #print(stock_weight) | ||
323 | + ''' | ||
324 | + stock_weight = stock_return.Date | ||
325 | + stock_weight = pd.DataFrame(stock_weight) | ||
326 | + c = 0 | ||
327 | + for stockweight in b: | ||
328 | + stock_weight[a[c]] = float(stockweight) | ||
329 | + c = c + 1 | ||
330 | + #print(stock_weight) | ||
331 | + ''' | ||
332 | + else : | ||
333 | + # 포트폴리오 최적화 코드를 통한 리벨런싱 이중 리스트 weight 산출 | ||
334 | + # 1. 입력 받은 start ~ end 날짜를 리밸런싱 기간으로 쪼개기 | ||
335 | + opt_start_datetime = stock_return.iloc[0,0] | ||
336 | + opt_end_datetime = stock_return.iloc[-1,0] | ||
337 | + opt_diff_months_list = list(rrule.rrule(rrule.MONTHLY, dtstart=opt_start_datetime, until=opt_end_datetime)) | ||
338 | + opt_month_gap = len(opt_diff_months_list) | ||
339 | + opt_rebal_roof = opt_month_gap//rebal_month | ||
340 | + opt_rebal_weight = pd.DataFrame() | ||
341 | + #opt_array = [[0]*stock_num]*(opt_rebal_roof+1) | ||
342 | + | ||
343 | + for i in range(opt_rebal_roof+1): | ||
344 | + opt_df = stock_return.loc[stock_return["Date"].between(opt_start_datetime,opt_start_datetime + relativedelta(months=rebal_month)+relativedelta(days = -1))] | ||
345 | + # 최적화 코드에서 기간마다의 가중치를 가져온다 | ||
346 | + c_m = c_Models(a,b,opt_df.iat[0,0]- relativedelta(months=3),opt_df.iat[-1,0]) | ||
347 | + ret_vol, efpoints, weights = c_m.plotting() | ||
348 | + weights = literal_eval(weights) | ||
349 | + weights = weights.get(opt_option) | ||
350 | + ##print(weights) | ||
351 | + # 리밸런싱 기간의 누적수익률 산출 | ||
352 | + for j in range(stock_num): | ||
353 | + opt_df.iloc[:,j+1] = (1 + opt_df.iloc[:,j+1]).cumprod() | ||
354 | + # 해당 누적수익률에 initial 투자비중을 곱해준다 | ||
355 | + for j in range(stock_num): | ||
356 | + opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]*float(weights[j]) | ||
357 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
358 | + opt_df['total_value'] = opt_df.sum(axis=1) | ||
359 | + for j in range(stock_num): | ||
360 | + opt_df.iloc[:,j+1] = opt_df.iloc[:,j+1]/opt_df['total_value'] | ||
361 | + | ||
362 | + # 이후 각각의 종목의 비중을 계산해서 산출한다 | ||
363 | + #print(opt_df) | ||
364 | + opt_rebal_weight = pd.concat([opt_rebal_weight,opt_df]) | ||
365 | + opt_start_datetime = opt_start_datetime + relativedelta(months=rebal_month) | ||
366 | + #리밸런싱으로 start 기간이 고객이 원하는 end 기간보다 커지게 되면 종료 | ||
367 | + if opt_start_datetime > stock_return.iloc[-1,0]: # i가 100일 때 | ||
368 | + break | ||
369 | + stock_weight = opt_rebal_weight.iloc[:,:-1] | ||
370 | + ##print(stock_weight) | ||
371 | + # 수익률 데이터와 투자비중을 곱한 하나의 데이터 생성 | ||
372 | + pfo_return = stock_weight.Date | ||
373 | + pfo_return = pd.DataFrame(pfo_return) | ||
374 | + # weight 와 return의 날짜 맞춰주기 | ||
375 | + #pfo_return = pfo_return[0:len(stock_weight)] | ||
376 | + pfo_return = pd.merge(pfo_return, stock_return, left_on='Date', right_on='Date', how='left') | ||
377 | + pfo_return['mean_return'] = 0 | ||
378 | + ##print(pfo_return) | ||
379 | + for i in range(0,len(pfo_return)): | ||
380 | + return_result = list(pfo_return.iloc[i,1:1+stock_num]) | ||
381 | + return_weight = list(stock_weight.iloc[i,1:1+stock_num]) | ||
382 | + pfo_return.iloc[i,1+stock_num] = np.dot(return_result,return_weight) | ||
383 | + #rint(pfo_return) | ||
384 | + pfo_return['acc_return'] = [x+1 for x in pfo_return['mean_return']] | ||
385 | + pfo_return['acc_return'] = list(it.accumulate(pfo_return['acc_return'], operator.mul)) | ||
386 | + pfo_return['acc_return'] = [x-1 for x in pfo_return['acc_return']] | ||
387 | + pfo_return['final_balance'] = float(start_amount) + float(start_amount)*pfo_return['acc_return'] | ||
388 | + pfo_return['Drawdown_list'] = back_test.dd(input,pfo_return['mean_return']) | ||
389 | + pfo_return = pfo_return.set_index('Date') | ||
390 | + #print(pfo_return) | ||
391 | + | ||
392 | + | ||
393 | + ### 벤치마크 데이터 로드 및 전처리 | ||
394 | + | ||
395 | + tiker_list = ['KS11','US500'] | ||
396 | + bench_list = [fdr.DataReader(ticker, start_data_1, end_data_1)['Change'] for ticker in tiker_list] | ||
397 | + bench = pd.concat(bench_list, axis=1) | ||
398 | + bench.columns = ['KOSPI', 'S&P500'] | ||
399 | + bench['KOSPI'] = bench['KOSPI'].fillna(0) | ||
400 | + bench['S&P500'] = bench['S&P500'].fillna(0) | ||
401 | + #bench = bench.dropna() | ||
402 | + | ||
403 | + # 벤치마크 누적수익률, DD 값 | ||
404 | + | ||
405 | + bench['KOSPI_acc'] = [x+1 for x in bench['KOSPI']] | ||
406 | + bench['KOSPI_acc'] = list(it.accumulate(bench['KOSPI_acc'], operator.mul)) | ||
407 | + bench['KOSPI_acc'] = [x-1 for x in bench['KOSPI_acc']] | ||
408 | + bench['KOSPI_balance'] = float(start_amount) + float(start_amount)*bench['KOSPI_acc'] | ||
409 | + bench['KOSPI_Drawdown'] = back_test.dd(input,bench['KOSPI']) | ||
410 | + bench['S&P500_acc'] = [x+1 for x in bench['S&P500']] | ||
411 | + bench['S&P500_acc'] = list(it.accumulate(bench['S&P500_acc'], operator.mul)) | ||
412 | + bench['S&P500_acc'] = [x-1 for x in bench['S&P500_acc']] | ||
413 | + bench['S&P500_balance'] = float(start_amount) + float(start_amount)*bench['S&P500_acc'] | ||
414 | + bench['S&P500_Drawdown'] = back_test.dd(input,bench['S&P500']) | ||
415 | + | ||
416 | + if interval == 'monthly' or interval == 'weekly' : | ||
417 | + if interval == 'monthly' : | ||
418 | + inter = 'M' | ||
419 | + if interval == 'weekly' : | ||
420 | + inter = 'W' | ||
421 | + pfo_return_interval = pfo_return.resample(inter).last() | ||
422 | + pfo_return_first = pd.DataFrame(pfo_return.iloc[0]).transpose() | ||
423 | + pfo_return_interval = pd.concat([pfo_return_first, pfo_return_interval]) | ||
424 | + pfo_return_interval['mean_return'] = pfo_return_interval['final_balance'].pct_change() | ||
425 | + pfo_return_interval = pfo_return_interval.dropna() | ||
426 | + | ||
427 | + # 월별 간격으로 만들어주기, 여기서는 return과 value만 monthly로 산출함 나머지값은 daily | ||
428 | + bench_interval = bench.resample(inter).last() | ||
429 | + #bench_ex['KOSPI'] = bench_ex['final_balance'].pct_change() | ||
430 | + bench_first = pd.DataFrame(bench.iloc[0]).transpose() | ||
431 | + bench_interval = pd.concat([bench_first, bench_interval]) | ||
432 | + bench_interval['KOSPI'] = bench_interval['KOSPI_balance'].pct_change() | ||
433 | + bench_interval['S&P500'] = bench_interval['S&P500_balance'].pct_change() | ||
434 | + bench_interval = bench_interval.dropna() | ||
435 | + | ||
436 | + # 날짜타입 열로 만들기 및 str 타입으로 전처리 | ||
437 | + pfo_return = pfo_return.rename_axis('Date').reset_index() | ||
438 | + pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date | ||
439 | + pfo_return['Date'] = list(map(str, pfo_return['Date'])) | ||
440 | + | ||
441 | + pfo_return_interval = pfo_return_interval.rename_axis('Date').reset_index() | ||
442 | + pfo_return_interval['Date'] = pd.to_datetime(pfo_return_interval['Date'], format='%d/%m/%Y').dt.date | ||
443 | + pfo_return_interval['Date'] = list(map(str, pfo_return_interval['Date'])) | ||
444 | + | ||
445 | + bench = bench.rename_axis('Date').reset_index() | ||
446 | + bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date | ||
447 | + bench['Date'] = list(map(str, bench['Date'])) | ||
448 | + | ||
449 | + bench_interval = bench_interval.rename_axis('Date').reset_index() | ||
450 | + bench_interval['Date'] = pd.to_datetime(bench_interval['Date'], format='%d/%m/%Y').dt.date | ||
451 | + bench_interval['Date'] = list(map(str, bench_interval['Date'])) | ||
452 | + | ||
453 | + backtest_return = { | ||
454 | + 'pfo_return': [ | ||
455 | + { | ||
456 | + 'Date': list(pfo_return_interval['Date']), | ||
457 | + 'mean_return': list(pfo_return_interval['mean_return']), | ||
458 | + 'acc_return ratio': list(pfo_return_interval['acc_return']), | ||
459 | + 'final_balance': list(pfo_return_interval['final_balance']), | ||
460 | + 'Drawdown_list' : list(pfo_return_interval['Drawdown_list']) | ||
461 | + } | ||
462 | + ], | ||
463 | + 'bench': [ | ||
464 | + { | ||
465 | + 'Date': list(bench_interval['Date']), | ||
466 | + 'KOSPI_return': list(bench_interval['KOSPI']), | ||
467 | + 'S&P500_return': list(bench_interval['S&P500']), | ||
468 | + 'KOSPI_acc_return': list(bench_interval['KOSPI_acc']), | ||
469 | + 'KOSPI_balance' : list(bench_interval['KOSPI_balance']), | ||
470 | + 'KOSPI_Drawdown': list(bench_interval['KOSPI_Drawdown']), | ||
471 | + 'S&P500_acc_return': list(bench_interval['S&P500_acc']), | ||
472 | + 'S&P500_balance' : list(bench_interval['S&P500_balance']), | ||
473 | + 'S&P500_Drawdown': list(bench_interval['S&P500_Drawdown']) | ||
474 | + } | ||
475 | + ], | ||
476 | + 'indicator': [ | ||
477 | + { | ||
478 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']), | ||
479 | + 'Std': pfo_return['mean_return'].std() * np.sqrt(365), | ||
480 | + 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']), | ||
481 | + 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']), | ||
482 | + 'MDD': back_test.mdd(input,pfo_return['mean_return']), | ||
483 | + 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']), | ||
484 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return']) | ||
485 | + } | ||
486 | + ], | ||
487 | + 'KOSPI_indicator': [ | ||
488 | + { | ||
489 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']), | ||
490 | + 'Std': bench['KOSPI'].std() * np.sqrt(365), | ||
491 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']), | ||
492 | + 'VaR': back_test.value_at_risk(input,bench['KOSPI']), | ||
493 | + 'MDD': back_test.mdd(input,bench['KOSPI']), | ||
494 | + 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']), | ||
495 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI']) | ||
496 | + } | ||
497 | + ], | ||
498 | + 'S&P500_indicator': [ | ||
499 | + { | ||
500 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']), | ||
501 | + 'Std': bench['S&P500'].std() * np.sqrt(365), | ||
502 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']), | ||
503 | + 'VaR': back_test.value_at_risk(input,bench['S&P500']), | ||
504 | + 'MDD': back_test.mdd(input,bench['S&P500']), | ||
505 | + 'Winning ratio': back_test.winning_rate(input,bench['S&P500']), | ||
506 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500']) | ||
507 | + } | ||
508 | + ] | ||
509 | + } | ||
510 | + | ||
511 | + else : | ||
512 | + # 날짜타입 열로 만들기 및 str 타입으로 전처리 | ||
513 | + pfo_return = pfo_return.rename_axis('Date').reset_index() | ||
514 | + pfo_return['Date'] = pd.to_datetime(pfo_return['Date'], format='%d/%m/%Y').dt.date | ||
515 | + pfo_return['Date'] = list(map(str, pfo_return['Date'])) | ||
516 | + | ||
517 | + bench = bench.rename_axis('Date').reset_index() | ||
518 | + bench['Date'] = pd.to_datetime(bench['Date'], format='%d/%m/%Y').dt.date | ||
519 | + bench['Date'] = list(map(str, bench['Date'])) | ||
520 | + backtest_return = { | ||
521 | + 'pfo_return': [ | ||
522 | + { | ||
523 | + 'Date': list(pfo_return['Date']), | ||
524 | + 'mean_return': list(pfo_return['mean_return']), | ||
525 | + 'acc_return ratio': list(pfo_return['acc_return']), | ||
526 | + 'final_balance': list(pfo_return['final_balance']), | ||
527 | + 'Drawdown_list' : list(pfo_return['Drawdown_list']) | ||
528 | + } | ||
529 | + ], | ||
530 | + 'bench': [ | ||
531 | + { | ||
532 | + 'Date': list(bench['Date']), | ||
533 | + 'KOSPI_return': list(bench['KOSPI']), | ||
534 | + 'S&P500_return': list(bench['S&P500']), | ||
535 | + 'KOSPI_acc_return': list(bench['KOSPI_acc']), | ||
536 | + 'KOSPI_balance' : list(bench['KOSPI_balance']), | ||
537 | + 'KOSPI_Drawdown': list(bench['KOSPI_Drawdown']), | ||
538 | + 'S&P500_acc_return': list(bench['S&P500_acc']), | ||
539 | + 'S&P500_balance' : list(bench['S&P500_balance']), | ||
540 | + 'S&P500_Drawdown': list(bench['S&P500_Drawdown']) | ||
541 | + } | ||
542 | + ], | ||
543 | + 'indicator': [ | ||
544 | + { | ||
545 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,pfo_return['mean_return']), | ||
546 | + 'Std': pfo_return['mean_return'].std() * np.sqrt(365), | ||
547 | + 'Sharpe ratio': back_test.sharpe_ratio(input,pfo_return['mean_return']), | ||
548 | + 'VaR': back_test.value_at_risk(input,pfo_return['mean_return']), | ||
549 | + 'MDD': back_test.mdd(input,pfo_return['mean_return']), | ||
550 | + 'Winning ratio': back_test.winning_rate(input,pfo_return['mean_return']), | ||
551 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,pfo_return['mean_return']) | ||
552 | + } | ||
553 | + ], | ||
554 | + 'KOSPI_indicator': [ | ||
555 | + { | ||
556 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['KOSPI']), | ||
557 | + 'Std': bench['KOSPI'].std() * np.sqrt(365), | ||
558 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['KOSPI']), | ||
559 | + 'VaR': back_test.value_at_risk(input,bench['KOSPI']), | ||
560 | + 'MDD': back_test.mdd(input,bench['KOSPI']), | ||
561 | + 'Winning ratio': back_test.winning_rate(input,bench['KOSPI']), | ||
562 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['KOSPI']) | ||
563 | + } | ||
564 | + ], | ||
565 | + 'S&P500_indicator': [ | ||
566 | + { | ||
567 | + 'Mean': back_test.Arithmetic_Mean_Annual(input,bench['S&P500']), | ||
568 | + 'Std': bench['S&P500'].std() * np.sqrt(365), | ||
569 | + 'Sharpe ratio': back_test.sharpe_ratio(input,bench['S&P500']), | ||
570 | + 'VaR': back_test.value_at_risk(input,bench['S&P500']), | ||
571 | + 'MDD': back_test.mdd(input,bench['S&P500']), | ||
572 | + 'Winning ratio': back_test.winning_rate(input,bench['S&P500']), | ||
573 | + 'Gain/Loss Ratio': back_test.profit_loss_ratio(input,bench['S&P500']) | ||
574 | + } | ||
575 | + ] | ||
576 | + } | ||
577 | + | ||
578 | + return backtest_return | ||
579 | + | ||
580 | + | ||
581 | + | ||
582 | +# print(back_test().backtest_data(['삼성전자','LG전자'],[0.9,0.1],'2010-01-01', '2021-01-01',10000000,3, 'monthly', 'gmv')['pfo_return'].mean_return) | ||
583 | +# print(back_test().backtest_data(['삼성전자','LG전자'],[0.9,0.1],'2010-01-01', '2021-01-01',10000000,3, 'monthly', 'gmv')['pfo_return'][0]['acc_return_ratio']) | ||
584 | +# print(back_test().backtest_data(['삼성전자','LG전자'],[0.9,0.1],'2018-01-01', '2021-01-01',10000000,6, 'monthly', 'gmv')) | ||
585 | + | ||
586 | + | ||
587 | +data = back_test().backtest_data([sys.argv[1],sys.argv[2]],[0.5,0.5],sys.argv[3], '2021-01-02',10000000,6, 'monthly', 'gmv') | ||
588 | +# data = back_test().backtest_data(['삼성전자','LG전자'],[0.5,0.5],'2020-01-01', '2021-01-02',10000000,6, 'monthly', 'gmv') | ||
589 | +x = data['pfo_return'][0]['Date'] | ||
590 | +y = data['pfo_return'][0]['acc_return ratio'] | ||
591 | +y2 = data['bench'][0]['KOSPI_acc_return'] | ||
592 | +y3 = data['bench'][0]['S&P500_acc_return'] | ||
593 | +x_ticks = [] | ||
594 | +for i,j in enumerate(x): | ||
595 | + if (i % 6) == 0: | ||
596 | + x_ticks.append(j) | ||
597 | + else: | ||
598 | + x_ticks.append('') | ||
599 | +x_ticks[-1]= x[-1] | ||
600 | +plt.figure(figsize=(10,5)) | ||
601 | +ax=plt.gca() | ||
602 | +ax.xaxis.set_major_locator(ticker.MultipleLocator(12)) | ||
603 | +plt.plot(x,y,label = 'gmv result') | ||
604 | +plt.plot(x,y2 ,label = 'kospi result') | ||
605 | +plt.plot(x,y3, label = 's&p500 result') | ||
606 | +plt.xticks(x_ticks,rotation=60) | ||
607 | +plt.xlabel('Date') | ||
608 | +plt.ylabel('Return') | ||
609 | +plt.title('result') | ||
610 | +plt.legend() | ||
611 | +plt.show() | ||
612 | +plt.savefig("./src/test.png", dpi = 400) | ||
613 | +print("end") | ||
614 | + |
-
Please register or login to post a comment