analyser_hj3415 2.10.6__py3-none-any.whl → 3.0.1__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/tsa.py DELETED
@@ -1,708 +0,0 @@
1
- """
2
- Time Series Analysis
3
- """
4
- from pprint import pprint
5
-
6
- import numpy as np
7
- import yfinance as yf
8
- from datetime import datetime, timedelta
9
- import pandas as pd
10
- from prophet import Prophet
11
- from sklearn.preprocessing import StandardScaler
12
- from utils_hj3415 import utils, helpers
13
- from typing import Optional
14
- import plotly.graph_objs as go
15
- from plotly.offline import plot
16
- import matplotlib.pyplot as plt # Matplotlib 수동 임포트
17
- from db_hj3415 import myredis
18
- from collections import OrderedDict
19
- from analyser_hj3415 import eval
20
- from sklearn.preprocessing import MinMaxScaler
21
- from tensorflow.keras.models import Sequential
22
- from tensorflow.keras.layers import LSTM, Dense, Dropout
23
- from tensorflow.keras.callbacks import EarlyStopping
24
- from tensorflow.keras import Input
25
- from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
26
- from dataclasses import dataclass
27
- import itertools
28
-
29
- import logging
30
-
31
- tsa_logger = helpers.setup_logger('tsa_logger', logging.WARNING)
32
-
33
-
34
- class MyProphet:
35
- def __init__(self, code: str):
36
- assert utils.is_6digit(code), f'Invalid value : {code}'
37
- self.scaler = StandardScaler()
38
-
39
- self.model = Prophet()
40
- self._code = code
41
- self.name = myredis.Corps(code, 'c101').get_name()
42
- self.raw_data = self._get_raw_data()
43
- self.df_real = self._preprocessing_for_prophet()
44
- self.df_forecast = self._make_forecast()
45
-
46
- @property
47
- def code(self) -> str:
48
- return self._code
49
-
50
- @code.setter
51
- def code(self, code: str):
52
- assert utils.is_6digit(code), f'Invalid value : {code}'
53
- tsa_logger.info(f'change code : {self.code} -> {code}')
54
- self.model = Prophet()
55
- self._code = code
56
- self.name = myredis.Corps(code, 'c101').get_name()
57
- self.raw_data = self._get_raw_data()
58
- self.df_real = self._preprocessing_for_prophet()
59
- self.df_forecast = self._make_forecast()
60
-
61
- @staticmethod
62
- def is_valid_date(date_string):
63
- try:
64
- # %Y-%m-%d 형식으로 문자열을 datetime 객체로 변환 시도
65
- datetime.strptime(date_string, '%Y-%m-%d')
66
- return True
67
- except ValueError:
68
- # 변환이 실패하면 ValueError가 발생, 형식이 맞지 않음
69
- return False
70
-
71
- def _get_raw_data(self) -> pd.DataFrame:
72
- """
73
- 야후에서 해당 종목의 4년간 주가 raw data를 받아온다.
74
- :return:
75
- """
76
- # 오늘 날짜 가져오기
77
- today = datetime.today()
78
-
79
- # 4년 전 날짜 계산 (4년 = 365일 * 4)
80
- four_years_ago = today - timedelta(days=365 * 4)
81
-
82
- return yf.download(
83
- self.code + '.KS',
84
- start=four_years_ago.strftime('%Y-%m-%d'),
85
- end=today.strftime('%Y-%m-%d')
86
- )
87
-
88
- def _preprocessing_for_prophet(self) -> pd.DataFrame:
89
- """
90
- Prophet이 사용할 수 있도록 데이터 준비
91
- ds는 날짜, y는 주가
92
- :return:
93
- """
94
- df = self.raw_data[['Close', 'Volume']].reset_index()
95
- df.columns = ['ds', 'y', 'volume'] # Prophet의 형식에 맞게 열 이름 변경
96
-
97
- # ds 열에서 타임존 제거
98
- df['ds'] = df['ds'].dt.tz_localize(None)
99
-
100
- # 추가 변수를 정규화
101
- df['volume_scaled'] = self.scaler.fit_transform(df[['volume']])
102
- tsa_logger.debug('_preprocessing_for_prophet')
103
- tsa_logger.debug(df)
104
- return df
105
-
106
- def _make_forecast(self) -> pd.DataFrame:
107
- # 정규화된 'volume_scaled' 변수를 외부 변수로 추가
108
- self.model.add_regressor('volume_scaled')
109
-
110
- self.model.fit(self.df_real)
111
-
112
- # 향후 180일 동안의 주가 예측
113
- future = self.model.make_future_dataframe(periods=180)
114
- tsa_logger.debug('_make_forecast_future')
115
- tsa_logger.debug(future)
116
-
117
- # 미래 데이터에 거래량 추가 (평균 거래량을 사용해 정규화)
118
- future_volume = pd.DataFrame({'volume': [self.raw_data['Volume'].mean()] * len(future)})
119
- future['volume_scaled'] = self.scaler.transform(future_volume[['volume']])
120
-
121
- forecast = self.model.predict(future)
122
- tsa_logger.debug('_make_forecast')
123
- tsa_logger.debug(forecast)
124
- return forecast
125
-
126
- def get_yhat(self) -> dict:
127
- """
128
- 최근 날짜의 예측데이터를 반환한다.
129
- :return: {'ds':..., 'yhat':.., 'yhat_lower':.., 'yhat_upper':..,}
130
- """
131
- df = self.df_forecast
132
- last_real_date = self.df_real.iloc[-1]['ds']
133
- tsa_logger.info(last_real_date)
134
- yhat_dict = df[df['ds']==last_real_date].iloc[0][['ds', 'yhat_lower', 'yhat_upper', 'yhat']].to_dict()
135
- tsa_logger.info(yhat_dict)
136
- return yhat_dict
137
-
138
- def visualization(self):
139
- # 예측 결과 출력
140
- print(self.df_forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
141
- # 예측 결과 시각화 (Matplotlib 사용)
142
- fig = self.model.plot(self.df_forecast)
143
- # 추세 및 계절성 시각화
144
- fig2 = self.model.plot_components(self.df_forecast)
145
- plt.show() # 시각화 창 띄우기
146
-
147
- def export(self, to="str") -> Optional[str]:
148
- """
149
- prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
150
- :param to: str, png, htmlfile
151
- :return:
152
- """
153
- # Plotly를 사용한 시각화
154
- fig = go.Figure()
155
-
156
- # 실제 데이터
157
- fig.add_trace(go.Scatter(x=self.df_real['ds'], y=self.df_real['y'], mode='markers', name='실제주가'))
158
- # 예측 데이터
159
- fig.add_trace(go.Scatter(x=self.df_forecast['ds'], y=self.df_forecast['yhat'], mode='lines', name='예측치'))
160
-
161
- # 상한/하한 구간
162
- fig.add_trace(
163
- go.Scatter(x=self.df_forecast['ds'], y=self.df_forecast['yhat_upper'], fill=None, mode='lines', name='상한'))
164
- fig.add_trace(
165
- go.Scatter(x=self.df_forecast['ds'], y=self.df_forecast['yhat_lower'], fill='tonexty', mode='lines', name='하한'))
166
-
167
- fig.update_layout(
168
- # title=f'{self.code} {self.name} 주가 예측 그래프(prophet)',
169
- xaxis_title='일자',
170
- yaxis_title='주가(원)',
171
- xaxis = dict(
172
- tickformat='%Y/%m', # X축을 '연/월' 형식으로 표시
173
- ),
174
- yaxis = dict(
175
- tickformat=".0f", # 소수점 없이 원래 숫자 표시
176
- ),
177
- showlegend=False,
178
- )
179
-
180
- if to == 'str':
181
- # 그래프 HTML로 변환 (string 형식으로 저장)
182
- graph_html = plot(fig, output_type='div')
183
- return graph_html
184
- elif to == 'png':
185
- # 그래프를 PNG 파일로 저장
186
- fig.write_image(f"myprophet_{self.code}.png")
187
- return None
188
- elif to == 'htmlfile':
189
- # 그래프를 HTML로 저장
190
- plot(fig, filename=f'myprophet_{self.code}.html', auto_open=False)
191
- return None
192
- else:
193
- Exception("to 인자가 맞지 않습니다.")
194
-
195
- def scoring(self) -> int:
196
- """
197
- prophet의 yhat_lower 예측치와 주가를 비교하여 주가가 낮으면 양의 점수를 높으면 음의 점수를 준다.
198
-
199
- Returns:
200
- int: The calculated score based on the deviation between the recent price
201
- and the expected lower limit.
202
-
203
- Parameters:
204
- None
205
-
206
- Raises:
207
- AttributeError: Raised if the necessary attributes like `df_real` or methods like `get_yhat`
208
- are not correctly set or implemented.
209
- KeyError: Raised if the expected keys (`'yhat_lower'` or `'y'`) are not found in the data involved.
210
- ValueError: Raised if the format of data does not conform to expected structure for calculations.
211
- """
212
- last_real_data = self.df_real.iloc[-1]
213
- recent_price = last_real_data['y']
214
- recent_date = datetime.strftime(last_real_data['ds'], '%Y-%m-%d')
215
- yhat_dict = self.get_yhat()
216
- tsa_logger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
217
- yhat_lower = int(yhat_dict['yhat_lower'])
218
- deviation = int(eval.Tools.cal_deviation(recent_price, yhat_lower))
219
- if recent_price > yhat_lower:
220
- score = -deviation
221
- else:
222
- score = deviation
223
- tsa_logger.info(f"{self.code}/{self.name} date: {recent_date} 가격:{recent_price} 기대하한값:{yhat_lower} 편차:{deviation} score:{score}")
224
- return score
225
-
226
-
227
- @dataclass
228
- class LSTMData:
229
- code: str
230
-
231
- data_2d: np.ndarray
232
- train_size: int
233
- train_data_2d: np.ndarray
234
- test_data_2d: np.ndarray
235
-
236
- X_train_3d: np.ndarray
237
- X_test_3d: np.ndarray
238
- y_train_1d: np.ndarray
239
- y_test_1d: np.ndarray
240
-
241
- @dataclass
242
- class LSTMGrade:
243
- """
244
- 딥러닝 모델의 학습 결과를 평가하기 위해 사용하는 데이터 클래스
245
- """
246
- code: str
247
-
248
- mean_train_prediction_2d: np.ndarray
249
- mean_test_predictions_2d: np.ndarray
250
-
251
- train_mse: float
252
- train_mae: float
253
- train_r2: float
254
- test_mse: float
255
- test_mae: float
256
- test_r2: float
257
-
258
- class MyLSTM:
259
- """
260
- LSTM(Long Short-Term Memory)
261
- """
262
- # 미래 몇일을 예측할 것인가?
263
- future_days = 30
264
-
265
- def __init__(self, code: str):
266
- assert utils.is_6digit(code), f'Invalid value : {code}'
267
- self._code = code
268
- self.name = myredis.Corps(code, 'c101').get_name()
269
- self.scaler = MinMaxScaler(feature_range=(0, 1))
270
- self.raw_data = self._get_raw_data()
271
- self.lstm_data = self._preprocessing_for_lstm()
272
-
273
- @property
274
- def code(self) -> str:
275
- return self._code
276
-
277
- @code.setter
278
- def code(self, code: str):
279
- assert utils.is_6digit(code), f'Invalid value : {code}'
280
- tsa_logger.debug(f'change code : {self.code} -> {code}')
281
-
282
- self._code = code
283
- self.name = myredis.Corps(code, 'c101').get_name()
284
- self.scaler = MinMaxScaler(feature_range=(0, 1))
285
- self.raw_data = self._get_raw_data()
286
- self.lstm_data = self._preprocessing_for_lstm()
287
-
288
- def _get_raw_data(self) -> pd.DataFrame:
289
- """
290
- 야후에서 해당 종목의 4년간 주가 raw data를 받아온다.
291
- :return:
292
- """
293
- # 오늘 날짜 가져오기
294
- today = datetime.today()
295
-
296
- # 4년 전 날짜 계산 (4년 = 365일 * 4)
297
- four_years_ago = today - timedelta(days=365 * 4)
298
- tsa_logger.info(f"start: {four_years_ago.strftime('%Y-%m-%d')}, end: {today.strftime('%Y-%m-%d')}")
299
-
300
- df = yf.download(
301
- self.code + '.KS',
302
- start=four_years_ago.strftime('%Y-%m-%d'),
303
- end=today.strftime('%Y-%m-%d')
304
- )
305
- df.index = df.index.tz_localize(None)
306
- tsa_logger.debug(df)
307
- return df
308
-
309
- def _preprocessing_for_lstm(self) -> LSTMData:
310
- """
311
- lstm이 사용할 수 있도록 데이터 준비(정규화 및 8:2 훈련데이터 검증데이터 분리 및 차원변환)
312
- :return:
313
- """
314
- # 필요한 열만 선택 (종가만 사용) - 2차웜 배열로 변환
315
- data_2d = self.raw_data['Close'].values.reshape(-1, 1)
316
- tsa_logger.debug(data_2d)
317
-
318
- # 데이터 정규화 (0과 1 사이로 스케일링)
319
- scaled_data_2d = self.scaler.fit_transform(data_2d)
320
-
321
- # 학습 데이터 생성
322
- # 주가 데이터를 80%는 학습용, 20%는 테스트용으로 분리하는 코드
323
- train_size = int(len(scaled_data_2d) * 0.8)
324
- train_data_2d = scaled_data_2d[:train_size]
325
- test_data_2d = scaled_data_2d[train_size:]
326
- tsa_logger.info(f'총 {len(data_2d)}개 데이터, train size : {train_size}')
327
-
328
- # 학습 데이터에 대한 입력(X)과 정답(y)를 생성
329
- def create_dataset(data, time_step=60):
330
- X, y = [], []
331
- for i in range(len(data) - time_step):
332
- X.append(data[i:i + time_step, 0])
333
- y.append(data[i + time_step, 0])
334
- return np.array(X), np.array(y)
335
-
336
-
337
- X_train, y_train_1d = create_dataset(train_data_2d)
338
- X_test, y_test_1d = create_dataset(test_data_2d)
339
- tsa_logger.debug(X_train.shape)
340
- tsa_logger.debug(X_test.shape)
341
-
342
- try:
343
- # LSTM 모델 입력을 위해 데이터를 3차원으로 변환
344
- X_train_3d = X_train.reshape(X_train.shape[0], X_train.shape[1], 1)
345
- X_test_3d = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
346
- except IndexError:
347
- return LSTMData(
348
- code=self.code,
349
- data_2d=np.array([]),
350
- train_size=0,
351
- train_data_2d=np.array([]),
352
- test_data_2d=np.array([]),
353
- X_train_3d=np.array([]),
354
- X_test_3d=np.array([]),
355
- y_train_1d=np.array([]),
356
- y_test_1d=np.array([]),
357
- )
358
-
359
- 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}')
360
- 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)}')
361
-
362
- return LSTMData(
363
- code=self.code,
364
- data_2d=data_2d,
365
- train_size=train_size,
366
- train_data_2d=train_data_2d,
367
- test_data_2d=test_data_2d,
368
- X_train_3d=X_train_3d,
369
- X_test_3d=X_test_3d,
370
- y_train_1d=y_train_1d,
371
- y_test_1d=y_test_1d,
372
- )
373
-
374
- def _model_training(self) -> Sequential:
375
- # LSTM 모델 생성 - 유닛과 드롭아웃의 수는 테스트로 최적화 됨.
376
- model = Sequential()
377
- # Input(shape=(50, 1))는 50개의 타임스텝을 가지는 입력 데이터를 처리하며, 각 타임스텝에 1개의 특성이 있다는 것을 의미
378
- model.add(Input(shape=(self.lstm_data.X_train_3d.shape[1], 1))) # 입력 레이어에 명시적으로 Input을 사용
379
- model.add(LSTM(units=150, return_sequences=True))
380
- model.add(Dropout(0.2))
381
- model.add(LSTM(units=75, return_sequences=False))
382
- model.add(Dropout(0.2))
383
- model.add(Dense(units=25))
384
- model.add(Dropout(0.3))
385
- model.add(Dense(units=1))
386
-
387
- # 모델 요약 출력
388
- # model.summary()
389
-
390
- # 모델 컴파일 및 학습
391
- model.compile(optimizer='adam', loss='mean_squared_error')
392
-
393
- # 조기 종료 설정
394
- early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
395
-
396
- # 모델 학습 - 과적합 방지위한 조기종료 세팅
397
- model.fit(self.lstm_data.X_train_3d, self.lstm_data.y_train_1d,
398
- epochs=75, batch_size=32, validation_data=(self.lstm_data.X_test_3d, self.lstm_data.y_test_1d),
399
- callbacks=[early_stopping])
400
- return model
401
-
402
- def ensemble_training(self, num) -> tuple:
403
- """
404
- 딥러닝을 num 회 반복하고 평균을 사용하는 함수
405
- :param num: 앙상블 모델 수
406
- :return:
407
- """
408
- def prediction(model_in: Sequential, data: np.ndarray) -> np.ndarray:
409
- """
410
- 훈련될 모델을 통해 예측을 시행하여 정규화를 복원하고 결과 반환한다.
411
- :param model_in:
412
- :param data:
413
- :return:
414
- """
415
- predictions_2d = model_in.predict(data)
416
- predictions_scaled_2d = self.scaler.inverse_transform(predictions_2d) # 스케일링 복원
417
- tsa_logger.info(f'predictions_scaled_2d : ndim - {predictions_scaled_2d.ndim} len - {len(predictions_scaled_2d)}') # numpy.ndarray 타입
418
- tsa_logger.debug(predictions_scaled_2d)
419
- return predictions_scaled_2d
420
-
421
- ensemble_train_predictions_2d = []
422
- ensemble_test_predictions_2d = []
423
- ensemble_future_predictions_2d = []
424
-
425
- for i in range(num):
426
- print(f"Training model {i + 1}/{num}...")
427
- model = self._model_training()
428
-
429
- # 훈련 데이터 예측
430
- train_predictions_scaled_2d = prediction(model, self.lstm_data.X_train_3d)
431
- ensemble_train_predictions_2d.append(train_predictions_scaled_2d)
432
-
433
- # 테스트 데이터 예측
434
- test_predictions_scaled_2d = prediction(model, self.lstm_data.X_test_3d)
435
- ensemble_test_predictions_2d.append(test_predictions_scaled_2d)
436
-
437
- # 8. 미래 30일 예측
438
- # 마지막 60일간의 데이터를 기반으로 미래 30일을 예측
439
-
440
- last_60_days_2d = self.lstm_data.test_data_2d[-60:]
441
- last_60_days_3d = last_60_days_2d.reshape(1, -1, 1)
442
-
443
- future_predictions = []
444
- for _ in range(self.future_days):
445
- predicted_price_2d = model.predict(last_60_days_3d)
446
- future_predictions.append(predicted_price_2d[0][0])
447
-
448
- # 예측값을 다시 입력으로 사용하여 새로운 예측을 만듦
449
- predicted_price_reshaped = np.reshape(predicted_price_2d, (1, 1, 1)) # 3D 배열로 변환
450
- last_60_days_3d = np.append(last_60_days_3d[:, 1:, :], predicted_price_reshaped, axis=1)
451
-
452
- # 예측된 주가를 다시 스케일링 복원
453
- future_predictions_2d = np.array(future_predictions).reshape(-1, 1)
454
- future_predictions_scaled_2d = self.scaler.inverse_transform(future_predictions_2d)
455
- ensemble_future_predictions_2d.append(future_predictions_scaled_2d)
456
-
457
- return ensemble_train_predictions_2d, ensemble_test_predictions_2d, ensemble_future_predictions_2d
458
-
459
- def grading(self, ensemble_train_predictions_2d: list, ensemble_test_predictions_2d: list) -> LSTMGrade:
460
- """
461
- 딥러닝 결과를 분석하기 위한 함수
462
- :param ensemble_train_predictions_2d:
463
- :param ensemble_test_predictions_2d:
464
- :return:
465
- """
466
- # 예측값을 평균내서 최종 예측값 도출
467
- mean_train_prediction_2d = np.mean(ensemble_train_predictions_2d, axis=0)
468
- mean_test_predictions_2d = np.mean(ensemble_test_predictions_2d, axis=0)
469
-
470
- # y값(정답) 정규화 해제
471
- y_train_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_train_1d.reshape(-1, 1))
472
- y_test_scaled_2d = self.scaler.inverse_transform(self.lstm_data.y_test_1d.reshape(-1, 1))
473
-
474
- # 평가 지표 계산
475
- train_mse = mean_squared_error(y_train_scaled_2d, mean_train_prediction_2d)
476
- train_mae = mean_absolute_error(y_train_scaled_2d, mean_train_prediction_2d)
477
- train_r2 = r2_score(y_train_scaled_2d, mean_train_prediction_2d)
478
-
479
- test_mse = mean_squared_error(y_test_scaled_2d, mean_test_predictions_2d)
480
- test_mae = mean_absolute_error(y_test_scaled_2d, mean_test_predictions_2d)
481
- test_r2 = r2_score(y_test_scaled_2d, mean_test_predictions_2d)
482
-
483
- # 평가 결과 출력
484
- print("Training Data:")
485
- print(f"Train MSE: {train_mse}, Train MAE: {train_mae}, Train R²: {train_r2}")
486
- print("\nTesting Data:")
487
- print(f"Test MSE: {test_mse}, Test MAE: {test_mae}, Test R²: {test_r2}")
488
- # mse, mae는 작을수록 좋으며 R^2은 0-1 사이값 1에 가까울수록 정확함
489
- # 과적합에 대한 평가는 train 과 test를 비교하여 test가 너무 않좋으면 과적합 의심.
490
-
491
- return LSTMGrade(
492
- code=self.code,
493
- mean_train_prediction_2d=mean_train_prediction_2d,
494
- mean_test_predictions_2d=mean_test_predictions_2d,
495
- train_mse=train_mse,
496
- train_mae=train_mae,
497
- train_r2=train_r2,
498
- test_mse=test_mse,
499
- test_mae=test_mae,
500
- test_r2=test_r2,
501
- )
502
-
503
- def get_final_predictions(self, refresh: bool, expire_time_h: int, num=5) -> tuple:
504
- """
505
- 미래 예측치를 레디스 캐시를 이용하여 반환함
506
- :param refresh:
507
- :param num: 앙상블 반복횟수
508
- :return:
509
- """
510
- print("**** Start get_final_predictions... ****")
511
- redis_name = f'{self.code}_mylstm_predictions'
512
-
513
- print(
514
- f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time_h}h")
515
-
516
- def fetch_final_predictions(num_in) -> tuple:
517
- """
518
- 앙상블법으로 딥러닝을 모델을 반복해서 평균을 내서 미래를 예측한다. 평가는 래시스 캐시로 반환하기 어려워 일단 디버그 용도로만 사용하기로
519
- :param num_in:
520
- :return:
521
- """
522
- # 앙상블 테스트와 채점
523
- try:
524
- _, _, ensemble_future_predictions_2d = self.ensemble_training(
525
- num=num_in)
526
- except IndexError:
527
- return [], []
528
-
529
- """if grading:
530
- lstm_grade = self.grading(ensemble_train_predictions_2d, ensemble_test_predictions_2d)
531
- else:
532
- lstm_grade = None"""
533
-
534
- # 시각화를 위한 준비 - 날짜 생성 (미래 예측 날짜), 미래예측값 평균
535
- last_date = self.raw_data.index[-1]
536
- future_dates = pd.date_range(last_date, periods=self.future_days + 1).tolist()[1:]
537
-
538
- # Timestamp 객체를 문자열로 변환
539
- future_dates_str= [date.strftime('%Y-%m-%d') for date in future_dates]
540
-
541
- final_future_predictions = np.mean(ensemble_future_predictions_2d, axis=0)
542
- tsa_logger.info(f'num - future dates : {len(future_dates_str)} future data : {len(final_future_predictions)}')
543
-
544
- assert len(future_dates_str) == len(final_future_predictions), "future_dates 와 final_future_predictions 개수가 일치하지 않습니다."
545
-
546
- return future_dates_str, final_future_predictions.tolist()
547
-
548
- future_dates_str, final_future_predictions = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time_h * 3600)
549
-
550
- # 문자열을 날짜 형식으로 변환
551
- future_dates = [datetime.strptime(date, '%Y-%m-%d') for date in future_dates_str]
552
-
553
- # 리스트를 다시 NumPy 배열로 변환
554
- final_future_predictions = np.array(final_future_predictions)
555
-
556
- return future_dates, final_future_predictions
557
-
558
- def export(self, refresh=False, expire_time_h=24, to="str") -> Optional[str]:
559
- """
560
- prophet과 plotly로 그래프를 그려서 html을 문자열로 반환
561
- :param refresh:
562
- :param to: str, htmlfile, png
563
- :return:
564
- """
565
- future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh, expire_time_h=expire_time_h)
566
- final_future_predictions = final_future_predictions.reshape(-1) # 차원을 하나 줄인다.
567
-
568
- # 데이터 준비
569
- self.raw_data = self.raw_data.reset_index()
570
- data = self.raw_data[['Date', 'Close']][-120:].reset_index(drop=True)
571
-
572
- # 'Date'와 'Close' 열 추출
573
- actual_dates = pd.to_datetime(data['Date'])
574
- actual_close = data['Close']
575
-
576
- # 'actual_close'가 Series인지 확인
577
- if isinstance(actual_close, pd.DataFrame):
578
- actual_close = actual_close.squeeze()
579
-
580
- # 'Close' 열의 데이터 타입 확인
581
- actual_close = actual_close.astype(float)
582
-
583
- # 예측 데이터 준비
584
- predicted_dates = pd.to_datetime(future_dates)
585
- predicted_close = pd.Series(final_future_predictions, index=range(len(final_future_predictions))).astype(float)
586
-
587
- # 그래프 생성
588
- fig = go.Figure()
589
-
590
- # 실제 데이터 추가
591
- fig.add_trace(go.Scatter(
592
- x=actual_dates,
593
- y=actual_close,
594
- mode='markers',
595
- name='실제주가'
596
- ))
597
-
598
- # 예측 데이터 추가
599
- fig.add_trace(go.Scatter(
600
- x=predicted_dates,
601
- y=predicted_close,
602
- mode='lines+markers',
603
- name='예측치(30일)'
604
- ))
605
-
606
- # 레이아웃 업데이트
607
- fig.update_layout(
608
- xaxis_title='일자',
609
- yaxis_title='주가(원)',
610
- xaxis=dict(
611
- tickformat='%Y/%m',
612
- ),
613
- yaxis=dict(
614
- tickformat=".0f",
615
- ),
616
- showlegend=True,
617
- )
618
-
619
- tsa_logger.debug(f"actual_dates({len(actual_dates)}) - {actual_dates}")
620
- tsa_logger.debug(f"actual_close({len(actual_close)} - {actual_close}")
621
- tsa_logger.debug(f"predicted_dates({len(future_dates)}) - {future_dates}")
622
- tsa_logger.debug(f"predicted_close({len(predicted_close)}) - {predicted_close}")
623
-
624
- fig.update_layout(
625
- # title=f'{self.code} {self.name} 주가 예측 그래프(prophet)',
626
- xaxis_title='일자',
627
- yaxis_title='주가(원)',
628
- xaxis = dict(
629
- tickformat='%Y/%m', # X축을 '연/월' 형식으로 표시
630
- ),
631
- yaxis = dict(
632
- tickformat=".0f", # 소수점 없이 원래 숫자 표시
633
- ),
634
- showlegend=False,
635
- )
636
-
637
- if to == 'str':
638
- # 그래프 HTML로 변환 (string 형식으로 저장)
639
- graph_html = plot(fig, output_type='div')
640
- return graph_html
641
- elif to == 'png':
642
- # 그래프를 PNG 파일로 저장
643
- fig.write_image(f"myLSTM_{self.code}.png")
644
- return None
645
- elif to == 'htmlfile':
646
- # 그래프를 HTML로 저장
647
- plot(fig, filename=f'myLSTM_{self.code}.html', auto_open=False)
648
- return None
649
- else:
650
- Exception("to 인자가 맞지 않습니다.")
651
-
652
- def visualization(self, refresh=True):
653
- future_dates, final_future_predictions = self.get_final_predictions(refresh=refresh, expire_time_h=1)
654
-
655
- # 시각화1
656
- plt.figure(figsize=(10, 6))
657
-
658
- # 실제 주가
659
- plt.plot(self.raw_data.index, self.raw_data['Close'], label='Actual Price')
660
-
661
- # 미래 주가 예측
662
- plt.plot(future_dates, final_future_predictions, label='Future Predicted Price', linestyle='--')
663
-
664
- plt.xlabel('Date')
665
- plt.ylabel('Stock Price')
666
- plt.legend()
667
- plt.title('Apple Stock Price Prediction with LSTM')
668
- plt.show()
669
-
670
- """# 시각화2
671
- plt.figure(figsize=(10, 6))
672
- 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')
673
- plt.plot(self.raw_data.index[self.lstm_data.train_size + 60:], lstm_grade.mean_test_predictions_2d, label='Predicted Price')
674
- plt.xlabel('Date')
675
- plt.ylabel('Price')
676
- plt.legend()
677
- plt.title('Stock Price Prediction with LSTM Ensemble')
678
- plt.show()"""
679
-
680
- def is_up(self)-> bool:
681
- """
682
- lstm 데이터가 증가하는 추세인지 확인후 참/거짓 반환
683
-
684
- Returns:
685
- bool: True if the data is strictly increasing, False otherwise.
686
- """
687
- # 튜플의 [0]은 날짜 [1]은 값 배열
688
- data = self.get_final_predictions(refresh=False, expire_time_h=24)[1]
689
- # 데이터를 1D 배열로 변환
690
- flattened_data = data.flatten()
691
- tsa_logger.debug(f"flattened_data : {flattened_data}")
692
- # 증가 여부 확인
693
- return all(flattened_data[i] < flattened_data[i + 1] for i in range(len(flattened_data) - 1))
694
-
695
- @staticmethod
696
- def caching_based_on_prophet_ranking(refresh: bool, expire_time_h: int, top=20):
697
- ranking_topn = MyProphet.ranking(refresh=False, top=top)
698
- tsa_logger.info(ranking_topn)
699
- mylstm = MyLSTM('005930')
700
- print(f"*** LSTM prediction redis cashing top{top} items ***")
701
- for i, (code, _) in enumerate(ranking_topn.items()):
702
- mylstm.code = code
703
- print(f"{i+1}. {mylstm.code}/{mylstm.name}")
704
- mylstm.get_final_predictions(refresh=refresh, expire_time_h=expire_time_h, num=5)
705
-
706
-
707
-
708
-