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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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()