analyser_hj3415 2.8.3__py2.py3-none-any.whl → 2.9.1__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,14 +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 관련된 명령')
44
+ # prophet 명령어 서브파서
45
+ prophet_parser = type_subparsers.add_parser('prophet', help='MyProphet 타입')
46
+ prophet_subparser = prophet_parser.add_subparsers(dest='command', help='prophet 관련된 명령')
47
47
  # ranking 파서
48
- ranking_parser = tsa_subparser.add_parser('ranking', help='tsa 랭킹 책정 및 레디스 저장')
48
+ ranking_parser = prophet_subparser.add_parser('ranking', help='prophet 랭킹 책정 및 레디스 저장')
49
49
  ranking_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
50
50
  ranking_parser.add_argument('-n', '--noti', action='store_true', help='작업 완료 후 메시지 전송 여부')
51
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
+
52
60
  # red 명령어 서브파서
53
61
  red_parser = type_subparsers.add_parser('red', help='red 타입')
54
62
  red_subparser = red_parser.add_subparsers(dest='command', help='red 관련된 명령')
@@ -203,12 +211,17 @@ def analyser_manager():
203
211
  pprint.pprint(growth.get(args.refresh))
204
212
  if args.noti:
205
213
  noti.telegram_to('manager', f"오늘의 Growth({args.code})를 레디스 캐시에 저장했습니다.(유효 12시간)")
206
- elif args.type == 'tsa':
214
+ elif args.type == 'prophet':
207
215
  if args.command == 'ranking':
208
216
  result = tsa.MyProphet.ranking(refresh=args.refresh)
209
217
  print(result)
210
218
  if args.noti:
211
- noti.telegram_to('manager', "오늘의 tsa ranking을 레디스캐시에 저장했습니다.(유효 12시간)")
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시간)")
212
225
  elif args.type == 'setting':
213
226
  if args.command == 'set':
214
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를 사용한 시각화
@@ -144,7 +163,8 @@ class MyProphet:
144
163
  ),
145
164
  yaxis = dict(
146
165
  tickformat=".0f", # 소수점 없이 원래 숫자 표시
147
- )
166
+ ),
167
+ showlegend=False,
148
168
  )
149
169
 
150
170
  if to == 'str':
@@ -153,19 +173,12 @@ class MyProphet:
153
173
  return graph_html
154
174
  elif to == 'png':
155
175
  # 그래프를 PNG 파일로 저장
156
- fig.write_image("plotly_graph.png")
176
+ fig.write_image(f"myprophet_{self.code}.png")
177
+ return None
157
178
  elif to == 'htmlfile':
158
179
  # 그래프를 HTML로 저장
159
- plot(fig, filename='graph_plotly.html', auto_open=False)
180
+ plot(fig, filename=f'myprophet_{self.code}.html', auto_open=False)
160
181
  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
182
  else:
170
183
  Exception("to 인자가 맞지 않습니다.")
171
184
 
@@ -191,7 +204,7 @@ class MyProphet:
191
204
  recent_price = last_real_data['y']
192
205
  recent_date = datetime.strftime(last_real_data['ds'], '%Y-%m-%d')
193
206
  yhat_dict = p.get_yhat()
194
- analyser_logger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
207
+ tsa_logger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
195
208
  yhat_lower = int(yhat_dict['yhat_lower'])
196
209
  if recent_price < yhat_lower:
197
210
  deviation = int(eval.Tools.cal_deviation(recent_price, yhat_lower))
@@ -203,5 +216,402 @@ class MyProphet:
203
216
 
204
217
  return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
205
218
 
219
+ @dataclass
220
+ class LSTMData:
221
+ code: str
222
+
223
+ data_2d: np.ndarray
224
+ train_size: int
225
+ train_data_2d: np.ndarray
226
+ test_data_2d: np.ndarray
227
+
228
+ X_train_3d: np.ndarray
229
+ X_test_3d: np.ndarray
230
+ y_train_1d: np.ndarray
231
+ y_test_1d: np.ndarray
232
+
233
+ @dataclass
234
+ class LSTMGrade:
235
+ """
236
+ 딥러닝 모델의 학습 결과를 평가하기 위해 사용하는 데이터 클래스
237
+ """
238
+ code: str
239
+
240
+ mean_train_prediction_2d: np.ndarray
241
+ mean_test_predictions_2d: np.ndarray
242
+
243
+ train_mse: float
244
+ train_mae: float
245
+ train_r2: float
246
+ test_mse: float
247
+ test_mae: float
248
+ test_r2: float
249
+
250
+ class MyLSTM:
251
+ """
252
+ LSTM(Long Short-Term Memory)
253
+ """
254
+ # 미래 몇일을 예측할 것인가?
255
+ future_days = 30
256
+
257
+ def __init__(self, code: str):
258
+ assert utils.is_6digit(code), f'Invalid value : {code}'
259
+ self._code = code
260
+ self.name = myredis.Corps(code, 'c101').get_name()
261
+ self.scaler = MinMaxScaler(feature_range=(0, 1))
262
+ self.raw_data = self._get_raw_data()
263
+ self.lstm_data = self._preprocessing_for_lstm()
264
+
265
+ @property
266
+ def code(self) -> str:
267
+ return self._code
268
+
269
+ @code.setter
270
+ def code(self, code: str):
271
+ assert utils.is_6digit(code), f'Invalid value : {code}'
272
+ tsa_logger.info(f'change code : {self.code} -> {code}')
273
+
274
+ self._code = code
275
+ self.name = myredis.Corps(code, 'c101').get_name()
276
+ self.scaler = MinMaxScaler(feature_range=(0, 1))
277
+ self.raw_data = self._get_raw_data()
278
+ self.lstm_data = self._preprocessing_for_lstm()
279
+
280
+ def _get_raw_data(self) -> pd.DataFrame:
281
+ """
282
+ 야후에서 해당 종목의 4년간 주가 raw data를 받아온다.
283
+ :return:
284
+ """
285
+ # 오늘 날짜 가져오기
286
+ today = datetime.today()
287
+
288
+ # 4년 전 날짜 계산 (4년 = 365일 * 4)
289
+ four_years_ago = today - timedelta(days=365 * 4)
290
+ tsa_logger.info(f'start: {four_years_ago.strftime('%Y-%m-%d')}, end: {today.strftime('%Y-%m-%d')}')
291
+
292
+ return yf.download(
293
+ self.code + '.KS',
294
+ start=four_years_ago.strftime('%Y-%m-%d'),
295
+ end=today.strftime('%Y-%m-%d')
296
+ )
297
+
298
+ def _preprocessing_for_lstm(self) -> LSTMData:
299
+ """
300
+ lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)
301
+ :return:
302
+ """
303
+ # 필요한 열만 선택 (종가만 사용) - 2차웜 배열로 변환
304
+ data_2d = self.raw_data['Close'].values.reshape(-1, 1)
305
+ tsa_logger.debug(data_2d)
306
+
307
+ # 데이터 정규화 (0과 1 사이로 스케일링)
308
+ scaled_data_2d = self.scaler.fit_transform(data_2d)
309
+
310
+ # 학습 데이터 생성
311
+ # 주가 데이터를 80%는 학습용, 20%는 테스트용으로 분리하는 코드
312
+ train_size = int(len(scaled_data_2d) * 0.8)
313
+ train_data_2d = scaled_data_2d[:train_size]
314
+ test_data_2d = scaled_data_2d[train_size:]
315
+ tsa_logger.info(f'총 {len(data_2d)}개 데이터, train size : {train_size}')
316
+
317
+ # 학습 데이터에 대한 입력(X)과 정답(y)를 생성
318
+ def create_dataset(data, time_step=60):
319
+ X, y = [], []
320
+ for i in range(len(data) - time_step):
321
+ X.append(data[i:i + time_step, 0])
322
+ y.append(data[i + time_step, 0])
323
+ return np.array(X), np.array(y)
324
+
325
+ X_train, y_train_1d = create_dataset(train_data_2d)
326
+ X_test, y_test_1d = create_dataset(test_data_2d)
327
+
328
+ # LSTM 모델 입력을 위해 데이터를 3차원으로 변환
329
+ X_train_3d = X_train.reshape(X_train.shape[0], X_train.shape[1], 1)
330
+ X_test_3d = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
331
+
332
+ 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}')
333
+ 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)}')
334
+
335
+ return LSTMData(
336
+ code=self.code,
337
+ data_2d=data_2d,
338
+ train_size=train_size,
339
+ train_data_2d=train_data_2d,
340
+ test_data_2d=test_data_2d,
341
+ X_train_3d=X_train_3d,
342
+ X_test_3d=X_test_3d,
343
+ y_train_1d=y_train_1d,
344
+ y_test_1d=y_test_1d,
345
+ )
346
+
347
+ def _model_training(self) -> Sequential:
348
+ # LSTM 모델 생성 - 유닛과 드롭아웃의 수는 테스트로 최적화 됨.
349
+ model = Sequential()
350
+ # Input(shape=(50, 1))는 50개의 타임스텝을 가지는 입력 데이터를 처리하며, 각 타임스텝에 1개의 특성이 있다는 것을 의미
351
+ model.add(Input(shape=(self.lstm_data.X_train_3d.shape[1], 1))) # 입력 레이어에 명시적으로 Input을 사용
352
+ model.add(LSTM(units=150, return_sequences=True))
353
+ model.add(Dropout(0.2))
354
+ model.add(LSTM(units=75, return_sequences=False))
355
+ model.add(Dropout(0.2))
356
+ model.add(Dense(units=25))
357
+ model.add(Dropout(0.3))
358
+ model.add(Dense(units=1))
359
+
360
+ # 모델 요약 출력
361
+ # model.summary()
362
+
363
+ # 모델 컴파일 및 학습
364
+ model.compile(optimizer='adam', loss='mean_squared_error')
365
+
366
+ # 조기 종료 설정
367
+ early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
368
+
369
+ # 모델 학습 - 과적합 방지위한 조기종료 세팅
370
+ model.fit(self.lstm_data.X_train_3d, self.lstm_data.y_train_1d,
371
+ epochs=75, batch_size=32, validation_data=(self.lstm_data.X_test_3d, self.lstm_data.y_test_1d),
372
+ callbacks=[early_stopping])
373
+ return model
374
+
375
+ def ensemble_training(self, num) -> tuple:
376
+ """
377
+ 딥러닝을 num 회 반복하고 평균을 사용하는 함수
378
+ :param num: 앙상블 모델 수
379
+ :return:
380
+ """
381
+ def prediction(model_in: Sequential, data: np.ndarray) -> np.ndarray:
382
+ """
383
+ 훈련될 모델을 통해 예측을 시행하여 정규화를 복원하고 결과 반환한다.
384
+ :param model_in:
385
+ :param data:
386
+ :return:
387
+ """
388
+ predictions_2d = model_in.predict(data)
389
+ predictions_scaled_2d = self.scaler.inverse_transform(predictions_2d) # 스케일링 복원
390
+ tsa_logger.info(f'predictions_scaled_2d : ndim - {predictions_scaled_2d.ndim} len - {len(predictions_scaled_2d)}') # numpy.ndarray 타입
391
+ tsa_logger.debug(predictions_scaled_2d)
392
+ return predictions_scaled_2d
393
+
394
+ ensemble_train_predictions_2d = []
395
+ ensemble_test_predictions_2d = []
396
+ ensemble_future_predictions_2d = []
397
+
398
+ for i in range(num):
399
+ print(f"Training model {i + 1}/{num}...")
400
+ model = self._model_training()
401
+
402
+ # 훈련 데이터 예측
403
+ train_predictions_scaled_2d = prediction(model, self.lstm_data.X_train_3d)
404
+ ensemble_train_predictions_2d.append(train_predictions_scaled_2d)
405
+
406
+ # 테스트 데이터 예측
407
+ test_predictions_scaled_2d = prediction(model, self.lstm_data.X_test_3d)
408
+ ensemble_test_predictions_2d.append(test_predictions_scaled_2d)
409
+
410
+ # 8. 미래 30일 예측
411
+ # 마지막 60일간의 데이터를 기반으로 미래 30일을 예측
412
+
413
+ last_60_days_2d = self.lstm_data.test_data_2d[-60:]
414
+ last_60_days_3d = last_60_days_2d.reshape(1, -1, 1)
415
+
416
+ future_predictions = []
417
+ for _ in range(self.future_days):
418
+ predicted_price_2d = model.predict(last_60_days_3d)
419
+ future_predictions.append(predicted_price_2d[0][0])
420
+
421
+ # 예측값을 다시 입력으로 사용하여 새로운 예측을 만듦
422
+ predicted_price_reshaped = np.reshape(predicted_price_2d, (1, 1, 1)) # 3D 배열로 변환
423
+ last_60_days_3d = np.append(last_60_days_3d[:, 1:, :], predicted_price_reshaped, axis=1)
424
+
425
+ # 예측된 주가를 다시 스케일링 복원
426
+ future_predictions_2d = np.array(future_predictions).reshape(-1, 1)
427
+ future_predictions_scaled_2d = self.scaler.inverse_transform(future_predictions_2d)
428
+ ensemble_future_predictions_2d.append(future_predictions_scaled_2d)
429
+
430
+ return ensemble_train_predictions_2d, ensemble_test_predictions_2d, ensemble_future_predictions_2d
431
+
432
+ def grading(self, ensemble_train_predictions_2d: list, ensemble_test_predictions_2d: list) -> LSTMGrade:
433
+ """
434
+ 딥러닝 결과를 분석하기 위한 함수
435
+ :param ensemble_train_predictions_2d:
436
+ :param ensemble_test_predictions_2d:
437
+ :return:
438
+ """
439
+ # 예측값을 평균내서 최종 예측값 도출
440
+ mean_train_prediction_2d = np.mean(ensemble_train_predictions_2d, axis=0)
441
+ mean_test_predictions_2d = np.mean(ensemble_test_predictions_2d, axis=0)
442
+
443
+ # y값(정답) 정규화 해제
444
+ y_train_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_train_1d.reshape(-1, 1))
445
+ y_test_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_test_1d.reshape(-1, 1))
446
+
447
+ # 평가 지표 계산
448
+ train_mse = mean_squared_error(y_train_scaled_2d, mean_train_prediction_2d)
449
+ train_mae = mean_absolute_error(y_train_scaled_2d, mean_train_prediction_2d)
450
+ train_r2 = r2_score(y_train_scaled_2d, mean_train_prediction_2d)
451
+
452
+ test_mse = mean_squared_error(y_test_scaled_2d, mean_test_predictions_2d)
453
+ test_mae = mean_absolute_error(y_test_scaled_2d, mean_test_predictions_2d)
454
+ test_r2 = r2_score(y_test_scaled_2d, mean_test_predictions_2d)
455
+
456
+ # 평가 결과 출력
457
+ print("Training Data:")
458
+ print(f"Train MSE: {train_mse}, Train MAE: {train_mae}, Train R²: {train_r2}")
459
+ print("\nTesting Data:")
460
+ print(f"Test MSE: {test_mse}, Test MAE: {test_mae}, Test R²: {test_r2}")
461
+ # mse, mae는 작을수록 좋으며 R^2은 0-1 사이값 1에 가까울수록 정확함
462
+ # 과적합에 대한 평가는 train 과 test를 비교하여 test가 너무 않좋으면 과적합 의심.
463
+
464
+ return LSTMGrade(
465
+ code=self.code,
466
+ mean_train_prediction_2d=mean_train_prediction_2d,
467
+ mean_test_predictions_2d=mean_test_predictions_2d,
468
+ train_mse=train_mse,
469
+ train_mae=train_mae,
470
+ train_r2=train_r2,
471
+ test_mse=test_mse,
472
+ test_mae=test_mae,
473
+ test_r2=test_r2,
474
+ )
475
+
476
+ def get_final_predictions(self, refresh, num=5) -> tuple:
477
+ """
478
+ 미래 예측치를 레디스 캐시를 이용하여 반환함
479
+ :param refresh:
480
+ :param num: 앙상블 반복횟수
481
+ :return:
482
+ """
483
+ print("**** Start get_final_predictions... ****")
484
+ redis_name = f'{self.code}_mylstm_predictions'
485
+
486
+ print(
487
+ f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time / 3600}h")
488
+
489
+ def fetch_final_predictions(num_in) -> tuple:
490
+ """
491
+ 앙상블법으로 딥러닝을 모델을 반복해서 평균을 내서 미래를 예측한다. 평가는 래시스 캐시로 반환하기 어려워 일단 디버그 용도로만 사용하기로
492
+ :param num_in:
493
+ :return:
494
+ """
495
+ # 앙상블 테스트와 채점
496
+ _, _, ensemble_future_predictions_2d = self.ensemble_training(
497
+ num=num_in)
498
+
499
+ """if grading:
500
+ lstm_grade = self.grading(ensemble_train_predictions_2d, ensemble_test_predictions_2d)
501
+ else:
502
+ lstm_grade = None"""
503
+
504
+ # 시각화를 위한 준비 - 날짜 생성 (미래 예측 날짜), 미래예측값 평균
505
+ last_date = self.raw_data.index[-1]
506
+ future_dates = pd.date_range(last_date, periods=self.future_days + 1).tolist()[1:]
507
+
508
+ # Timestamp 객체를 문자열로 변환
509
+ future_dates_str= [date.strftime('%Y-%m-%d') for date in future_dates]
510
+
511
+ final_future_predictions = np.mean(ensemble_future_predictions_2d, axis=0)
512
+ tsa_logger.info(f'num - future dates : {len(future_dates_str)} future data : {len(final_future_predictions)}')
513
+
514
+ assert len(future_dates_str) == len(final_future_predictions), "future_dates 와 final_future_predictions 개수가 일치하지 않습니다."
515
+
516
+ return future_dates_str, final_future_predictions.tolist()
517
+
518
+ future_dates_str, final_future_predictions = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time)
519
+
520
+ # 문자열을 날짜 형식으로 변환
521
+ future_dates = [datetime.strptime(date, '%Y-%m-%d') for date in future_dates_str]
522
+
523
+ # 리스트를 다시 NumPy 배열로 변환
524
+ final_future_predictions = np.array(final_future_predictions)
525
+
526
+ return future_dates, final_future_predictions
527
+
528
+ def export(self, refresh=False, to="str") -> Optional[str]:
529
+ """
530
+ prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
531
+ :param refresh:
532
+ :param to: str, htmlfile, png
533
+ :return:
534
+ """
535
+ future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
536
+ final_future_predictions = final_future_predictions.reshape(-1) # 차원을 하나 줄인다.
537
+
538
+ # Plotly를 사용한 시각화
539
+ fig = go.Figure()
540
+
541
+ # 실제 데이터
542
+ fig.add_trace(go.Scatter(x=self.raw_data.index[-120:], y=self.raw_data['Close'][-120:], mode='markers', name='실제주가'))
543
+ tsa_logger.debug(f"self.raw_data.index[-120:] - {self.raw_data.index[-120:]}")
544
+ tsa_logger.debug(f"self.raw_data['Close'][-120:] - {self.raw_data['Close'][-120:]}")
545
+ # 예측 데이터
546
+ fig.add_trace(go.Scatter(x=future_dates, y=final_future_predictions, mode='lines+markers', name='예측치(30일)'))
547
+ tsa_logger.debug(f"future_dates - {future_dates}")
548
+ tsa_logger.debug(f"final_future_predictions - {final_future_predictions}")
549
+
550
+ fig.update_layout(
551
+ # title=f'{self.code} {self.name} 주가 예측 그래프(prophet)',
552
+ xaxis_title='일자',
553
+ yaxis_title='주가(원)',
554
+ xaxis = dict(
555
+ tickformat='%Y/%m', # X축을 '연/월' 형식으로 표시
556
+ ),
557
+ yaxis = dict(
558
+ tickformat=".0f", # 소수점 없이 원래 숫자 표시
559
+ ),
560
+ showlegend=False,
561
+ )
562
+
563
+ if to == 'str':
564
+ # 그래프 HTML로 변환 (string 형식으로 저장)
565
+ graph_html = plot(fig, output_type='div')
566
+ return graph_html
567
+ elif to == 'png':
568
+ # 그래프를 PNG 파일로 저장
569
+ fig.write_image(f"myLSTM_{self.code}.png")
570
+ return None
571
+ elif to == 'htmlfile':
572
+ # 그래프를 HTML로 저장
573
+ plot(fig, filename=f'myLSTM_{self.code}.html', auto_open=False)
574
+ return None
575
+ else:
576
+ Exception("to 인자가 맞지 않습니다.")
577
+
578
+ def visualization(self, refresh=True):
579
+ future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh)
580
+
581
+ # 시각화1
582
+ plt.figure(figsize=(10, 6))
583
+
584
+ # 실제 주가
585
+ plt.plot(self.raw_data.index, self.raw_data['Close'], label='Actual Price')
586
+
587
+ # 미래 주가 예측
588
+ plt.plot(future_dates, final_future_predictions, label='Future Predicted Price', linestyle='--')
589
+
590
+ plt.xlabel('Date')
591
+ plt.ylabel('Stock Price')
592
+ plt.legend()
593
+ plt.title('Apple Stock Price Prediction with LSTM')
594
+ plt.show()
595
+
596
+ """# 시각화2
597
+ plt.figure(figsize=(10, 6))
598
+ 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')
599
+ plt.plot(self.raw_data.index[self.lstm_data.train_size + 60:], lstm_grade.mean_test_predictions_2d, label='Predicted Price')
600
+ plt.xlabel('Date')
601
+ plt.ylabel('Price')
602
+ plt.legend()
603
+ plt.title('Stock Price Prediction with LSTM Ensemble')
604
+ plt.show()"""
605
+
606
+ def caching_based_on_prophet_ranking(self, refresh: bool, top=20):
607
+ ranking_topn = OrderedDict(itertools.islice(MyProphet.ranking().items(), top))
608
+ tsa_logger.info(ranking_topn)
609
+ print(f"*** LSTM prediction redis cashing top{top} items ***")
610
+ for i, (code, _) in enumerate(ranking_topn.items()):
611
+ print(f"{i+1}. {self.code}/{self.name}")
612
+ self.code = code
613
+ self.get_final_predictions(refresh=refresh, num=5)
614
+
615
+
206
616
 
207
617
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: analyser_hj3415
3
- Version: 2.8.3
3
+ Version: 2.9.1
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=6ZPAkU2b3bYx82_e3X1DvWUWUKUUZMc1crXHS4yvUFA,11924
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=5ZfnRNQGRRVK9TIVlg6Pe4HRMH9XSIOEOXqs4pTvkAI,25159
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.3.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
13
- analyser_hj3415-2.8.3.dist-info/LICENSE,sha256=QVKTp0dTnB5xG8RLgG17LwSWCKNEzYoVVM6KjoCPKc0,1079
14
- analyser_hj3415-2.8.3.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
15
- analyser_hj3415-2.8.3.dist-info/METADATA,sha256=0dU99-ahUJ2iUbdYajmHp-oUd42tciwREnnQRE--QDk,6607
16
- analyser_hj3415-2.8.3.dist-info/RECORD,,
11
+ analyser_hj3415-2.9.1.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
12
+ analyser_hj3415-2.9.1.dist-info/LICENSE,sha256=QVKTp0dTnB5xG8RLgG17LwSWCKNEzYoVVM6KjoCPKc0,1079
13
+ analyser_hj3415-2.9.1.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
14
+ analyser_hj3415-2.9.1.dist-info/METADATA,sha256=qrTU549kDdNMAxp7bCfxIFLIoSdXtXyFXpJX7mnwlOU,6607
15
+ analyser_hj3415-2.9.1.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()