analyser_hj3415 3.4.2__py3-none-any.whl → 4.0.0__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.
@@ -1,7 +1,9 @@
1
1
  import os
2
+ from collections import OrderedDict
2
3
  from dataclasses import dataclass
3
- from typing import Tuple
4
+ from typing import Tuple, Dict, List
4
5
  import math
6
+ import pickle
5
7
 
6
8
  from utils_hj3415 import tools, setup_logger
7
9
  from db_hj3415 import myredis
@@ -9,7 +11,7 @@ from db_hj3415 import myredis
9
11
  from analyser_hj3415.analyser.eval.common import Tools
10
12
 
11
13
 
12
- mylogger = setup_logger(__name__,'WARNING')
14
+ mylogger = setup_logger(__name__,'INFO')
13
15
  expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
14
16
 
15
17
 
@@ -87,6 +89,7 @@ class Red:
87
89
  recent_price (float): 최근 주가.
88
90
  expect_earn (float): 기대수익률. 기본값은 0.06 (6%).
89
91
  """
92
+ REDIS_RED_DATA_SUFFIX = "red_data"
90
93
 
91
94
  def __init__(self, code: str, expect_earn: float = 0.06):
92
95
  assert tools.is_6digit(code), f'Invalid value : {code}'
@@ -137,7 +140,7 @@ class Red:
137
140
  로그:
138
141
  - 비유동부채 데이터가 없을 경우 경고 메시지를 출력합니다.
139
142
  """
140
- mylogger.info(f'In the calc비유동부채... refresh : {refresh}')
143
+ mylogger.debug(f'In the calc비유동부채... refresh : {refresh}')
141
144
  self.c103.page = 'c103재무상태표q'
142
145
 
143
146
  d, 비유동부채 = self.c103.sum_recent_4q('비유동부채', refresh)
@@ -264,30 +267,69 @@ class Red:
264
267
  score = score,
265
268
  )
266
269
 
267
- def get(self, refresh = False, verbose = True) -> RedData:
268
- """
269
- RedData 객체를 Redis 캐시에서 가져오거나, 새로 계산하여 반환합니다.
270
-
271
- 캐시에서 데이터를 검색하고, 없을 경우 `_generate_data`를 호출하여 새로운 데이터를 생성한 후,
272
- 캐시에 저장합니다.
273
-
274
- 매개변수:
275
- refresh (bool, optional): 캐시 데이터를 무시하고 새로 계산 여부. 기본값은 False.
276
- verbose (bool, optional): 실행 중 상세 정보를 출력 여부. 기본값은 True.
277
-
278
- 반환값:
279
- RedData: Redis 캐시에서 가져오거나 새로 생성된 RedData 객체.
280
-
281
- 로그:
282
- - 캐시 검색 상태와 새로 계산된 데이터를 출력합니다.
283
- """
284
- redis_name = f"{self.code}_red_data"
285
- mylogger.info(f"{self} RedData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
286
- if verbose:
287
- print(f"{self} redisname: '{redis_name}' / expect_earn: {self.expect_earn} / refresh : {refresh} / expire_time : {expire_time/3600}h")
270
+ def get(self, refresh = False) -> RedData:
271
+ mylogger.info(f"*** Get red data ***")
272
+ redis_name = f"{self.code}_{self.REDIS_RED_DATA_SUFFIX}_{self.expect_earn}"
273
+ mylogger.info(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
288
274
 
289
275
  def fetch_generate_data(refresh_in: bool) -> RedData:
290
276
  return self._generate_data(refresh_in) # type: ignore
291
277
 
292
278
  return myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time)
293
279
 
280
+ @classmethod
281
+ def bulk_generate_data(cls, codes: List[str], expect_earn: float, refresh: bool) -> Dict[str, RedData]:
282
+ # --- (1) 파이프라인 GET ---
283
+ pipe = myredis.Base.redis_client.pipeline()
284
+ redis_keys = [f"{code}_{cls.REDIS_RED_DATA_SUFFIX}_{expect_earn}" for code in codes]
285
+ for redis_key in redis_keys:
286
+ pipe.get(redis_key)
287
+ results_from_redis = pipe.execute() # [val1, val2, ...]
288
+
289
+ final_results = {}
290
+ missing_codes = []
291
+
292
+ # refresh=True 이면 기존 데이터 무시하고 다시 계산해야 하므로 모두 missing 처리
293
+ if refresh:
294
+ missing_codes = codes[:]
295
+ else:
296
+ # refresh=False 이면, Redis 값이 None인 티커만 다시 계산
297
+ for code, val in zip(codes, results_from_redis):
298
+ if val is None:
299
+ missing_codes.append(code)
300
+ else:
301
+ # Redis에 pickled 데이터가 있다면 언피클해서 담기
302
+ red_data = pickle.loads(val)
303
+ final_results[code] = red_data
304
+
305
+ # --- (2) 필요한 티커만 직접 연산 ---
306
+ newly_computed_data = {}
307
+ for code in missing_codes:
308
+ mylogger.debug(f"*** bulk_generate_data : {code}")
309
+ data = cls(code, expect_earn)._generate_data(refresh=refresh)
310
+ newly_computed_data[code] = data
311
+
312
+ # --- (3) 파이프라인 SET ---
313
+ if newly_computed_data:
314
+ pipe = myredis.Base.redis_client.pipeline()
315
+ for code, data in newly_computed_data.items():
316
+ redis_key = f"{code}_{cls.REDIS_RED_DATA_SUFFIX}_{expect_earn}"
317
+ # ProphetLatestData 객체를 pickle로 직렬화
318
+ pickled_data = pickle.dumps(data)
319
+ # SET + expire_time
320
+ pipe.setex(redis_key, expire_time, pickled_data)
321
+ pipe.execute()
322
+
323
+ # 최종 결과 딕셔너리 (캐시에 있었던 것 + 새로 만든 것)
324
+ final_results.update(newly_computed_data)
325
+ return final_results
326
+
327
+ @staticmethod
328
+ def ranking(expect_earn: float = 0.06, refresh=False) -> OrderedDict:
329
+ mylogger.info("**** Start red ranking ... ****")
330
+
331
+ data = Red.bulk_generate_data(myredis.Corps.list_all_codes(), expect_earn, refresh)
332
+ mylogger.debug(data)
333
+ return OrderedDict(sorted(data.items(), key=lambda x: x[1].score, reverse=True))
334
+
335
+
@@ -1,12 +1,10 @@
1
1
  import os
2
2
  import numpy as np
3
+ import pandas
3
4
  import yfinance as yf
4
5
  from datetime import datetime, timedelta
5
6
  import pandas as pd
6
- from typing import Optional, Tuple
7
- import plotly.graph_objs as go
8
- from plotly.offline import plot
9
- import matplotlib.pyplot as plt # Matplotlib 수동 임포트
7
+ from typing import Tuple, Dict, List
10
8
  from sklearn.preprocessing import MinMaxScaler
11
9
  from tensorflow.keras.models import Sequential # type: ignore
12
10
  from tensorflow.keras.layers import LSTM, Dense, Dropout # type: ignore
@@ -19,7 +17,7 @@ from utils_hj3415 import tools, setup_logger
19
17
  from db_hj3415 import myredis
20
18
  from analyser_hj3415.analyser import MIs, tsa
21
19
 
22
- mylogger = setup_logger(__name__,'WARNING')
20
+ mylogger = setup_logger(__name__,'INFO')
23
21
  expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
24
22
 
25
23
 
@@ -75,6 +73,17 @@ class LSTMGrade:
75
73
  test_r2: float
76
74
 
77
75
 
76
+ @dataclass
77
+ class LSTMChartData:
78
+ ticker: str
79
+ labels: List[pandas.Timestamp]
80
+ prices: List[Dict[pandas.Timestamp, float]]
81
+ future_prices: List[Dict[pandas.Timestamp, float]]
82
+ grade: LSTMGrade
83
+ num: int
84
+ is_lstm_up: bool
85
+
86
+
78
87
  class MyLSTM:
79
88
  """
80
89
  주가 데이터를 기반으로 LSTM 모델을 생성, 학습 및 예측하는 클래스.
@@ -90,9 +99,10 @@ class MyLSTM:
90
99
  future_days = 30
91
100
 
92
101
  def __init__(self, ticker: str):
93
- mylogger.info(f'set up ticker : {ticker}')
102
+ mylogger.info(f'MyLSTM set up ticker : {ticker}')
94
103
  self.scaler = MinMaxScaler(feature_range=(0, 1))
95
104
  self._ticker = ticker
105
+ self.initialized = False
96
106
 
97
107
  self.raw_data = pd.DataFrame()
98
108
  self.lstm_data = LSTMData(
@@ -113,9 +123,10 @@ class MyLSTM:
113
123
 
114
124
  @ticker.setter
115
125
  def ticker(self, ticker: str):
116
- mylogger.info(f'change ticker : {self.ticker} -> {ticker}')
126
+ mylogger.info(f'MyLstm change ticker : {self.ticker} -> {ticker}')
117
127
  self.scaler = MinMaxScaler(feature_range=(0, 1))
118
128
  self._ticker = ticker
129
+ self.initialized = False
119
130
 
120
131
  self.raw_data = pd.DataFrame()
121
132
  self.lstm_data = LSTMData(
@@ -148,7 +159,7 @@ class MyLSTM:
148
159
 
149
160
  # 4년 전 날짜 계산 (4년 = 365일 * 4)
150
161
  four_years_ago = today - timedelta(days=365 * 4)
151
- mylogger.info(
162
+ mylogger.debug(
152
163
  f"Get raw data from yfinance - start: {four_years_ago.strftime('%Y-%m-%d')}, end: {today.strftime('%Y-%m-%d')}")
153
164
 
154
165
  df = yf.download(
@@ -165,7 +176,7 @@ class MyLSTM:
165
176
  lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)
166
177
  :return:
167
178
  """
168
- mylogger.info("lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)")
179
+ mylogger.debug("lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)")
169
180
  # 필요한 열만 선택 (종가만 사용) - 2차웜 배열로 변환
170
181
  data_2d = self.raw_data['Close'].values.reshape(-1, 1)
171
182
  mylogger.debug(f"종가데이터 2차원베열값[:5] : {data_2d[:5]}")
@@ -178,7 +189,7 @@ class MyLSTM:
178
189
  train_size = int(len(scaled_data_2d) * 0.8)
179
190
  train_data_2d = scaled_data_2d[:train_size]
180
191
  test_data_2d = scaled_data_2d[train_size:]
181
- mylogger.info(f'총 {len(data_2d)}개 데이터, train size : {train_size}')
192
+ mylogger.debug(f'총 {len(data_2d)}개 데이터, train size : {train_size}')
182
193
 
183
194
  # 학습 데이터에 대한 입력(X)과 정답(y)를 생성
184
195
  def create_dataset(data, time_step=60):
@@ -228,10 +239,13 @@ class MyLSTM:
228
239
  y_test_1d=y_test_1d,
229
240
  )
230
241
 
242
+ mylogger.info("*** Initializing ***")
243
+
231
244
  self.scaler = MinMaxScaler(feature_range=(0, 1))
232
245
 
233
246
  self.raw_data = get_raw_data()
234
247
  self.lstm_data = preprocessing_for_lstm()
248
+ self.initialized = True
235
249
 
236
250
  def ensemble_training(self, num) -> Tuple[list, LSTMGrade]:
237
251
  """
@@ -289,7 +303,7 @@ class MyLSTM:
289
303
  """
290
304
  predictions_2d = model_in.predict(data)
291
305
  predictions_scaled_2d = self.scaler.inverse_transform(predictions_2d) # 스케일링 복원
292
- mylogger.info(
306
+ mylogger.debug(
293
307
  f'predictions_scaled_2d : ndim - {predictions_scaled_2d.ndim} len - {len(predictions_scaled_2d)}') # numpy.ndarray 타입
294
308
  mylogger.debug(f'predictions_scaled_2d[:5] :{predictions_scaled_2d[:5]}')
295
309
  return predictions_scaled_2d
@@ -331,10 +345,10 @@ class MyLSTM:
331
345
  test_r2 = r2_score(y_test_scaled_2d, mean_test_predictions_2d)
332
346
 
333
347
  # 평가 결과 출력
334
- print("Training Data:")
335
- print(f"Train MSE: {train_mse}, Train MAE: {train_mae}, Train R²: {train_r2}")
336
- print("\nTesting Data:")
337
- print(f"Test MSE: {test_mse}, Test MAE: {test_mae}, Test R²: {test_r2}")
348
+ mylogger.debug("Training Data:")
349
+ mylogger.debug(f"Train MSE: {train_mse}, Train MAE: {train_mae}, Train R²: {train_r2}")
350
+ mylogger.debug("\nTesting Data:")
351
+ mylogger.debug(f"Test MSE: {test_mse}, Test MAE: {test_mae}, Test R²: {test_r2}")
338
352
  # mse, mae는 작을수록 좋으며 R^2은 0-1 사이값 1에 가까울수록 정확함
339
353
  # 과적합에 대한 평가는 train 과 test를 비교하여 test가 너무 않좋으면 과적합 의심.
340
354
 
@@ -348,19 +362,21 @@ class MyLSTM:
348
362
  test_r2=test_r2,
349
363
  )
350
364
 
365
+ mylogger.info(f"*** ensemble training / num : {num} ***")
366
+
351
367
  ensemble_train_predictions_2d = []
352
368
  ensemble_test_predictions_2d = []
353
369
  ensemble_future_predictions_2d = []
354
370
 
355
371
  for i in range(num):
356
- print(f"Training model {i + 1}/{num}...")
372
+ mylogger.info(f"Training model {i + 1}/{num}...")
357
373
  model = model_training()
358
374
 
359
375
  if len(model.layers) == 0:
360
376
  mylogger.warning("이 모델은 빈 Sequential() 입니다.")
361
377
  return [], grading([],[])
362
378
  else:
363
- mylogger.info("레이어가 있는 모델입니다.")
379
+ mylogger.debug("레이어가 있는 모델입니다.")
364
380
 
365
381
  # 훈련 데이터 예측
366
382
  train_predictions_scaled_2d = prediction(model, self.lstm_data.X_train_3d)
@@ -390,11 +406,12 @@ class MyLSTM:
390
406
  future_predictions_scaled_2d = self.scaler.inverse_transform(future_predictions_2d)
391
407
  ensemble_future_predictions_2d.append(future_predictions_scaled_2d)
392
408
 
409
+
393
410
  lstm_grade = grading(ensemble_train_predictions_2d, ensemble_test_predictions_2d)
394
411
 
395
412
  return ensemble_future_predictions_2d, lstm_grade
396
413
 
397
- def get_final_predictions(self, refresh: bool, num=5) -> Tuple[dict, LSTMGrade]:
414
+ def get_final_predictions(self, refresh: bool, num=5) -> Tuple[Dict[str, float], LSTMGrade]:
398
415
  """
399
416
  LSTM 모델을 사용하여 미래 주가를 예측하고 평가 데이터를 반환합니다.
400
417
 
@@ -409,10 +426,10 @@ class MyLSTM:
409
426
  - 캐시 데이터 검색 및 새 데이터 생성 과정 출력.
410
427
  - 예측 값의 증가 추세를 분석하여 캐시에 저장.
411
428
  """
412
- print("**** Start get_final_predictions... ****")
429
+ mylogger.info("**** Start get_final_predictions... ****")
413
430
  redis_name = f'{self.ticker}_mylstm_predictions'
414
431
 
415
- print(
432
+ mylogger.info(
416
433
  f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
417
434
 
418
435
  def caching_is_lstm_up(future_data_in: dict):
@@ -424,9 +441,9 @@ class MyLSTM:
424
441
  bool: 기울기가 양수이면 True, 아니면 False
425
442
  """
426
443
 
427
- print("**** Caching is_lstm_up ... ****")
444
+ mylogger.debug("**** Caching is_lstm_up ... ****")
428
445
  redis_name = f'{self.ticker}_is_lstm_up'
429
- print(f"redisname: '{redis_name}' / expire_time : {expire_time / 3600}h")
446
+ mylogger.debug(f"redisname: '{redis_name}' / expire_time : {expire_time / 3600}h")
430
447
 
431
448
  is_up = tsa.common.is_up_by_OLS(future_data_in)
432
449
  mylogger.debug(f"is_up: {is_up}")
@@ -456,7 +473,8 @@ class MyLSTM:
456
473
  data[future_dates[i].strftime("%Y-%m-%d")] = final_future_predictions[i][0]
457
474
  return data
458
475
 
459
- self.initializing()
476
+ if not self.initialized:
477
+ self.initializing()
460
478
  # 앙상블 트레이닝 시행
461
479
  future_predictions_2d, lstm_grade = self.ensemble_training(num=num_in)
462
480
  mylogger.debug(f'future_predictions_2d[:5] : {future_predictions_2d[:5]}')
@@ -477,159 +495,72 @@ class MyLSTM:
477
495
 
478
496
  return future_data, lstm_grade
479
497
 
480
- def export(self, refresh=False, to="html", num=5) -> Optional[str]:
481
- """
482
- 과거 예측된 주가 데이터를 기반으로 시각화를 생성하고 저장합니다.
498
+ def generate_chart_data(self, refresh:bool, num=5) -> LSTMChartData:
499
+ mylogger.info("**** Start generate_lstm_chart_data... ****")
500
+ redis_name = f'{self.ticker}_lstm_chart_data'
483
501
 
484
- 매개변수:
485
- refresh (bool): 데이터 새로고침 여부. 기본값은 False.
486
- to (str): 그래프 출력 형식 ('hrml', 'png', 'file'). 기본값은 'html'.
487
- num (int): 예측 모델 수. 기본값은 5.
502
+ mylogger.info(
503
+ f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time / 3600}h")
488
504
 
489
- 반환값:
490
- Optional[str]: HTML 형식의 그래프 문자열(`to='html'`인 경우).
491
- None: PNG 또는 HTML 파일로 저장된 경우.
505
+ def fetch_generate_lstm_chart_data(num_in) -> LSTMChartData:
506
+ def prepare_past_data(past_days) -> tuple:
507
+ mylogger.info("*** prepare past data ... ****")
508
+ # 데이터 준비
509
+ raw_data_copied = self.raw_data.reset_index()
510
+ data = raw_data_copied[['Date', 'Close']][-past_days:].reset_index(drop=True)
492
511
 
493
- 예외:
494
- Exception: 잘못된 `to` 값이 주어졌을 때 발생.
495
- """
496
- def prepare_past_data(past_days) -> tuple:
497
- # 데이터 준비
498
- raw_data_copied = self.raw_data.reset_index()
499
- data = raw_data_copied[['Date', 'Close']][-past_days:].reset_index(drop=True)
500
-
501
- # 'Date'와 'Close' 열 추출
502
- past_dates = pd.to_datetime(data['Date'])
503
- past_prices = data['Close']
504
-
505
- # 'past_prices'가 Series인지 확인
506
- if isinstance(past_prices, pd.DataFrame):
507
- past_prices = past_prices.squeeze()
508
-
509
- # 'Close' 열의 데이터 타입 변경
510
- past_prices = past_prices.astype(float)
511
- return past_dates, past_prices
512
-
513
- def prepare_future_data(refresh_in, num_in) -> tuple:
514
- future_data, lstm_grade = self.get_final_predictions(refresh=refresh_in, num=num_in)
515
-
516
- # 예측 데이터 준비
517
- future_dates = pd.to_datetime(list(future_data.keys()))
518
-
519
- future_prices = pd.Series(future_data.values(), index=range(len(future_data.values()))).astype(float)
520
- return future_dates, future_prices
521
-
522
- self.initializing()
523
- past_dates, past_prices = prepare_past_data(past_days=120)
524
- future_dates, future_prices = prepare_future_data(refresh_in=refresh, num_in=num)
525
-
526
- # 그래프 생성
527
- fig = go.Figure()
528
-
529
- # 실제 데이터 추가
530
- fig.add_trace(go.Scatter(
531
- x=past_dates,
532
- y=past_prices,
533
- mode='markers',
534
- name='실제주가'
535
- ))
536
-
537
- # 예측 데이터 추가
538
- fig.add_trace(go.Scatter(
539
- x=future_dates,
540
- y=future_prices,
541
- mode='lines+markers',
542
- name='예측치(30일)'
543
- ))
544
-
545
- # 레이아웃 업데이트
546
- fig.update_layout(
547
- xaxis_title='일자',
548
- yaxis_title='주가(원)',
549
- xaxis=dict(
550
- tickformat='%Y/%m',
551
- ),
552
- yaxis=dict(
553
- tickformat=".0f",
554
- ),
555
- showlegend=True,
556
- )
512
+ # 'Date'와 'Close' 열 추출
513
+ past_dates = pd.to_datetime(data['Date'])
514
+ past_prices = data['Close']
557
515
 
558
- mylogger.debug(f"past_dates({len(past_dates)}) - {past_dates}")
559
- mylogger.debug(f"past_prices({len(past_prices)} - {past_prices}")
560
- mylogger.debug(f"future_dates({len(future_dates)}) - {future_dates}")
561
- mylogger.debug(f"future_prices({len(future_prices)}) - {future_prices}")
562
-
563
- fig.update_layout(
564
- # title=f'{self.code} {self.name} 주가 예측 그래프(prophet)',
565
- xaxis_title='일자',
566
- yaxis_title='주가(원)',
567
- xaxis=dict(
568
- tickformat='%Y/%m', # X축을 '연/월' 형식으로 표시
569
- ),
570
- yaxis=dict(
571
- tickformat=".0f", # 소수점 없이 원래 숫자 표시
572
- ),
573
- showlegend=False,
574
- )
516
+ # 'past_prices'가 Series인지 확인
517
+ if isinstance(past_prices, pd.DataFrame):
518
+ past_prices = past_prices.squeeze()
575
519
 
576
- if to == 'html':
577
- # 그래프 HTML로 변환 (string 형식으로 저장)
578
- graph_html = plot(fig, output_type='div')
579
- return graph_html
580
- elif to == 'png':
581
- # 그래프를 PNG 파일로 저장
582
- fig.write_image(f"myLSTM_{self.ticker}.png")
583
- return None
584
- elif to == 'file':
585
- # 그래프를 HTML로 저장
586
- plot(fig, filename=f'myLSTM_{self.ticker}.html', auto_open=False)
587
- return None
588
- else:
589
- Exception("to 인자가 맞지 않습니다.")
590
-
591
- def visualization(self, refresh=True):
592
- """
593
- 실제 주가와 예측 주가를 시각화합니다.
520
+ # 'Close' 열의 데이터 타입 변경
521
+ past_prices = past_prices.astype(float)
522
+ return past_dates, past_prices
594
523
 
595
- 매개변수:
596
- refresh (bool): 예측 데이터를 새로고침할지 여부. 기본값은 True.
524
+ def prepare_future_data(refresh_in, num_in) -> tuple:
525
+ mylogger.info("*** prepare future data ... ****")
526
+ future_data, lstm_grade = self.get_final_predictions(refresh=refresh_in, num=num_in)
597
527
 
598
- 반환값:
599
- None: 시각화를 출력합니다.
600
- """
601
- self.initializing()
602
- future_data, _ = self.get_final_predictions(refresh=refresh)
603
- mylogger.debug(f'future_data : {future_data}')
604
- future_dates = pd.to_datetime(list(future_data.keys()))
605
- mylogger.debug(f'future_dates : {future_dates}')
606
- future_prices = pd.Series(future_data.values(), index=range(len(future_data.values()))).astype(float)
607
- mylogger.debug(f'future_prices : {future_prices}')
608
-
609
- # 시각화1
610
- plt.figure(figsize=(10, 6))
611
-
612
- # 실제 주가
613
- plt.plot(self.raw_data.index, self.raw_data['Close'], label='Actual Price')
614
-
615
- # 미래 주가 예측
616
- plt.plot(future_dates, future_prices, label='Future Predicted Price', linestyle='--')
617
-
618
- plt.xlabel('Date')
619
- plt.ylabel('Stock Price')
620
- plt.legend()
621
- plt.title(f'{self.ticker} Stock Price Prediction with LSTM')
622
- plt.show()
623
-
624
- """# 시각화2
625
- plt.figure(figsize=(10, 6))
626
- 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')
627
- plt.plot(self.raw_data.index[self.lstm_data.train_size + 60:], lstm_grade.mean_test_predictions_2d, label='Predicted Price')
628
- plt.xlabel('Date')
629
- plt.ylabel('Price')
630
- plt.legend()
631
- plt.title('Stock Price Prediction with LSTM Ensemble')
632
- plt.show()"""
528
+ # 예측 데이터 준비
529
+ future_dates = pd.to_datetime(list(future_data.keys()))
530
+
531
+ future_prices = pd.Series(future_data.values(), index=range(len(future_data.values()))).astype(float)
532
+ return future_dates, future_prices, lstm_grade
533
+
534
+ if not self.initialized:
535
+ self.initializing()
536
+ past_dates, past_prices = prepare_past_data(past_days=120)
537
+ future_dates, future_prices, lstm_grade = prepare_future_data(refresh_in=refresh, num_in=num)
538
+ past_df = pd.DataFrame({"ds": past_dates, "y": past_prices})
539
+ future_df = pd.DataFrame({"ds": future_dates, "future_price": future_prices})
540
+
541
+ # 날짜(ds)를 기준으로 outer join
542
+ merged_df = pd.merge(past_df, future_df, on="ds", how="outer")
543
+ mylogger.debug(f"df_merged head: {merged_df.head()}")
544
+ mylogger.debug(f"df_merged tail: {merged_df.tail()}")
545
+
546
+ data = LSTMChartData(
547
+ ticker=self.ticker,
548
+ labels=merged_df["ds"].tolist(),
549
+ prices=[{"x": ds, "y": y} for ds, y in zip(merged_df["ds"], merged_df["y"]) if pd.notna(y)], # type: ignore
550
+ future_prices=[{"x": ds, "y": future_price} for ds, future_price in zip(merged_df["ds"], merged_df["future_price"])], # type: ignore
551
+ grade=lstm_grade,
552
+ num=num_in,
553
+ is_lstm_up=self.is_lstm_up(),
554
+ )
555
+ #import pprint
556
+ #pprint.pprint(data.prices[-10:], compact=True)
557
+ #pprint.pprint(data.future_prices[:10], compact=True)
558
+ #print(data.grade)
559
+ return data
560
+
561
+ lstm_chart_data = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_lstm_chart_data,
562
+ num, timer=expire_time)
563
+ return lstm_chart_data
633
564
 
634
565
  def is_lstm_up(self) -> bool:
635
566
  """
@@ -640,6 +571,16 @@ class MyLSTM:
640
571
  """
641
572
  return myredis.Base.get_value(f'{self.ticker}_is_lstm_up')
642
573
 
574
+ @staticmethod
575
+ def caching_chart_data(tickers:list, num:int):
576
+ mylogger.info(f"*** caching_chart_data ***")
577
+ mylstm = MyLSTM('005930.KS')
578
+ for i, ticker in enumerate(tickers):
579
+ mylstm.ticker = ticker
580
+ mylogger.info(f"{i + 1}. {ticker}")
581
+ chart_data = mylstm.generate_chart_data(refresh=True, num=num)
582
+ mylogger.debug(chart_data)
583
+
643
584
 
644
585
  class CorpLSTM(MyLSTM):
645
586
  """
@@ -666,6 +607,26 @@ class CorpLSTM(MyLSTM):
666
607
  self.name = myredis.Corps(code, 'c101').get_name()
667
608
  self.ticker = self.code + '.KS'
668
609
 
610
+ @staticmethod
611
+ def ticker_to_code(ticker: str):
612
+ return ticker[:-3]
613
+
614
+ @staticmethod
615
+ def code_to_ticker(code: str):
616
+ return code + '.KS'
617
+
618
+ @staticmethod
619
+ def caching_chart_data_topn(top=40, num=5):
620
+ mylogger.info(f"*** caching_chart_data_topn : {top} ***")
621
+ ranking_topn = tsa.CorpProphet.ranking(refresh=False, top=top)
622
+ mylogger.debug(ranking_topn)
623
+ MyLSTM.caching_chart_data([CorpLSTM.code_to_ticker(code) for code in ranking_topn.keys() ], num=num)
624
+
625
+ @staticmethod
626
+ def caching_chart_data_bulk(codes: list, num=5):
627
+ mylogger.info(f"*** caching_chart_data_bulk : {len(codes)} items ***")
628
+ MyLSTM.caching_chart_data([CorpLSTM.code_to_ticker(code) for code in codes], num=num)
629
+
669
630
 
670
631
  class MILSTM(MyLSTM):
671
632
  """
@@ -689,3 +650,11 @@ class MILSTM(MyLSTM):
689
650
  self._mi_type = mi_type
690
651
  self.ticker = getattr(MIs, mi_type)
691
652
 
653
+ @staticmethod
654
+ def caching_chart_data_mi_all(num=5):
655
+ mylogger.info(f"*** caching_chart_data_mi_all ***")
656
+ mylogger.debug(f"mi_type : {MIs._fields}")
657
+ MyLSTM.caching_chart_data([getattr(MIs, mi_type) for mi_type in MIs._fields], num=num)
658
+
659
+
660
+