analyser_hj3415 2.10.5__py3-none-any.whl → 3.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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
-