analyser_hj3415 2.8.2__py2.py3-none-any.whl → 2.9.0__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
analyser_hj3415/cli.py CHANGED
@@ -41,15 +41,22 @@ def analyser_manager():
41
41
  parser = argparse.ArgumentParser(description="Analyser Commands")
42
42
  type_subparsers = parser.add_subparsers(dest='type', help='분석 타입')
43
43
 
44
- # tsa 명령어 서브파서
45
- tsa_parser = type_subparsers.add_parser('tsa', help='Time serial analysis 타입')
46
- tsa_subparser = tsa_parser.add_subparsers(dest='command', help='tsa 관련된 명령')
47
- # red - ranking 파서
48
- ranking_parser = red_subparser.add_parser('ranking', help='red 랭킹 책정 및 레디스 저장')
49
- ranking_parser.add_argument('-e', '--expect_earn', type=float, help='기대수익률 (실수 값 입력)')
44
+ # prophet 명령어 서브파서
45
+ prophet_parser = type_subparsers.add_parser('prophet', help='MyProphet 타입')
46
+ prophet_subparser = prophet_parser.add_subparsers(dest='command', help='prophet 관련된 명령')
47
+ # ranking 파서
48
+ ranking_parser = prophet_subparser.add_parser('ranking', help='prophet 랭킹 책정 및 레디스 저장')
50
49
  ranking_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
51
50
  ranking_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
52
51
 
52
+ # lstm 명령어 서브파서
53
+ lstm_parser = type_subparsers.add_parser('lstm', help='MyLSTM 타입')
54
+ lstm_subparser = lstm_parser.add_subparsers(dest='command', help='lstm 관련된 명령')
55
+ # caching 파서
56
+ caching_parser = lstm_subparser.add_parser('caching', help='lstm 랭킹 책정 및 레디스 저장')
57
+ caching_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
58
+ caching_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
59
+
53
60
  # red 명령어 서브파서
54
61
  red_parser = type_subparsers.add_parser('red', help='red 타입')
55
62
  red_subparser = red_parser.add_subparsers(dest='command', help='red 관련된 명령')
@@ -110,7 +117,7 @@ def analyser_manager():
110
117
 
111
118
  args = parser.parse_args()
112
119
 
113
- from analyser_hj3415 import eval
120
+ from analyser_hj3415 import eval, tsa
114
121
 
115
122
  if args.type == 'red':
116
123
  if args.command == 'get':
@@ -204,7 +211,17 @@ def analyser_manager():
204
211
  pprint.pprint(growth.get(args.refresh))
205
212
  if args.noti:
206
213
  noti.telegram_to('manager', f"오늘의 Growth({args.code})를 레디스 캐시에 저장했습니다.(유효 12시간)")
207
-
214
+ elif args.type == 'prophet':
215
+ if args.command == 'ranking':
216
+ result = tsa.MyProphet.ranking(refresh=args.refresh)
217
+ print(result)
218
+ if args.noti:
219
+ noti.telegram_to('manager', "오늘의 prophet ranking을 레디스캐시에 저장했습니다.(유효 24시간)")
220
+ elif args.type == 'lstm':
221
+ if args.command == 'caching':
222
+ result = tsa.MyLSTM('005930').caching_based_on_prophet_ranking(refresh=args.refresh)
223
+ if args.noti:
224
+ noti.telegram_to('manager', "오늘의 lstm caching을 레디스캐시에 저장했습니다.(유효 24시간)")
208
225
  elif args.type == 'setting':
209
226
  if args.command == 'set':
210
227
  settings_manager.set_value(args.title, args.value)
analyser_hj3415/eval.py CHANGED
@@ -8,7 +8,7 @@ from analyser_hj3415.cli import AnalyserSettingsManager
8
8
  from collections import OrderedDict
9
9
  import logging
10
10
 
11
- analyser_logger = helpers.setup_logger('analyser_logger', logging.WARNING)
11
+ eval_logger = helpers.setup_logger('eval_logger', logging.WARNING)
12
12
 
13
13
  expire_time = 3600 * 12
14
14
 
@@ -48,30 +48,30 @@ class Tools:
48
48
  """
49
49
  name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
50
50
 
51
- analyser_logger.info(f'{c103.code} / {name} Tools : 당기순이익 계산.. refresh : {refresh}')
51
+ eval_logger.info(f'{c103.code} / {name} Tools : 당기순이익 계산.. refresh : {refresh}')
52
52
  c103.page = 'c103재무상태표q'
53
53
 
54
54
  d1, 지배당기순이익 = c103.latest_value_pop2('*(지배)당기순이익', refresh)
55
- analyser_logger.debug(f"*(지배)당기순이익: {지배당기순이익}")
55
+ eval_logger.debug(f"*(지배)당기순이익: {지배당기순이익}")
56
56
 
57
57
  if math.isnan(지배당기순이익):
58
- analyser_logger.warning(f"{c103.code} / {name} - (지배)당기순이익이 없는 종목. 수동으로 계산합니다.")
58
+ eval_logger.warning(f"{c103.code} / {name} - (지배)당기순이익이 없는 종목. 수동으로 계산합니다.")
59
59
  c103.page = 'c103손익계산서q'
60
60
  d2, 최근4분기당기순이익 = c103.sum_recent_4q('당기순이익', refresh)
61
- analyser_logger.debug(f"{c103.code} / {name} - 최근4분기당기순이익 : {최근4분기당기순이익}")
61
+ eval_logger.debug(f"{c103.code} / {name} - 최근4분기당기순이익 : {최근4분기당기순이익}")
62
62
  c103.page = 'c103재무상태표y'
63
63
  d3, 비지배당기순이익 = c103.latest_value_pop2('*(비지배)당기순이익', refresh)
64
- analyser_logger.debug(f"{c103.code} / {name} - 비지배당기순이익y : {비지배당기순이익}")
64
+ eval_logger.debug(f"{c103.code} / {name} - 비지배당기순이익y : {비지배당기순이익}")
65
65
  # 가변리스트 언패킹으로 하나의 날짜만 사용하고 나머지는 버린다.
66
66
  # 여기서 *_는 “나머지 값을 다 무시하겠다”는 의미
67
- analyser_logger.debug(f"d2:{d2}, d3: {d3}")
67
+ eval_logger.debug(f"d2:{d2}, d3: {d3}")
68
68
  try:
69
69
  date, *_ = Tools.date_set(d2, d3)
70
70
  except ValueError:
71
71
  # 날짜 데이터가 없는경우
72
72
  date = ''
73
73
  계산된지배당기순이익= round(최근4분기당기순이익 - utils.nan_to_zero(비지배당기순이익), 1)
74
- analyser_logger.debug(f"{c103.code} / {name} - 계산된 지배당기순이익 : {계산된지배당기순이익}")
74
+ eval_logger.debug(f"{c103.code} / {name} - 계산된 지배당기순이익 : {계산된지배당기순이익}")
75
75
  return date, 계산된지배당기순이익
76
76
  else:
77
77
  return d1, 지배당기순이익
@@ -85,20 +85,20 @@ class Tools:
85
85
  """
86
86
  name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
87
87
 
88
- analyser_logger.info(f'{c103.code} / {name} Tools : 유동자산계산... refresh : {refresh}')
88
+ eval_logger.info(f'{c103.code} / {name} Tools : 유동자산계산... refresh : {refresh}')
89
89
  c103.page = 'c103재무상태표q'
90
90
 
91
91
  d, 유동자산 = c103.sum_recent_4q('유동자산', refresh)
92
92
  if math.isnan(유동자산):
93
- analyser_logger.warning(f"{c103.code} / {name} - 유동자산이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
93
+ eval_logger.warning(f"{c103.code} / {name} - 유동자산이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
94
94
  d1, v1 = c103.latest_value_pop2('현금및예치금', refresh)
95
95
  d2, v2 = c103.latest_value_pop2('단기매매금융자산', refresh)
96
96
  d3, v3 = c103.latest_value_pop2('매도가능금융자산', refresh)
97
97
  d4, v4 = c103.latest_value_pop2('만기보유금융자산', refresh)
98
- analyser_logger.debug(f'{c103.code} / {name} 현금및예치금 : {d1}, {v1}')
99
- analyser_logger.debug(f'{c103.code} / {name} 단기매매금융자산 : {d2}, {v2}')
100
- analyser_logger.debug(f'{c103.code} / {name} 매도가능금융자산 : {d3}, {v3}')
101
- analyser_logger.debug(f'{c103.code} / {name} 만기보유금융자산 : {d4}, {v4}')
98
+ eval_logger.debug(f'{c103.code} / {name} 현금및예치금 : {d1}, {v1}')
99
+ eval_logger.debug(f'{c103.code} / {name} 단기매매금융자산 : {d2}, {v2}')
100
+ eval_logger.debug(f'{c103.code} / {name} 매도가능금융자산 : {d3}, {v3}')
101
+ eval_logger.debug(f'{c103.code} / {name} 만기보유금융자산 : {d4}, {v4}')
102
102
 
103
103
  try:
104
104
  date, *_ = Tools.date_set(d1, d2, d3, d4)
@@ -107,7 +107,7 @@ class Tools:
107
107
  date = ''
108
108
  계산된유동자산value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4),1)
109
109
 
110
- analyser_logger.info(f"{c103.code} / {name} - 계산된 유동자산 : {계산된유동자산value}")
110
+ eval_logger.info(f"{c103.code} / {name} - 계산된 유동자산 : {계산된유동자산value}")
111
111
  return date, 계산된유동자산value
112
112
  else:
113
113
  return d, 유동자산
@@ -121,20 +121,20 @@ class Tools:
121
121
  """
122
122
  name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
123
123
 
124
- analyser_logger.info(f'{c103.code} / {name} Tools : 유동부채계산... refresh : {refresh}')
124
+ eval_logger.info(f'{c103.code} / {name} Tools : 유동부채계산... refresh : {refresh}')
125
125
  c103.page = 'c103재무상태표q'
126
126
 
127
127
  d, 유동부채 = c103.sum_recent_4q('유동부채', refresh)
128
128
  if math.isnan(유동부채):
129
- analyser_logger.warning(f"{c103.code} / {name} - 유동부채가 없는 종목. 수동으로 계산합니다.")
129
+ eval_logger.warning(f"{c103.code} / {name} - 유동부채가 없는 종목. 수동으로 계산합니다.")
130
130
  d1, v1 = c103.latest_value_pop2('당기손익인식(지정)금융부채', refresh)
131
131
  d2, v2 = c103.latest_value_pop2('당기손익-공정가치측정금융부채', refresh)
132
132
  d3, v3 = c103.latest_value_pop2('매도파생결합증권', refresh)
133
133
  d4, v4 = c103.latest_value_pop2('단기매매금융부채', refresh)
134
- analyser_logger.debug(f'{c103.code} / {name} 당기손익인식(지정)금융부채 : {d1}, {v1}')
135
- analyser_logger.debug(f'{c103.code} / {name} 당기손익-공정가치측정금융부채 : {d2}, {v2}')
136
- analyser_logger.debug(f'{c103.code} / {name} 매도파생결합증권 : {d3}, {v3}')
137
- analyser_logger.debug(f'{c103.code} / {name} 단기매매금융부채 : {d4}, {v4}')
134
+ eval_logger.debug(f'{c103.code} / {name} 당기손익인식(지정)금융부채 : {d1}, {v1}')
135
+ eval_logger.debug(f'{c103.code} / {name} 당기손익-공정가치측정금융부채 : {d2}, {v2}')
136
+ eval_logger.debug(f'{c103.code} / {name} 매도파생결합증권 : {d3}, {v3}')
137
+ eval_logger.debug(f'{c103.code} / {name} 단기매매금융부채 : {d4}, {v4}')
138
138
 
139
139
  try:
140
140
  date, *_ = Tools.date_set(d1, d2, d3, d4)
@@ -143,7 +143,7 @@ class Tools:
143
143
  date = ''
144
144
  계산된유동부채value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4), 1)
145
145
 
146
- analyser_logger.info(f"{c103.code} / {name} - 계산된 유동부채 : {계산된유동부채value}")
146
+ eval_logger.info(f"{c103.code} / {name} - 계산된 유동부채 : {계산된유동부채value}")
147
147
  return date, 계산된유동부채value
148
148
  else:
149
149
  return d, 유동부채
@@ -192,7 +192,7 @@ class Red:
192
192
 
193
193
  def __init__(self, code: str):
194
194
  assert utils.is_6digit(code), f'Invalid value : {code}'
195
- analyser_logger.debug(f"Red : 초기화 ({code})")
195
+ eval_logger.debug(f"Red : 초기화 ({code})")
196
196
  self.c101 = myredis.C101(code)
197
197
  self.c103 = myredis.C103(code, 'c103재무상태표q')
198
198
 
@@ -209,7 +209,7 @@ class Red:
209
209
  @code.setter
210
210
  def code(self, code: str):
211
211
  assert utils.is_6digit(code), f'Invalid value : {code}'
212
- analyser_logger.debug(f"Red : 종목코드 변경({self.code} -> {code})")
212
+ eval_logger.debug(f"Red : 종목코드 변경({self.code} -> {code})")
213
213
  self.c101.code = code
214
214
  self.c103.code = code
215
215
 
@@ -222,21 +222,21 @@ class Red:
222
222
  일반적인 경우로 비유동부채를 찾아서 반환한다.\n
223
223
  금융기관의 경우는 간접적으로 계산한다.\n
224
224
  """
225
- analyser_logger.info(f'In the calc비유동부채... refresh : {refresh}')
225
+ eval_logger.info(f'In the calc비유동부채... refresh : {refresh}')
226
226
  self.c103.page = 'c103재무상태표q'
227
227
 
228
228
  d, 비유동부채 = self.c103.sum_recent_4q('비유동부채', refresh)
229
229
  if math.isnan(비유동부채):
230
- analyser_logger.warning(f"{self} - 비유동부채가 없는 종목. 수동으로 계산합니다.")
230
+ eval_logger.warning(f"{self} - 비유동부채가 없는 종목. 수동으로 계산합니다.")
231
231
  # 보험관련업종은 예수부채가 없는대신 보험계약부채가 있다...
232
232
  d1, v1 = self.c103.latest_value_pop2('예수부채', refresh)
233
233
  d2, v2 = self.c103.latest_value_pop2('보험계약부채(책임준비금)', refresh)
234
234
  d3, v3 = self.c103.latest_value_pop2('차입부채', refresh)
235
235
  d4, v4 = self.c103.latest_value_pop2('기타부채', refresh)
236
- analyser_logger.debug(f'예수부채 : {d1}, {v1}')
237
- analyser_logger.debug(f'보험계약부채(책임준비금) : {d2}, {v2}')
238
- analyser_logger.debug(f'차입부채 : {d3}, {v3}')
239
- analyser_logger.debug(f'기타부채 : {d4}, {v4}')
236
+ eval_logger.debug(f'예수부채 : {d1}, {v1}')
237
+ eval_logger.debug(f'보험계약부채(책임준비금) : {d2}, {v2}')
238
+ eval_logger.debug(f'차입부채 : {d3}, {v3}')
239
+ eval_logger.debug(f'기타부채 : {d4}, {v4}')
240
240
 
241
241
  try:
242
242
  date, *_ = Tools.date_set(d1, d2, d3, d4)
@@ -244,7 +244,7 @@ class Red:
244
244
  # 날짜 데이터가 없는경우
245
245
  date = ''
246
246
  계산된비유동부채value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4),1)
247
- analyser_logger.info(f"{self} - 계산된 비유동부채 : {계산된비유동부채value}")
247
+ eval_logger.info(f"{self} - 계산된 비유동부채 : {계산된비유동부채value}")
248
248
  return date, 계산된비유동부채value
249
249
  else:
250
250
  return d, 비유동부채
@@ -266,13 +266,13 @@ class Red:
266
266
  else:
267
267
  score = utils.to_int(math.log10(deviation + 1) * 33) # desmos그래프상 33이 제일 적당한듯(최대100점에 가깝게)
268
268
 
269
- analyser_logger.debug(f"최근주가 : {recent_price} red가격 : {red_price} 괴리율 : {utils.to_int(deviation)} score : {score}")
269
+ eval_logger.debug(f"최근주가 : {recent_price} red가격 : {red_price} 괴리율 : {utils.to_int(deviation)} score : {score}")
270
270
 
271
271
  return score
272
272
 
273
273
  def _generate_data(self, refresh: bool) -> RedData:
274
274
  d1, 지배주주당기순이익 = Tools.calc당기순이익(self.c103, refresh)
275
- analyser_logger.debug(f"{self} 지배주주당기순이익: {지배주주당기순이익}")
275
+ eval_logger.debug(f"{self} 지배주주당기순이익: {지배주주당기순이익}")
276
276
  d2, 유동자산 = Tools.calc유동자산(self.c103, refresh)
277
277
  d3, 유동부채 = Tools.calc유동부채(self.c103, refresh)
278
278
  d4, 부채평가 = self._calc비유동부채(refresh)
@@ -331,7 +331,7 @@ class Red:
331
331
  :return:
332
332
  """
333
333
  redis_name = f"{self.code}_red"
334
- analyser_logger.info(f"{self} RedData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
334
+ eval_logger.info(f"{self} RedData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
335
335
  expire_time = 3600 * 12
336
336
  if verbose:
337
337
  print(f"{self} redisname: '{redis_name}' / expect_earn: {Red.expect_earn} / refresh : {refresh} / expire_time : {expire_time/3600}h")
@@ -355,11 +355,11 @@ class Red:
355
355
  # expect_earn 및 refresh 설정
356
356
  if expect_earn is None:
357
357
  expect_earn = cls.expect_earn
358
- analyser_logger.info(f"기대수익률을 {expect_earn}으로 설정합니다.")
358
+ eval_logger.info(f"기대수익률을 {expect_earn}으로 설정합니다.")
359
359
  previous_expect_earn = float(AnalyserSettingsManager().get_value('RED_RANKING_EXPECT_EARN'))
360
- analyser_logger.debug(f"previous red ranking expect earn : {previous_expect_earn}")
360
+ eval_logger.debug(f"previous red ranking expect earn : {previous_expect_earn}")
361
361
  if previous_expect_earn != expect_earn:
362
- analyser_logger.warning(f"expect earn : {expect_earn} / RED_RANKING_EXPECT_EARN : {previous_expect_earn} 두 값이 달라 refresh = True")
362
+ eval_logger.warning(f"expect earn : {expect_earn} / RED_RANKING_EXPECT_EARN : {previous_expect_earn} 두 값이 달라 refresh = True")
363
363
  refresh = True
364
364
 
365
365
  redis_name = 'red_ranking'
@@ -422,7 +422,7 @@ class MilData:
422
422
  class Mil:
423
423
  def __init__(self, code: str):
424
424
  assert utils.is_6digit(code), f'Invalid value : {code}'
425
- analyser_logger.debug(f"Mil : 종목코드 ({code})")
425
+ eval_logger.debug(f"Mil : 종목코드 ({code})")
426
426
 
427
427
  self.c101 = myredis.C101(code)
428
428
  self.c103 = myredis.C103(code, 'c103현금흐름표q')
@@ -442,7 +442,7 @@ class Mil:
442
442
  @code.setter
443
443
  def code(self, code: str):
444
444
  assert utils.is_6digit(code), f'Invalid value : {code}'
445
- analyser_logger.debug(f"Mil : 종목코드 변경({self.code} -> {code})")
445
+ eval_logger.debug(f"Mil : 종목코드 변경({self.code} -> {code})")
446
446
 
447
447
  self.c101.code = code
448
448
  self.c103.code = code
@@ -459,7 +459,7 @@ class Mil:
459
459
  """
460
460
  c101r = self.c101.get_recent(refresh)
461
461
  시가총액 = int(utils.to_int(c101r.get('시가총액', math.nan)) / 100000000)
462
- analyser_logger.debug(f"시가총액: {시가총액}억원")
462
+ eval_logger.debug(f"시가총액: {시가총액}억원")
463
463
  return 시가총액
464
464
 
465
465
  def _calc주주수익률(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float]:
@@ -469,7 +469,7 @@ class Mil:
469
469
  주주수익률 = round((재무활동현금흐름 / 시가총액_억 * -100), 2)
470
470
  except ZeroDivisionError:
471
471
  주주수익률 = math.nan
472
- analyser_logger.warning(f'{self} 주주수익률: {주주수익률} 재무활동현금흐름: {재무활동현금흐름}')
472
+ eval_logger.warning(f'{self} 주주수익률: {주주수익률} 재무활동현금흐름: {재무활동현금흐름}')
473
473
  return d, 주주수익률, 재무활동현금흐름
474
474
 
475
475
  def _calc이익지표(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float, float]:
@@ -480,7 +480,7 @@ class Mil:
480
480
  이익지표 = round(((지배주주당기순이익 - 영업활동현금흐름) / 시가총액_억) * 100, 2)
481
481
  except ZeroDivisionError:
482
482
  이익지표 = math.nan
483
- analyser_logger.warning(f'{self} 이익지표: {이익지표} 영업활동현금흐름: {영업활동현금흐름} 지배주주당기순이익: {지배주주당기순이익}')
483
+ eval_logger.warning(f'{self} 이익지표: {이익지표} 영업활동현금흐름: {영업활동현금흐름} 지배주주당기순이익: {지배주주당기순이익}')
484
484
  try:
485
485
  date, *_ = Tools.date_set(d1, d2)
486
486
  except ValueError:
@@ -521,15 +521,15 @@ class Mil:
521
521
  self.c103.page = 'c103재무상태표y'
522
522
  _, capex = self.c103.find('*CAPEX', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
523
523
 
524
- analyser_logger.debug(f'영업활동현금흐름 {영업활동현금흐름_dict}')
525
- analyser_logger.debug(f'CAPEX {capex}')
524
+ eval_logger.debug(f'영업활동현금흐름 {영업활동현금흐름_dict}')
525
+ eval_logger.debug(f'CAPEX {capex}')
526
526
 
527
527
  if len(영업활동현금흐름_dict) == 0:
528
528
  return {}
529
529
 
530
530
  if len(capex) == 0:
531
531
  # CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.
532
- analyser_logger.warning(f"{self} - CAPEX가 없는 업종으로 영업현금흐름을 그대로 사용합니다..")
532
+ eval_logger.warning(f"{self} - CAPEX가 없는 업종으로 영업현금흐름을 그대로 사용합니다..")
533
533
  return 영업활동현금흐름_dict
534
534
 
535
535
  # 영업 활동으로 인한 현금 흐름에서 CAPEX 를 각 연도별로 빼주어 fcf 를 구하고 리턴값으로 fcf 딕셔너리를 반환한다.
@@ -543,7 +543,7 @@ class Mil:
543
543
  if 영업활동현금흐름date == CAPEXdate:
544
544
  fcf_dict[영업활동현금흐름date] = round(영업활동현금흐름value - CAPEXvalue, 2)
545
545
 
546
- analyser_logger.debug(f'fcf_dict {fcf_dict}')
546
+ eval_logger.debug(f'fcf_dict {fcf_dict}')
547
547
  # 연도순으로 정렬해서 딕셔너리로 반환한다.
548
548
  return dict(sorted(fcf_dict.items(), reverse=False))
549
549
 
@@ -556,7 +556,7 @@ class Mil:
556
556
  https://www.investopedia.com/terms/p/pricetofreecashflow.asp
557
557
  """
558
558
  if math.isnan(시가총액_억):
559
- analyser_logger.warning(f"{self} - 시가총액이 nan으로 pFCF를 계산할수 없습니다.")
559
+ eval_logger.warning(f"{self} - 시가총액이 nan으로 pFCF를 계산할수 없습니다.")
560
560
  return {}
561
561
 
562
562
  # pfcf 계산
@@ -569,7 +569,7 @@ class Mil:
569
569
 
570
570
  pfcf_dict = mymongo.C1034.del_unnamed_key(pfcf_dict)
571
571
 
572
- analyser_logger.debug(f'pfcf_dict : {pfcf_dict}')
572
+ eval_logger.debug(f'pfcf_dict : {pfcf_dict}')
573
573
  return pfcf_dict
574
574
 
575
575
  def _calc가치지표(self, 시가총액_억: float, refresh: bool) -> tuple:
@@ -585,15 +585,15 @@ class Mil:
585
585
  return [0,]
586
586
 
587
587
  def _generate_data(self, refresh: bool) -> MilData:
588
- analyser_logger.info(f"In generate_data..refresh : {refresh}")
588
+ eval_logger.info(f"In generate_data..refresh : {refresh}")
589
589
  시가총액_억 = self.get_marketcap억(refresh)
590
- analyser_logger.info(f"{self} 시가총액(억) : {시가총액_억}")
590
+ eval_logger.info(f"{self} 시가총액(억) : {시가총액_억}")
591
591
 
592
592
  d1, 주주수익률, 재무활동현금흐름 = self._calc주주수익률(시가총액_억, refresh)
593
- analyser_logger.info(f"{self} 주주수익률 : {주주수익률}, {d1}")
593
+ eval_logger.info(f"{self} 주주수익률 : {주주수익률}, {d1}")
594
594
 
595
595
  d2, 이익지표, 영업활동현금흐름, 지배주주당기순이익 = self._calc이익지표(시가총액_억, refresh)
596
- analyser_logger.info(f"{self} 이익지표 : {이익지표}, {d2}")
596
+ eval_logger.info(f"{self} 이익지표 : {이익지표}, {d2}")
597
597
 
598
598
  d3, roic_r, roic_dict, roe_r, roe106, roa_r = self._calc투자수익률(refresh)
599
599
  d4, fcf_dict, pfcf_dict, pcr_dict = self._calc가치지표(시가총액_억, refresh)
@@ -640,7 +640,7 @@ class Mil:
640
640
  :return:
641
641
  """
642
642
  redis_name = f"{self.code}_mil"
643
- analyser_logger.info(f"{self} MilData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
643
+ eval_logger.info(f"{self} MilData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
644
644
  if verbose:
645
645
  print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
646
646
 
@@ -677,7 +677,7 @@ class BlueData:
677
677
  class Blue:
678
678
  def __init__(self, code: str):
679
679
  assert utils.is_6digit(code), f'Invalid value : {code}'
680
- analyser_logger.debug(f"Blue : 종목코드 ({code})")
680
+ eval_logger.debug(f"Blue : 종목코드 ({code})")
681
681
 
682
682
  self.c101 = myredis.C101(code)
683
683
  self.c103 = myredis.C103(code, 'c103재무상태표q')
@@ -696,7 +696,7 @@ class Blue:
696
696
  @code.setter
697
697
  def code(self, code: str):
698
698
  assert utils.is_6digit(code), f'Invalid value : {code}'
699
- analyser_logger.debug(f"Blue : 종목코드 변경({self.code} -> {code})")
699
+ eval_logger.debug(f"Blue : 종목코드 변경({self.code} -> {code})")
700
700
 
701
701
  self.c101.code = code
702
702
  self.c103.code = code
@@ -711,11 +711,11 @@ class Blue:
711
711
  c104q에서 최근유동비율 찾아보고 유효하지 않거나 \n
712
712
  100이하인 경우에는수동으로 계산해서 다시 한번 평가해 본다.\n
713
713
  """
714
- analyser_logger.info(f'In the calc유동비율... refresh : {refresh}')
714
+ eval_logger.info(f'In the calc유동비율... refresh : {refresh}')
715
715
  self.c104.page = 'c104q'
716
716
 
717
717
  유동비율date, 유동비율value = self.c104.latest_value('유동비율', pop_count=pop_count)
718
- analyser_logger.info(f'{self} 유동비율 : {유동비율value}/({유동비율date})')
718
+ eval_logger.info(f'{self} 유동비율 : {유동비율value}/({유동비율date})')
719
719
 
720
720
  if math.isnan(유동비율value) or 유동비율value < 100:
721
721
  유동자산date, 유동자산value = Tools.calc유동자산(self.c103, refresh)
@@ -723,23 +723,23 @@ class Blue:
723
723
 
724
724
  self.c103.page = 'c103현금흐름표q'
725
725
  추정영업현금흐름date, 추정영업현금흐름value = self.c103.sum_recent_4q('영업활동으로인한현금흐름', refresh)
726
- analyser_logger.debug(f'{self} 계산전 유동비율 : {유동비율value} / ({유동비율date})')
726
+ eval_logger.debug(f'{self} 계산전 유동비율 : {유동비율value} / ({유동비율date})')
727
727
 
728
728
  계산된유동비율 = 0
729
729
  try:
730
730
  계산된유동비율 = round(((유동자산value + 추정영업현금흐름value) / 유동부채value) * 100, 2)
731
731
  except ZeroDivisionError:
732
- analyser_logger.info(f'유동자산: {유동자산value} + 추정영업현금흐름: {추정영업현금흐름value} / 유동부채: {유동부채value}')
732
+ eval_logger.info(f'유동자산: {유동자산value} + 추정영업현금흐름: {추정영업현금흐름value} / 유동부채: {유동부채value}')
733
733
  계산된유동비율 = float('inf')
734
734
  finally:
735
- analyser_logger.debug(f'{self} 계산된 유동비율 : {계산된유동비율}')
735
+ eval_logger.debug(f'{self} 계산된 유동비율 : {계산된유동비율}')
736
736
 
737
737
  try:
738
738
  date, *_ = Tools.date_set(유동자산date, 유동부채date, 추정영업현금흐름date)
739
739
  except ValueError:
740
740
  # 날짜 데이터가 없는경우
741
741
  date = ''
742
- analyser_logger.warning(f'{self} 유동비율 이상(100 이하 또는 nan) : {유동비율value} -> 재계산 : {계산된유동비율}')
742
+ eval_logger.warning(f'{self} 유동비율 이상(100 이하 또는 nan) : {유동비율value} -> 재계산 : {계산된유동비율}')
743
743
  return date, 계산된유동비율
744
744
  else:
745
745
  return 유동비율date, 유동비율value
@@ -749,7 +749,7 @@ class Blue:
749
749
 
750
750
  def _generate_data(self, refresh: bool) -> BlueData:
751
751
  d1, 유동비율 = self._calc유동비율(pop_count=3, refresh=refresh)
752
- analyser_logger.info(f'유동비율 {유동비율} / [{d1}]')
752
+ eval_logger.info(f'유동비율 {유동비율} / [{d1}]')
753
753
 
754
754
  재고자산회전율_c106 = myredis.C106.make_like_c106(self.code, 'c104q', '재고자산회전율', refresh)
755
755
 
@@ -766,16 +766,16 @@ class Blue:
766
766
  d9, 순부채비율_r = self.c104.latest_value_pop2('순부채비율', refresh)
767
767
 
768
768
  if len(이자보상배율_dict) == 0:
769
- analyser_logger.warning(f'empty dict - 이자보상배율 : {이자보상배율_r} / {이자보상배율_dict}')
769
+ eval_logger.warning(f'empty dict - 이자보상배율 : {이자보상배율_r} / {이자보상배율_dict}')
770
770
 
771
771
  if len(순운전자본회전율_dict) == 0:
772
- analyser_logger.warning(f'empty dict - 순운전자본회전율 : {순운전자본회전율_r} / {순운전자본회전율_dict}')
772
+ eval_logger.warning(f'empty dict - 순운전자본회전율 : {순운전자본회전율_r} / {순운전자본회전율_dict}')
773
773
 
774
774
  if len(재고자산회전율_dict) == 0:
775
- analyser_logger.warning(f'empty dict - 재고자산회전율 : {재고자산회전율_r} / {재고자산회전율_dict}')
775
+ eval_logger.warning(f'empty dict - 재고자산회전율 : {재고자산회전율_r} / {재고자산회전율_dict}')
776
776
 
777
777
  if len(순부채비율_dict) == 0:
778
- analyser_logger.warning(f'empty dict - 순부채비율 : {순부채비율_r} / {순부채비율_dict}')
778
+ eval_logger.warning(f'empty dict - 순부채비율 : {순부채비율_r} / {순부채비율_dict}')
779
779
 
780
780
  score = self._score()
781
781
 
@@ -813,7 +813,7 @@ class Blue:
813
813
  :return:
814
814
  """
815
815
  redis_name = f"{self.code}_blue"
816
- analyser_logger.info(f"{self} BlueData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
816
+ eval_logger.info(f"{self} BlueData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
817
817
  if verbose:
818
818
  print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
819
819
 
@@ -841,7 +841,7 @@ class GrowthData:
841
841
  class Growth:
842
842
  def __init__(self, code: str):
843
843
  assert utils.is_6digit(code), f'Invalid value : {code}'
844
- analyser_logger.debug(f"Growth : 종목코드 ({code})")
844
+ eval_logger.debug(f"Growth : 종목코드 ({code})")
845
845
 
846
846
  self.c101 = myredis.C101(code)
847
847
  self.c104 = myredis.C104(code, 'c104q')
@@ -860,7 +860,7 @@ class Growth:
860
860
  @code.setter
861
861
  def code(self, code: str):
862
862
  assert utils.is_6digit(code), f'Invalid value : {code}'
863
- analyser_logger.debug(f"Growth : 종목코드 변경({self.code} -> {code})")
863
+ eval_logger.debug(f"Growth : 종목코드 변경({self.code} -> {code})")
864
864
 
865
865
  self.c101.code = code
866
866
  self.c104.code = code
@@ -879,7 +879,7 @@ class Growth:
879
879
  self.c104.page = 'c104q'
880
880
  d2, 매출액증가율_r = self.c104.latest_value_pop2('매출액증가율')
881
881
 
882
- analyser_logger.info(f'매출액증가율 : {매출액증가율_r} {매출액증가율_dict}')
882
+ eval_logger.info(f'매출액증가율 : {매출액증가율_r} {매출액증가율_dict}')
883
883
 
884
884
  # c106 에서 타 기업과 영업이익률 비교
885
885
  self.c106.page = 'c106y'
@@ -913,7 +913,7 @@ class Growth:
913
913
  :return:
914
914
  """
915
915
  redis_name = f"{self.code}_growth"
916
- analyser_logger.info(f"{self} GrowthData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
916
+ eval_logger.info(f"{self} GrowthData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
917
917
  if verbose:
918
918
  print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
919
919
 
analyser_hj3415/tsa.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Time Series Analysis
3
3
  """
4
+ import numpy as np
4
5
  import yfinance as yf
5
6
  from datetime import datetime, timedelta
6
7
  import pandas as pd
@@ -14,11 +15,20 @@ import matplotlib.pyplot as plt # Matplotlib 수동 임포트
14
15
  from db_hj3415 import myredis
15
16
  from collections import OrderedDict
16
17
  from analyser_hj3415 import eval
18
+ from sklearn.preprocessing import MinMaxScaler
19
+ from tensorflow.keras.models import Sequential
20
+ from tensorflow.keras.layers import LSTM, Dense, Dropout
21
+ from tensorflow.keras.callbacks import EarlyStopping
22
+ from tensorflow.keras import Input
23
+ from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
24
+ from dataclasses import dataclass
25
+ import itertools
17
26
 
18
27
  import logging
19
- analyser_logger = helpers.setup_logger('analyser_logger', logging.WARNING)
20
28
 
21
- expire_time = 3600 * 12
29
+ tsa_logger = helpers.setup_logger('tsa_logger', logging.WARNING)
30
+
31
+ expire_time = 3600 * 24
22
32
 
23
33
  class MyProphet:
24
34
  def __init__(self, code: str):
@@ -39,7 +49,7 @@ class MyProphet:
39
49
  @code.setter
40
50
  def code(self, code: str):
41
51
  assert utils.is_6digit(code), f'Invalid value : {code}'
42
- analyser_logger.info(f'change code : {self.code} -> {code}')
52
+ tsa_logger.info(f'change code : {self.code} -> {code}')
43
53
  self.model = Prophet()
44
54
  self._code = code
45
55
  self.name = myredis.Corps(code, 'c101').get_name()
@@ -110,15 +120,24 @@ class MyProphet:
110
120
  """
111
121
  df = self.df_forecast
112
122
  last_real_date = self.df_real.iloc[-1]['ds']
113
- analyser_logger.info(last_real_date)
123
+ tsa_logger.info(last_real_date)
114
124
  yhat_dict = df[df['ds']==last_real_date].iloc[0][['ds', 'yhat_lower', 'yhat_upper', 'yhat']].to_dict()
115
- analyser_logger.info(yhat_dict)
125
+ tsa_logger.info(yhat_dict)
116
126
  return yhat_dict
117
127
 
118
- def export_to(self, to="str") -> Optional[str]:
128
+ def visualization(self):
129
+ # 예측 결과 출력
130
+ print(self.df_forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
131
+ # 예측 결과 시각화 (Matplotlib 사용)
132
+ fig = self.model.plot(self.df_forecast)
133
+ # 추세 및 계절성 시각화
134
+ fig2 = self.model.plot_components(self.df_forecast)
135
+ plt.show() # 시각화 창 띄우기
136
+
137
+ def export(self, to="str") -> Optional[str]:
119
138
  """
120
139
  prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
121
- :param to: str, png, htmlfile, show
140
+ :param to: str, png, htmlfile
122
141
  :return:
123
142
  """
124
143
  # Plotly를 사용한 시각화
@@ -153,19 +172,12 @@ class MyProphet:
153
172
  return graph_html
154
173
  elif to == 'png':
155
174
  # 그래프를 PNG 파일로 저장
156
- fig.write_image("plotly_graph.png")
175
+ fig.write_image(f"myprophet_{self.code}.png")
176
+ return None
157
177
  elif to == 'htmlfile':
158
178
  # 그래프를 HTML로 저장
159
- plot(fig, filename='graph_plotly.html', auto_open=False)
179
+ plot(fig, filename=f'myprophet_{self.code}.html', auto_open=False)
160
180
  return None
161
- elif to == 'show':
162
- # 예측 결과 출력
163
- print(self.df_forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
164
- # 예측 결과 시각화 (Matplotlib 사용)
165
- fig = self.model.plot(self.df_forecast)
166
- # 추세 및 계절성 시각화
167
- fig2 = self.model.plot_components(self.df_forecast)
168
- plt.show() # 시각화 창 띄우기
169
181
  else:
170
182
  Exception("to 인자가 맞지 않습니다.")
171
183
 
@@ -191,7 +203,7 @@ class MyProphet:
191
203
  recent_price = last_real_data['y']
192
204
  recent_date = datetime.strftime(last_real_data['ds'], '%Y-%m-%d')
193
205
  yhat_dict = p.get_yhat()
194
- analyser_logger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
206
+ tsa_logger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
195
207
  yhat_lower = int(yhat_dict['yhat_lower'])
196
208
  if recent_price < yhat_lower:
197
209
  deviation = int(eval.Tools.cal_deviation(recent_price, yhat_lower))
@@ -203,5 +215,401 @@ class MyProphet:
203
215
 
204
216
  return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
205
217
 
218
+ @dataclass
219
+ class LSTMData:
220
+ code: str
221
+
222
+ data_2d: np.ndarray
223
+ train_size: int
224
+ train_data_2d: np.ndarray
225
+ test_data_2d: np.ndarray
226
+
227
+ X_train_3d: np.ndarray
228
+ X_test_3d: np.ndarray
229
+ y_train_1d: np.ndarray
230
+ y_test_1d: np.ndarray
231
+
232
+ @dataclass
233
+ class LSTMGrade:
234
+ """
235
+ 딥러닝 모델의 학습 결과를 평가하기 위해 사용하는 데이터 클래스
236
+ """
237
+ code: str
238
+
239
+ mean_train_prediction_2d: np.ndarray
240
+ mean_test_predictions_2d: np.ndarray
241
+
242
+ train_mse: float
243
+ train_mae: float
244
+ train_r2: float
245
+ test_mse: float
246
+ test_mae: float
247
+ test_r2: float
248
+
249
+ class MyLSTM:
250
+ """
251
+ LSTM(Long Short-Term Memory)
252
+ """
253
+ # 미래 몇일을 예측할 것인가?
254
+ future_days = 30
255
+
256
+ def __init__(self, code: str):
257
+ assert utils.is_6digit(code), f'Invalid value : {code}'
258
+ self._code = code
259
+ self.name = myredis.Corps(code, 'c101').get_name()
260
+ self.scaler = MinMaxScaler(feature_range=(0, 1))
261
+ self.raw_data = self._get_raw_data()
262
+ self.lstm_data = self._preprocessing_for_lstm()
263
+
264
+ @property
265
+ def code(self) -> str:
266
+ return self._code
267
+
268
+ @code.setter
269
+ def code(self, code: str):
270
+ assert utils.is_6digit(code), f'Invalid value : {code}'
271
+ tsa_logger.info(f'change code : {self.code} -> {code}')
272
+
273
+ self._code = code
274
+ self.name = myredis.Corps(code, 'c101').get_name()
275
+ self.scaler = MinMaxScaler(feature_range=(0, 1))
276
+ self.raw_data = self._get_raw_data()
277
+ self.lstm_data = self._preprocessing_for_lstm()
278
+
279
+ def _get_raw_data(self) -> pd.DataFrame:
280
+ """
281
+ 야후에서 해당 종목의 4년간 주가 raw data를 받아온다.
282
+ :return:
283
+ """
284
+ # 오늘 날짜 가져오기
285
+ today = datetime.today()
286
+
287
+ # 4년 전 날짜 계산 (4년 = 365일 * 4)
288
+ four_years_ago = today - timedelta(days=365 * 4)
289
+ tsa_logger.info(f'start: {four_years_ago.strftime('%Y-%m-%d')}, end: {today.strftime('%Y-%m-%d')}')
290
+
291
+ return yf.download(
292
+ self.code + '.KS',
293
+ start=four_years_ago.strftime('%Y-%m-%d'),
294
+ end=today.strftime('%Y-%m-%d')
295
+ )
296
+
297
+ def _preprocessing_for_lstm(self) -> LSTMData:
298
+ """
299
+ lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)
300
+ :return:
301
+ """
302
+ # 필요한 열만 선택 (종가만 사용) - 2차웜 배열로 변환
303
+ data_2d = self.raw_data['Close'].values.reshape(-1, 1)
304
+ tsa_logger.debug(data_2d)
305
+
306
+ # 데이터 정규화 (0과 1 사이로 스케일링)
307
+ scaled_data_2d = self.scaler.fit_transform(data_2d)
308
+
309
+ # 학습 데이터 생성
310
+ # 주가 데이터를 80%는 학습용, 20%는 테스트용으로 분리하는 코드
311
+ train_size = int(len(scaled_data_2d) * 0.8)
312
+ train_data_2d = scaled_data_2d[:train_size]
313
+ test_data_2d = scaled_data_2d[train_size:]
314
+ tsa_logger.info(f'총 {len(data_2d)}개 데이터, train size : {train_size}')
315
+
316
+ # 학습 데이터에 대한 입력(X)과 정답(y)를 생성
317
+ def create_dataset(data, time_step=60):
318
+ X, y = [], []
319
+ for i in range(len(data) - time_step):
320
+ X.append(data[i:i + time_step, 0])
321
+ y.append(data[i + time_step, 0])
322
+ return np.array(X), np.array(y)
323
+
324
+ X_train, y_train_1d = create_dataset(train_data_2d)
325
+ X_test, y_test_1d = create_dataset(test_data_2d)
326
+
327
+ # LSTM 모델 입력을 위해 데이터를 3차원으로 변환
328
+ X_train_3d = X_train.reshape(X_train.shape[0], X_train.shape[1], 1)
329
+ X_test_3d = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
330
+
331
+ tsa_logger.debug(f'n_dim - X_train_3d : {X_train_3d.ndim}, X_test_3d : {X_test_3d.ndim}, y_train : {y_train_1d.ndim}, y_test : {y_test_1d.ndim}')
332
+ tsa_logger.debug(f'len - X_train_3d : {len(X_train_3d)}, X_test_3d : {len(X_test_3d)}, y_train : {len(y_train_1d)}, y_test : {len(y_test_1d)}')
333
+
334
+ return LSTMData(
335
+ code=self.code,
336
+ data_2d=data_2d,
337
+ train_size=train_size,
338
+ train_data_2d=train_data_2d,
339
+ test_data_2d=test_data_2d,
340
+ X_train_3d=X_train_3d,
341
+ X_test_3d=X_test_3d,
342
+ y_train_1d=y_train_1d,
343
+ y_test_1d=y_test_1d,
344
+ )
345
+
346
+ def _model_training(self) -> Sequential:
347
+ # LSTM 모델 생성 - 유닛과 드롭아웃의 수는 테스트로 최적화 됨.
348
+ model = Sequential()
349
+ # Input(shape=(50, 1))는 50개의 타임스텝을 가지는 입력 데이터를 처리하며, 각 타임스텝에 1개의 특성이 있다는 것을 의미
350
+ model.add(Input(shape=(self.lstm_data.X_train_3d.shape[1], 1))) # 입력 레이어에 명시적으로 Input을 사용
351
+ model.add(LSTM(units=150, return_sequences=True))
352
+ model.add(Dropout(0.2))
353
+ model.add(LSTM(units=75, return_sequences=False))
354
+ model.add(Dropout(0.2))
355
+ model.add(Dense(units=25))
356
+ model.add(Dropout(0.3))
357
+ model.add(Dense(units=1))
358
+
359
+ # 모델 요약 출력
360
+ # model.summary()
361
+
362
+ # 모델 컴파일 및 학습
363
+ model.compile(optimizer='adam', loss='mean_squared_error')
364
+
365
+ # 조기 종료 설정
366
+ early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
367
+
368
+ # 모델 학습 - 과적합 방지위한 조기종료 세팅
369
+ model.fit(self.lstm_data.X_train_3d, self.lstm_data.y_train_1d,
370
+ epochs=75, batch_size=32, validation_data=(self.lstm_data.X_test_3d, self.lstm_data.y_test_1d),
371
+ callbacks=[early_stopping])
372
+ return model
373
+
374
+ def ensemble_training(self, num) -> tuple:
375
+ """
376
+ 딥러닝을 num 회 반복하고 평균을 사용하는 함수
377
+ :param num: 앙상블 모델 수
378
+ :return:
379
+ """
380
+ def prediction(model_in: Sequential, data: np.ndarray) -> np.ndarray:
381
+ """
382
+ 훈련될 모델을 통해 예측을 시행하여 정규화를 복원하고 결과 반환한다.
383
+ :param model_in:
384
+ :param data:
385
+ :return:
386
+ """
387
+ predictions_2d = model_in.predict(data)
388
+ predictions_scaled_2d = self.scaler.inverse_transform(predictions_2d) # 스케일링 복원
389
+ tsa_logger.info(f'predictions_scaled_2d : ndim - {predictions_scaled_2d.ndim} len - {len(predictions_scaled_2d)}') # numpy.ndarray 타입
390
+ tsa_logger.debug(predictions_scaled_2d)
391
+ return predictions_scaled_2d
392
+
393
+ ensemble_train_predictions_2d = []
394
+ ensemble_test_predictions_2d = []
395
+ ensemble_future_predictions_2d = []
396
+
397
+ for i in range(num):
398
+ print(f"Training model {i + 1}/{num}...")
399
+ model = self._model_training()
400
+
401
+ # 훈련 데이터 예측
402
+ train_predictions_scaled_2d = prediction(model, self.lstm_data.X_train_3d)
403
+ ensemble_train_predictions_2d.append(train_predictions_scaled_2d)
404
+
405
+ # 테스트 데이터 예측
406
+ test_predictions_scaled_2d = prediction(model, self.lstm_data.X_test_3d)
407
+ ensemble_test_predictions_2d.append(test_predictions_scaled_2d)
408
+
409
+ # 8. 미래 30일 예측
410
+ # 마지막 60일간의 데이터를 기반으로 미래 30일을 예측
411
+
412
+ last_60_days_2d = self.lstm_data.test_data_2d[-60:]
413
+ last_60_days_3d = last_60_days_2d.reshape(1, -1, 1)
414
+
415
+ future_predictions = []
416
+ for _ in range(self.future_days):
417
+ predicted_price_2d = model.predict(last_60_days_3d)
418
+ future_predictions.append(predicted_price_2d[0][0])
419
+
420
+ # 예측값을 다시 입력으로 사용하여 새로운 예측을 만듦
421
+ predicted_price_reshaped = np.reshape(predicted_price_2d, (1, 1, 1)) # 3D 배열로 변환
422
+ last_60_days_3d = np.append(last_60_days_3d[:, 1:, :], predicted_price_reshaped, axis=1)
423
+
424
+ # 예측된 주가를 다시 스케일링 복원
425
+ future_predictions_2d = np.array(future_predictions).reshape(-1, 1)
426
+ future_predictions_scaled_2d = self.scaler.inverse_transform(future_predictions_2d)
427
+ ensemble_future_predictions_2d.append(future_predictions_scaled_2d)
428
+
429
+ return ensemble_train_predictions_2d, ensemble_test_predictions_2d, ensemble_future_predictions_2d
430
+
431
+ def grading(self, ensemble_train_predictions_2d: list, ensemble_test_predictions_2d: list) -> LSTMGrade:
432
+ """
433
+ 딥러닝 결과를 분석하기 위한 함수
434
+ :param ensemble_train_predictions_2d:
435
+ :param ensemble_test_predictions_2d:
436
+ :return:
437
+ """
438
+ # 예측값을 평균내서 최종 예측값 도출
439
+ mean_train_prediction_2d = np.mean(ensemble_train_predictions_2d, axis=0)
440
+ mean_test_predictions_2d = np.mean(ensemble_test_predictions_2d, axis=0)
441
+
442
+ # y값(정답) 정규화 해제
443
+ y_train_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_train_1d.reshape(-1, 1))
444
+ y_test_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_test_1d.reshape(-1, 1))
445
+
446
+ # 평가 지표 계산
447
+ train_mse = mean_squared_error(y_train_scaled_2d, mean_train_prediction_2d)
448
+ train_mae = mean_absolute_error(y_train_scaled_2d, mean_train_prediction_2d)
449
+ train_r2 = r2_score(y_train_scaled_2d, mean_train_prediction_2d)
450
+
451
+ test_mse = mean_squared_error(y_test_scaled_2d, mean_test_predictions_2d)
452
+ test_mae = mean_absolute_error(y_test_scaled_2d, mean_test_predictions_2d)
453
+ test_r2 = r2_score(y_test_scaled_2d, mean_test_predictions_2d)
454
+
455
+ # 평가 결과 출력
456
+ print("Training Data:")
457
+ print(f"Train MSE: {train_mse}, Train MAE: {train_mae}, Train R²: {train_r2}")
458
+ print("\nTesting Data:")
459
+ print(f"Test MSE: {test_mse}, Test MAE: {test_mae}, Test R²: {test_r2}")
460
+ # mse, mae는 작을수록 좋으며 R^2은 0-1 사이값 1에 가까울수록 정확함
461
+ # 과적합에 대한 평가는 train 과 test를 비교하여 test가 너무 않좋으면 과적합 의심.
462
+
463
+ return LSTMGrade(
464
+ code=self.code,
465
+ mean_train_prediction_2d=mean_train_prediction_2d,
466
+ mean_test_predictions_2d=mean_test_predictions_2d,
467
+ train_mse=train_mse,
468
+ train_mae=train_mae,
469
+ train_r2=train_r2,
470
+ test_mse=test_mse,
471
+ test_mae=test_mae,
472
+ test_r2=test_r2,
473
+ )
474
+
475
+ def get_final_predictions(self, refresh, num=5) -> tuple:
476
+ """
477
+ 미래 예측치를 레디스 캐시를 이용하여 반환함
478
+ :param refresh:
479
+ :param num: 앙상블 반복횟수
480
+ :return:
481
+ """
482
+ print("**** Start get_final_predictions... ****")
483
+ redis_name = f'{self.code}_mylstm_predictions'
484
+
485
+ print(
486
+ f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time / 3600}h")
487
+
488
+ def fetch_final_predictions(num_in) -> tuple:
489
+ """
490
+ 앙상블법으로 딥러닝을 모델을 반복해서 평균을 내서 미래를 예측한다. 평가는 래시스 캐시로 반환하기 어려워 일단 디버그 용도로만 사용하기로
491
+ :param num_in:
492
+ :return:
493
+ """
494
+ # 앙상블 테스트와 채점
495
+ _, _, ensemble_future_predictions_2d = self.ensemble_training(
496
+ num=num_in)
497
+
498
+ """if grading:
499
+ lstm_grade = self.grading(ensemble_train_predictions_2d, ensemble_test_predictions_2d)
500
+ else:
501
+ lstm_grade = None"""
502
+
503
+ # 시각화를 위한 준비 - 날짜 생성 (미래 예측 날짜), 미래예측값 평균
504
+ last_date = self.raw_data.index[-1]
505
+ future_dates = pd.date_range(last_date, periods=self.future_days + 1).tolist()[1:]
506
+
507
+ # Timestamp 객체를 문자열로 변환
508
+ future_dates_str= [date.strftime('%Y-%m-%d') for date in future_dates]
509
+
510
+ final_future_predictions = np.mean(ensemble_future_predictions_2d, axis=0)
511
+ tsa_logger.info(f'num - future dates : {len(future_dates_str)} future data : {len(final_future_predictions)}')
512
+
513
+ assert len(future_dates_str) == len(final_future_predictions), "future_dates 와 final_future_predictions 개수가 일치하지 않습니다."
514
+
515
+ return future_dates_str, final_future_predictions.tolist()
516
+
517
+ future_dates_str, final_future_predictions = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time)
518
+
519
+ # 문자열을 날짜 형식으로 변환
520
+ future_dates = [datetime.strptime(date, '%Y-%m-%d') for date in future_dates_str]
521
+
522
+ # 리스트를 다시 NumPy 배열로 변환
523
+ final_future_predictions = np.array(final_future_predictions)
524
+
525
+ return future_dates, final_future_predictions
526
+
527
+ def export(self, refresh=False, to="str") -> Optional[str]:
528
+ """
529
+ prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
530
+ :param refresh:
531
+ :param to: str, htmlfile, png
532
+ :return:
533
+ """
534
+ future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
535
+ final_future_predictions = final_future_predictions.reshape(-1) # 차원을 하나 줄인다.
536
+
537
+ # Plotly를 사용한 시각화
538
+ fig = go.Figure()
539
+
540
+ # 실제 데이터
541
+ fig.add_trace(go.Scatter(x=self.raw_data.index[-120:], y=self.raw_data['Close'][-120:], mode='markers', name='실제주가'))
542
+ tsa_logger.debug(f"self.raw_data.index[-120:] - {self.raw_data.index[-120:]}")
543
+ tsa_logger.debug(f"self.raw_data['Close'][-120:] - {self.raw_data['Close'][-120:]}")
544
+ # 예측 데이터
545
+ fig.add_trace(go.Scatter(x=future_dates, y=final_future_predictions, mode='lines+markers', name='예측치(30일)'))
546
+ tsa_logger.debug(f"future_dates - {future_dates}")
547
+ tsa_logger.debug(f"final_future_predictions - {final_future_predictions}")
548
+
549
+ fig.update_layout(
550
+ # title=f'{self.code} {self.name} 주가 예측 그래프(prophet)',
551
+ xaxis_title='일자',
552
+ yaxis_title='주가(원)',
553
+ xaxis = dict(
554
+ tickformat='%Y/%m', # X축을 '연/월' 형식으로 표시
555
+ ),
556
+ yaxis = dict(
557
+ tickformat=".0f", # 소수점 없이 원래 숫자 표시
558
+ )
559
+ )
560
+
561
+ if to == 'str':
562
+ # 그래프 HTML로 변환 (string 형식으로 저장)
563
+ graph_html = plot(fig, output_type='div')
564
+ return graph_html
565
+ elif to == 'png':
566
+ # 그래프를 PNG 파일로 저장
567
+ fig.write_image(f"myLSTM_{self.code}.png")
568
+ return None
569
+ elif to == 'htmlfile':
570
+ # 그래프를 HTML로 저장
571
+ plot(fig, filename=f'myLSTM_{self.code}.html', auto_open=False)
572
+ return None
573
+ else:
574
+ Exception("to 인자가 맞지 않습니다.")
575
+
576
+ def visualization(self, refresh=True):
577
+ future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
578
+
579
+ # 시각화1
580
+ plt.figure(figsize=(10, 6))
581
+
582
+ # 실제 주가
583
+ plt.plot(self.raw_data.index, self.raw_data['Close'], label='Actual Price')
584
+
585
+ # 미래 주가 예측
586
+ plt.plot(future_dates, final_future_predictions, label='Future Predicted Price', linestyle='--')
587
+
588
+ plt.xlabel('Date')
589
+ plt.ylabel('Stock Price')
590
+ plt.legend()
591
+ plt.title('Apple Stock Price Prediction with LSTM')
592
+ plt.show()
593
+
594
+ """# 시각화2
595
+ plt.figure(figsize=(10, 6))
596
+ plt.plot(self.raw_data.index[self.lstm_data.train_size + 60:], self.lstm_data.data_2d[self.lstm_data.train_size + 60:], label='Actual Price')
597
+ plt.plot(self.raw_data.index[self.lstm_data.train_size + 60:], lstm_grade.mean_test_predictions_2d, label='Predicted Price')
598
+ plt.xlabel('Date')
599
+ plt.ylabel('Price')
600
+ plt.legend()
601
+ plt.title('Stock Price Prediction with LSTM Ensemble')
602
+ plt.show()"""
603
+
604
+ def caching_based_on_prophet_ranking(self, refresh: bool, top=20):
605
+ ranking_topn = OrderedDict(itertools.islice(MyProphet.ranking().items(), top))
606
+ tsa_logger.info(ranking_topn)
607
+ print(f"*** LSTM prediction redis cashing top{top} items ***")
608
+ for i, (code, _) in enumerate(ranking_topn.items()):
609
+ print(f"{i+1}. {self.code}/{self.name}")
610
+ self.code = code
611
+ self.get_final_predictions(refresh=refresh, num=5)
612
+
613
+
206
614
 
207
615
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: analyser_hj3415
3
- Version: 2.8.2
3
+ Version: 2.9.0
4
4
  Summary: Stock analyser and database processing programs
5
5
  Author-email: Hyungjin Kim <hj3415@gmail.com>
6
6
  Description-Content-Type: text/markdown
@@ -1,16 +1,15 @@
1
1
  analyser_hj3415/.DS_Store,sha256=qr9-0FPn5CFKe6kEu8_dWCNhzQ0sN7bwQgffKsaJEEo,6148
2
2
  analyser_hj3415/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- analyser_hj3415/cli.py,sha256=SnIcejcjbXR9rMA7zIm-hIXgbIIlFJkZPMUCvo_AgtQ,11725
4
- analyser_hj3415/eval.py,sha256=4F0GIknCogAhv_iTq8auLrmwW20u8kH0HY0fP4SaVa4,39099
5
- analyser_hj3415/tsa.py,sha256=3mZ0hjUNt202MAytnjAYAoG1Cj9Zl6S_Rmz39_0iC98,7875
3
+ analyser_hj3415/cli.py,sha256=EW0-lIrpZHNNeDKksxC4qDBCiHPkYMGMHsZhAg6VsS8,12870
4
+ analyser_hj3415/eval.py,sha256=WWIvB4BebjW9GNGcF8rMd-MLL-lPXUBOH01_FpSq95I,38811
5
+ analyser_hj3415/tsa.py,sha256=fAC4hsqWMkzoHrRr9Ulcq0s6lVfbQE7vTP_ulJmXkcQ,25097
6
6
  analyser_hj3415/workroom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- analyser_hj3415/workroom/lstm.py,sha256=b0ICzIVeCODcH4tAkVyrHVJLXwf21wmkIambKPHaZMQ,4155
8
7
  analyser_hj3415/workroom/mysklearn.py,sha256=wJXKz5MqqTzADdG2mqRMMzc_G9RzwYjj5_j4gyOopxQ,2030
9
8
  analyser_hj3415/workroom/mysklearn2.py,sha256=1lIy6EWEQHkOzDS-av8U0zQH6DuCLKWMI73dnJx5KRs,1495
10
9
  analyser_hj3415/workroom/score.py,sha256=P6nHBJYmyhigGtT4qna4BmNtvt4B93b7SKyzdstJK24,17376
11
10
  analyser_hj3415/workroom/trash.py,sha256=zF-W0piqkGr66UP6-iybo9EXh2gO0RP6R1FnIpsGkl8,12262
12
- analyser_hj3415-2.8.2.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
13
- analyser_hj3415-2.8.2.dist-info/LICENSE,sha256=QVKTp0dTnB5xG8RLgG17LwSWCKNEzYoVVM6KjoCPKc0,1079
14
- analyser_hj3415-2.8.2.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
15
- analyser_hj3415-2.8.2.dist-info/METADATA,sha256=czwwaazkGIYfdqgPoz6AOleeIQgsLuR_99Xd-8OtV0o,6607
16
- analyser_hj3415-2.8.2.dist-info/RECORD,,
11
+ analyser_hj3415-2.9.0.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
12
+ analyser_hj3415-2.9.0.dist-info/LICENSE,sha256=QVKTp0dTnB5xG8RLgG17LwSWCKNEzYoVVM6KjoCPKc0,1079
13
+ analyser_hj3415-2.9.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
14
+ analyser_hj3415-2.9.0.dist-info/METADATA,sha256=i3FwxTCib11QDlFyxVKM_mKBxH8fXmTxP82gjttoiYE,6607
15
+ analyser_hj3415-2.9.0.dist-info/RECORD,,
@@ -1,115 +0,0 @@
1
- import yfinance as yf
2
- import numpy as np
3
- import pandas as pd
4
- from sklearn.preprocessing import MinMaxScaler
5
- from tensorflow.keras.models import Sequential
6
- from tensorflow.keras.layers import LSTM, Dense, Dropout
7
- import matplotlib.pyplot as plt
8
-
9
- # 1. 데이터 다운로드 (애플 주식 데이터를 사용)
10
- #stock_data = yf.download('AAPL', start='2020-01-01', end='2023-01-01')
11
- # 삼성전자 주식 데이터 가져오기 (KOSPI 상장)
12
- #stock_data = yf.download('005930.KS', start='2019-01-01', end='2024-10-11')
13
- # 크래프톤 주식 데이터 가져오기 (KOSPI 상장)
14
- stock_data = yf.download('259960.KS', start='2020-01-01', end='2024-10-11')
15
- # 하이닉스 주식 데이터 가져오기 (KOSPI 상장)
16
- #stock_data = yf.download('000660.KS', start='2019-01-01', end='2024-10-11')
17
-
18
- stock_data = yf.download('004490.KS', start='2020-01-01', end='2024-10-15')
19
-
20
-
21
- # 2. 필요한 열만 선택 (종가만 사용)
22
- data = stock_data['Close'].values.reshape(-1, 1)
23
-
24
- # 3. 데이터 정규화 (0과 1 사이로 스케일링)
25
- scaler = MinMaxScaler(feature_range=(0, 1))
26
- scaled_data = scaler.fit_transform(data)
27
-
28
- # 4. 학습 데이터 생성
29
- # 주가 데이터를 80%는 학습용, 20%는 테스트용으로 분리하는 코드
30
- train_size = int(len(scaled_data) * 0.8)
31
- train_data = scaled_data[:train_size]
32
- test_data = scaled_data[train_size:]
33
-
34
-
35
- # 학습 데이터에 대한 입력(X)과 출력(y)를 생성
36
- def create_dataset(data, time_step=60):
37
- X, y = [], []
38
- for i in range(len(data) - time_step):
39
- X.append(data[i:i + time_step, 0])
40
- y.append(data[i + time_step, 0])
41
- return np.array(X), np.array(y)
42
-
43
-
44
- X_train, y_train = create_dataset(train_data)
45
- X_test, y_test = create_dataset(test_data)
46
-
47
- # LSTM 모델 입력을 위해 데이터를 3차원으로 변환
48
- X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], 1)
49
- X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
50
-
51
- # 5. LSTM 모델 생성
52
- model = Sequential()
53
- model.add(LSTM(units=200, return_sequences=True, input_shape=(X_train.shape[1], 1)))
54
- model.add(Dropout(0.2))
55
- model.add(LSTM(units=100, return_sequences=False))
56
- model.add(Dropout(0.2))
57
- model.add(Dense(units=50))
58
- model.add(Dropout(0.3))
59
- model.add(Dense(units=1))
60
-
61
- # 6. 모델 컴파일 및 학습
62
- model.compile(optimizer='adam', loss='mean_squared_error')
63
- model.fit(X_train, y_train, epochs=20, batch_size=32)
64
-
65
- # 7. 테스트 데이터 예측
66
- predictions = model.predict(X_test)
67
- predictions = scaler.inverse_transform(predictions) # 스케일링 복원
68
-
69
- # 8. 미래 30일 예측
70
- # 마지막 60일간의 데이터를 기반으로 미래 30일을 예측
71
- future_days = 30
72
- last_60_days = test_data[-60:]
73
- last_60_days = last_60_days.reshape(1, -1, 1)
74
-
75
- future_predictions = []
76
- for _ in range(future_days):
77
- predicted_price = model.predict(last_60_days)
78
- future_predictions.append(predicted_price[0][0])
79
-
80
- # 예측값을 다시 입력으로 사용하여 새로운 예측을 만듦
81
- predicted_price_reshaped = np.reshape(predicted_price, (1, 1, 1)) # 3D 배열로 변환
82
- last_60_days = np.append(last_60_days[:, 1:, :], predicted_price_reshaped, axis=1)
83
-
84
- # 예측된 주가를 다시 스케일링 복원
85
- future_predictions = np.array(future_predictions).reshape(-1, 1)
86
- future_predictions = scaler.inverse_transform(future_predictions)
87
-
88
- # 9. 날짜 생성 (미래 예측 날짜)
89
- last_date = stock_data.index[-1]
90
- future_dates = pd.date_range(last_date, periods=future_days + 1).tolist()[1:]
91
-
92
- # 10. 시각화
93
- plt.figure(figsize=(10, 6))
94
-
95
- # 실제 주가
96
- plt.plot(stock_data.index, stock_data['Close'], label='Actual Price')
97
-
98
- # 미래 주가 예측
99
- plt.plot(future_dates, future_predictions, label='Future Predicted Price', linestyle='--')
100
-
101
- plt.xlabel('Date')
102
- plt.ylabel('Stock Price')
103
- plt.legend()
104
- plt.title('Apple Stock Price Prediction with LSTM')
105
- plt.show()
106
-
107
- # 8. 시각화
108
- plt.figure(figsize=(10, 6))
109
- plt.plot(stock_data.index[train_size + 60:], data[train_size + 60:], label='Actual Price')
110
- plt.plot(stock_data.index[train_size + 60:], predictions, label='Predicted Price')
111
- plt.xlabel('Date')
112
- plt.ylabel('Price')
113
- plt.legend()
114
- plt.title('Apple Stock Price Prediction with LSTM')
115
- plt.show()