Ubuntu

update files

...@@ -10,26 +10,75 @@ const sslport = 23023; ...@@ -10,26 +10,75 @@ const sslport = 23023;
10 10
11 const bodyParser = require('body-parser'); 11 const bodyParser = require('body-parser');
12 var app = express(); 12 var app = express();
13 +
14 +
15 +
16 +var holder1 = '';
17 +var holder2 = '';
18 +
19 +
20 +
13 app.use(bodyParser.json()); 21 app.use(bodyParser.json());
14 app.post('/hook', function (req, res) { 22 app.post('/hook', function (req, res) {
15 23
16 var eventObj = req.body.events[0]; 24 var eventObj = req.body.events[0];
17 var source = eventObj.source; 25 var source = eventObj.source;
18 -
19 var message = eventObj.message; 26 var message = eventObj.message;
27 +<<<<<<< HEAD
20 var pystring; 28 var pystring;
21 console.log(message); 29 console.log(message);
22 - const spawn = require("child_process").spawn; 30 +=======
23 - const process = spawn("python", ["basic.py", eventObj.message.text]);
24 - const Callback = (data) => {
25 - console.log("Data :", data.toString());
26 - pystring = data.toString();
27 // request log 31 // request log
28 console.log('======================', new Date() ,'======================'); 32 console.log('======================', new Date() ,'======================');
29 console.log('[request]', req.body); 33 console.log('[request]', req.body);
30 console.log('[request source] ', eventObj.source); 34 console.log('[request source] ', eventObj.source);
31 console.log('[request message]', eventObj.message); 35 console.log('[request message]', eventObj.message);
36 + if(eventObj.type == 'postback')
37 + {
38 + if(eventObj.postback.data == 'action=datetemp&selectId=1')
39 + {
40 + console.log("optimizer 실행")
41 + app.use('/simages', express.static(__dirname + '/src'));
42 + optimizer(eventObj.replyToken, holder1, holder2, eventObj.postback.params.date)
43 + app.use('/simages', express.static(__dirname + '/src'));
44 + }
45 + }
46 + else
47 + {
48 + if(eventObj.message.text == '도움말')
49 + {
50 + printhelp(eventObj.replyToken, eventObj.message.text)
51 + }
52 + else if(eventObj.message.text == '주가')
53 + {
54 + printhelp(eventObj.replyToken, eventObj.message.text)
55 + }
56 + else if(eventObj.message.text == '비중 추천')
57 + {
58 + printhelp(eventObj.replyToken, eventObj.message.text)
59 + }
60 + else if(eventObj.message.text == '백테스트')
61 + {
62 + printhelp(eventObj.replyToken, eventObj.message.text)
63 + }
64 + else if(eventObj.message.text.indexOf(' ') != -1)
65 + {
66 + date(eventObj.replyToken, eventObj.message.text)
67 + }
68 + else
69 + {
70 + basicinform(eventObj.replyToken, eventObj.message.text)
71 + }
72 +
73 + }
74 +
75 +
76 + res.sendStatus(200);
32 77
78 +});
79 +
80 +function printhelp(replyToken, message){
81 + if(message == '도움말'){
33 request.post( 82 request.post(
34 { 83 {
35 url: TARGET_URL, 84 url: TARGET_URL,
...@@ -37,27 +86,344 @@ app.post('/hook', function (req, res) { ...@@ -37,27 +86,344 @@ app.post('/hook', function (req, res) {
37 'Authorization': `Bearer ${TOKEN}` 86 'Authorization': `Bearer ${TOKEN}`
38 }, 87 },
39 json: { 88 json: {
40 - "replyToken":eventObj.replyToken, 89 + "replyToken":replyToken,
41 "messages":[ 90 "messages":[
42 { 91 {
43 "type":"text", 92 "type":"text",
44 - "text":pystring 93 + "text":"궁금하신 기능을 선택해주세요.",
94 + "quickReply": {
95 + "items": [
96 + {
97 + "type": "action",
98 + "action": {
99 + "type": "message",
100 + "label": '주가',
101 + "text": '주가'
102 + }
103 + },
104 + {
105 + "type": "action",
106 + "action": {
107 + "type": "message",
108 + "label": '비중 추천',
109 + "text": '비중 추천'
110 + }
111 + },
112 + {
113 + "type": "action",
114 + "action": {
115 + "type": "message",
116 + "label": '백테스트',
117 + "text": '백테스트'
118 + }
119 + }
120 + ]
121 + }
122 + }
123 + ]
124 + }
125 + },(error, response, body) => {
126 + console.log(body)
127 + });
128 + }
129 + else if(message == '주가')
130 + {
131 + request.post(
132 + {
133 + url: TARGET_URL,
134 + headers: {
135 + 'Authorization': `Bearer ${TOKEN}`
136 + },
137 + json: {
138 + "replyToken":replyToken,
139 + "messages":[
140 + {
141 + "type":"text",
142 + "text":"사용자 : 주가\n챗봇: 종목명을 알려주세요.\n사용자: 종목명 입력 (ex 삼성전자)\n챗봇 : 현재가 거래량 전일대비 수익률"
143 + }
144 + ]
145 + }
146 + },(error, response, body) => {
147 + console.log(body)
148 + });
149 + }
150 + else if(message == '비중 추천')
151 + {
152 + request.post(
153 + {
154 + url: TARGET_URL,
155 + headers: {
156 + 'Authorization': `Bearer ${TOKEN}`
157 + },
158 + json: {
159 + "replyToken":replyToken,
160 + "messages":[
161 + {
162 + "type":"text",
163 + "text":"사용자 : 비중 추천\n챗봇 : 포트폴리오에 넣을 종목을 선택해주세요(2 종목 이상)\n사용자 : 종목명 입력 (ex 삼성전자, LG전자 ...)\n챗봇: 전략을 선택해주세요. (gmv, ms , rp)\n사용자: gmv, ms, rp 중 입력\n챗봇 : 케이스에 맞게 함수 실행 후 비중 출력"
164 + }
165 + ]
166 + }
167 + },(error, response, body) => {
168 + console.log(body)
169 + });
170 + }
171 + else if(message == '백테스트')
172 + {
173 + request.post(
174 + {
175 + url: TARGET_URL,
176 + headers: {
177 + 'Authorization': `Bearer ${TOKEN}`
178 + },
179 + json: {
180 + "replyToken":replyToken,
181 + "messages":[
182 + {
183 + "type":"text",
184 + "text":"사용자 : 백테스트\n챗봇: 포트폴리오에 넣을 종목을 선택해주세요(2 종목 이상)\n사용자: 종목명 입력\n챗봇: 시작할 날짜를 입력해주세요\n사용자: 입력\n챗봇: 전략을 선택해주세요 (gmv, ms , rp)\n사용자: gmv,ms,rp 중 입력\n챗봇: 함수 실행 후 그래프 출력"
185 + }
186 + ]
187 + }
188 + },(error, response, body) => {
189 + console.log(body)
190 + });
191 + }
192 + else if(message == '주가')
193 + {
194 + request.post(
195 + {
196 + url: TARGET_URL,
197 + headers: {
198 + 'Authorization': `Bearer ${TOKEN}`
45 }, 199 },
200 + json: {
201 + "replyToken":replyToken,
202 + "messages":[
203 + {
204 + "type":"text",
205 + "text":"사용자 : 주가\n챗봇: 종목명을 알려주세요.\n사용자: 종목명 입력 (ex 삼성전자)\n챗봇 : 현재가 거래량 전일대비 수익률"
206 + }
46 ] 207 ]
47 } 208 }
48 },(error, response, body) => { 209 },(error, response, body) => {
49 console.log(body) 210 console.log(body)
50 }); 211 });
212 + }
51 213
214 +}
52 215
53 - res.sendStatus(200); 216 +function basicinform(replyToken, message) {
217 + var pystring;
218 +>>>>>>> f1c2d34f3272bc7cfebf6e887b72493185c4c57c
219 + const spawn = require("child_process").spawn;
220 + const process = spawn("python", ["basic.py", message]);
221 + const Callback = (data) => {
222 + pystring = data.toString();
223 + if(pystring[0] == '1')
224 + {
225 + pystring = pystring.replace('1현', '현');
226 + request.post(
227 + {
228 + url: TARGET_URL,
229 + headers: {
230 + 'Authorization': `Bearer ${TOKEN}`
231 + },
232 + json: {
233 + "replyToken":replyToken,
234 + "messages":[
235 + {
236 + "type":"text",
237 + "text":pystring
238 + }
239 + ]
240 + }
241 + },(error, response, body) => {
242 + console.log(body)
243 + });
244 + }
245 + else
246 + {
247 + var candarr = pystring.split('\n')
248 + request.post(
249 + {
250 + url: TARGET_URL,
251 + headers: {
252 + 'Authorization': `Bearer ${TOKEN}`
253 + },
254 + json: {
255 + "replyToken":replyToken,
256 + "messages":[
257 + {
258 + "type": "text",
259 + "text": pystring,
260 + "quickReply": {
261 + "items": [
262 + {
263 + "type": "action",
264 + "action": {
265 + "type": "message",
266 + "label": candarr[0],
267 + "text": candarr[0]
268 + }
269 + },
270 + {
271 + "type": "action",
272 + "action": {
273 + "type": "message",
274 + "label": candarr[1],
275 + "text": candarr[1]
276 + }
277 + },
278 + {
279 + "type": "action",
280 + "action": {
281 + "type": "message",
282 + "label": candarr[2],
283 + "text": candarr[2]
284 + }
285 + }
286 + ]
287 + }
288 + }
289 + ]
54 290
291 + }
292 + },(error, response, body) => {
293 + console.log(body)
294 + });
295 + }
55 }; 296 };
56 process.stdout.on("data", Callback); 297 process.stdout.on("data", Callback);
298 +}
299 +
300 +function optimizer(replyToken, stock1, stock2, sdate) {
301 + sdate = sdate.toString();
302 + console.log(typeof(stock1), typeof(stock2), typeof(sdate))
303 + console.log(stock1, stock2, sdate)
304 + const spawn = require("child_process").spawn;
305 + const process = spawn("python", ["optimizer.py", stock1, stock2, sdate]);
306 + const Callback = (data) => {
307 + console.log(stock1, stock2, sdate)
308 + request.post(
309 + {
310 + url: TARGET_URL,
311 + headers: {
312 + 'Authorization': `Bearer ${TOKEN}`
313 + },
314 + json: {
315 + "replyToken":replyToken,
316 + "messages":[
317 + {
318 + "type":"text",
319 + "text":'조회하신 ' + holder1 +', ' + holder2 + '의 백테스트 결과입니다.'
320 + },
321 +<<<<<<< HEAD
322 +=======
323 + {
324 + "type":"image",
325 + "originalContentUrl": "https://2017103989.oss2021.tk:23023/simages/test.png",
326 + "previewImageUrl": "https://2017103989.oss2021.tk:23023/simages/test.png"
327 + }
328 +>>>>>>> f1c2d34f3272bc7cfebf6e887b72493185c4c57c
329 + ]
330 + }
331 + },(error, response, body) => {
332 + console.log(body)
333 + });
334 + }
335 + process.stdout.on("data", Callback);
336 +}
337 +
338 +function date(replyToken, message) {
339 + var holder = message.split(' ')
340 + holder1 = holder[0]
341 + holder2 = holder[1]
342 + var today = new Date();
343 + var year = today.getFullYear();
344 + var month = today.getMonth() + 1;
345 + var date = today.getDate();
346 + if(month < 10)
347 + {
348 + month = '0'+ month
349 + }
350 + if(date < 10)
351 + {
352 + date = '0'+ date
353 + }
354 + var stoday = year + '-' + month + '-' + date;
355 +
356 +
357 + const messageObject = {
358 + "type": "template",
359 + "altText": "this is a buttons template",
360 + "template": {
361 + "type": "buttons",
362 + "title": "조회하실 날짜를 선택하세요.",
363 + "text": "선택하신 날짜에서 현재(오늘)까지 조회됩니다.",
364 + "actions": [
365 + {
366 + "type": "datetimepicker",
367 + "label": "날짜 선택",
368 + "mode": "date",
369 + "initial":"2020-01-01",
370 + "max":stoday,
371 + "min":"2010-01-01",
372 + "data": "action=datetemp&selectId=1"
373 + },
374 + {
375 + "type": "postback",
376 + "label": "처음부터 다시할래요",
377 + "data": "action=cancel&selectId=2"
378 + },
379 + ]
380 + }
381 + };
382 + request.post(
383 + {
384 + url: TARGET_URL,
385 + headers: {
386 + 'Authorization': `Bearer ${TOKEN}`
387 + },
388 + json: {
389 + "replyToken":replyToken,
390 + "messages":[
391 + // {
392 + // "type":"text",
393 + // "text":'조회하실 날짜를 선택하세요. 선택하신 날짜에서 현재까지 조회됩니다.',
394 + // "quickReply": {
395 + // "items": [
396 + // {
397 + // "type": "action",
398 + // "action": {
399 + // "type": "datetimepicker",
400 + // "label":"날짜 선택하기",
401 + // "data":"storeId=12345",
402 + // "mode":"date",
403 + // "initial":"2015-01-01",
404 + // "max":stoday,
405 + // "min":"2010-01-01"
406 + // }
407 + // }
408 + // ]
409 + // }
410 + // },
411 + // {
412 + // "type":"text",
413 + // "text":req.body.postback.params
414 + // }
415 + messageObject
416 + ]
57 417
58 418
419 + }
420 + },(error, response, body) => {
421 + console.log(body)
422 + });
59 423
60 -}); 424 +
425 +
426 +}
61 427
62 try { 428 try {
63 const option = { 429 const option = {
......
...@@ -10,7 +10,12 @@ def get_matches(query, choices, limit=3): ...@@ -10,7 +10,12 @@ def get_matches(query, choices, limit=3):
10 return result 10 return result
11 11
12 def basicinform(input): 12 def basicinform(input):
13 +<<<<<<< HEAD
13 stocks = pd.read_csv('stockcodename.csv', index_col=0) 14 stocks = pd.read_csv('stockcodename.csv', index_col=0)
15 +=======
16 + stocks = pd.read_csv('stockcodename.csv', names=['Symbol', 'Market', 'Name'
17 + , 'Sector', 'Industry', 'ListingDate', 'SettleMonth', 'Represetitive', 'HomePage', 'Region'], index_col=0)
18 +>>>>>>> f1c2d34f3272bc7cfebf6e887b72493185c4c57c
14 symbol = '' 19 symbol = ''
15 20
16 for i in enumerate(stocks.Name): 21 for i in enumerate(stocks.Name):
...@@ -23,8 +28,8 @@ def basicinform(input): ...@@ -23,8 +28,8 @@ def basicinform(input):
23 cand = '' 28 cand = ''
24 for i in fuzzy: 29 for i in fuzzy:
25 cand += i[0] 30 cand += i[0]
26 - cand += " " 31 + cand += "\n"
27 - cand += "중 찾는게 있으신가요? \n다시 입력해주세요." 32 + cand += "중 찾는게 있으신가요? 다시 입력해주세요."
28 return cand 33 return cand
29 34
30 df = fdr.DataReader(symbol) 35 df = fdr.DataReader(symbol)
...@@ -33,13 +38,22 @@ def basicinform(input): ...@@ -33,13 +38,22 @@ def basicinform(input):
33 price = df.Close.iloc[-1] 38 price = df.Close.iloc[-1]
34 ror = ror_df[-1] 39 ror = ror_df[-1]
35 40
36 - value = { 41 + ror = round(ror, 4)
37 - "현재가": price, 42 + ror = ror * 100
38 - "거래랑": volume, 43 + value = ''
39 - "전일 대비 수익률:": ror 44 + value = "1현재가: " + str(price) + "원\n거래랑: " + str(volume) + "건\n전일대비: " + str(ror) + "%"
40 - } 45 + # value = {
46 + # "현재가": price,
47 + # "거래랑": volume,
48 + # "전일 대비 수익률:": ror
49 + # }
41 return value 50 return value
42 51
43 52
53 +<<<<<<< HEAD
54 +=======
55 +# print(basicinform('호텔신라'))
56 +
57 +>>>>>>> f1c2d34f3272bc7cfebf6e887b72493185c4c57c
44 args = sys.argv 58 args = sys.argv
45 print(basicinform(args[0])) 59 print(basicinform(args[0]))
......
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 +