analyser_hj3415 3.0.3__tar.gz → 3.0.5__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (23) hide show
  1. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/PKG-INFO +2 -2
  2. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/__init__.py +1 -1
  3. analyser_hj3415-3.0.5/analyser_hj3415/analyser/compile.py +145 -0
  4. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/common.py +40 -0
  5. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/red.py +14 -45
  6. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/lstm.py +79 -55
  7. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/prophet.py +43 -16
  8. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/cli.py +6 -6
  9. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/pyproject.toml +2 -2
  10. analyser_hj3415-3.0.3/analyser_hj3415/analyser/score.py +0 -164
  11. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/.gitignore +0 -0
  12. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/README.md +0 -0
  13. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/__init__.py +0 -0
  14. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/__init__.py +0 -0
  15. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/blue.py +0 -0
  16. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/growth.py +0 -0
  17. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/mil.py +0 -0
  18. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/__init__.py +0 -0
  19. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/__init__.py +0 -0
  20. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/mysklearn.py +0 -0
  21. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/mysklearn2.py +0 -0
  22. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/score.py +0 -0
  23. {analyser_hj3415-3.0.3 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/trash.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: analyser_hj3415
3
- Version: 3.0.3
3
+ Version: 3.0.5
4
4
  Summary: Stock analyser and database processing programs
5
5
  Requires-Python: >=3.6
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: utils-hj3415>=3.0.8
7
+ Requires-Dist: utils-hj3415>=3.0.10
8
8
  Requires-Dist: db-hj3415>=4.3.0
9
9
  Requires-Dist: scikit-learn>=1.5.2
10
10
  Requires-Dist: plotly>=5.24.1
@@ -8,6 +8,6 @@ if env_path is None:
8
8
  load_dotenv(env_path)
9
9
 
10
10
  from analyser_hj3415.analyser import eval
11
- from analyser_hj3415.analyser import score
11
+ from analyser_hj3415.analyser import compile
12
12
  from analyser_hj3415.analyser import tsa
13
13
 
@@ -0,0 +1,145 @@
1
+ import os
2
+ from collections import OrderedDict
3
+ from typing import Union
4
+
5
+ from db_hj3415 import myredis,mymongo
6
+ from utils_hj3415 import tools, setup_logger
7
+
8
+ from analyser_hj3415.analyser import tsa
9
+ from analyser_hj3415.analyser import eval
10
+
11
+ mylogger = setup_logger(__name__,'WARNING')
12
+ expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
13
+
14
+
15
+ class Compile:
16
+ def __init__(self, code: str, expect_earn=0.06):
17
+ assert tools.is_6digit(code), f'Invalid value : {code}'
18
+ self._code = code
19
+ self.name = mymongo.Corps.get_name(code)
20
+ self.red = eval.Red(code, expect_earn)
21
+ self.mil = eval.Mil(code)
22
+ self.prophet = tsa.MyProphet(code)
23
+
24
+ @property
25
+ def code(self) -> str:
26
+ return self._code
27
+
28
+ @code.setter
29
+ def code(self, code: str):
30
+ assert tools.is_6digit(code), f'Invalid value : {code}'
31
+ mylogger.info(f'change code : {self.code} -> {code}')
32
+ self._code = code
33
+ self.name = mymongo.Corps.get_name(code)
34
+ self.red.code = code
35
+ self.mil.code = code
36
+ self.prophet.code = code
37
+
38
+ def get(self, refresh=False) -> dict:
39
+ print(f"{self.code}/{self.name}의 compiling을 시작합니다.")
40
+ redis_name = self.code + '_compile_scores'
41
+ print(
42
+ f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
43
+
44
+ def fetch_compile_scores() -> dict:
45
+ mylogger.info("Red score 계산중..")
46
+ red_score = self.red.get(verbose=False).score
47
+
48
+ mylogger.info("Mil data 계산중..")
49
+ mil_data = self.mil.get(verbose=False)
50
+
51
+ mylogger.info("\tProphet 최근 데이터 조회중..")
52
+ trading_action, prophet_score = self.prophet.scoring()
53
+
54
+ return {
55
+ 'name': self.name,
56
+ 'red_score': red_score,
57
+ '이익지표': mil_data.이익지표,
58
+ '주주수익률': mil_data.주주수익률,
59
+ 'trading_action': trading_action,
60
+ 'prophet_score': prophet_score,
61
+ }
62
+ data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_compile_scores, timer=expire_time)
63
+ return data_dict
64
+
65
+ @staticmethod
66
+ def prophet_ranking(refresh=False, top: Union[int, str]='all') -> OrderedDict:
67
+
68
+ print("**** Start Compiling scores and sorting... ****")
69
+ redis_name = 'prophet_ranking'
70
+
71
+ print(
72
+ f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
73
+
74
+ def fetch_ranking() -> dict:
75
+ data = {}
76
+ c = Compile('005930')
77
+ for code in myredis.Corps.list_all_codes():
78
+ try:
79
+ c.code = code
80
+ except ValueError:
81
+ mylogger.error(f'prophet ranking error : {code}')
82
+ continue
83
+ scores= c.get(refresh=refresh)
84
+ print(f'{code} compiled : {scores}')
85
+ data[code] = scores
86
+ return data
87
+
88
+ data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, timer=expire_time)
89
+
90
+ # prophet_score를 기준으로 정렬
91
+ ranking = OrderedDict(sorted(data_dict.items(), key=lambda x: x[1]['prophet_score'], reverse=True))
92
+
93
+ if top == 'all':
94
+ return ranking
95
+ else:
96
+ if isinstance(top, int):
97
+ return OrderedDict(list(ranking.items())[:top])
98
+ else:
99
+ raise ValueError("top 인자는 'all' 이나 int형 이어야 합니다.")
100
+
101
+ @staticmethod
102
+ def analyse_lstm_topn(refresh: bool, top=40):
103
+ ranking_topn = Compile.prophet_ranking(refresh=False, top=top)
104
+ mylogger.info(ranking_topn)
105
+ mylstm = tsa.MyLSTM('005930')
106
+ print(f"*** LSTM prediction redis cashing top{top} items ***")
107
+ for i, (code, _) in enumerate(ranking_topn.items()):
108
+ mylstm.code = code
109
+ print(f"{i + 1}. {mylstm.code}/{mylstm.name}")
110
+ mylstm.initializing()
111
+ mylstm.get_final_predictions(refresh=refresh, num=5)
112
+
113
+ @staticmethod
114
+ def red_ranking(expect_earn: float = 0.06, refresh=False) -> OrderedDict:
115
+ # 이전 expect earn 과 비교하여 다르거나 없으면 강제 refresh 설정
116
+ redis_name = 'red_ranking_prev_expect_earn'
117
+ pee = tools.to_float(myredis.Base.get_value(redis_name))
118
+ if pee != expect_earn:
119
+ # expect earn의 이전 계산값이 없거나 이전 값과 다르면 새로 계산
120
+ mylogger.warning(
121
+ f"expect earn : {expect_earn} / prev expect earn : {pee} 두 값이 달라 refresh = True"
122
+ )
123
+ myredis.Base.set_value(redis_name, str(expect_earn))
124
+ refresh = True
125
+
126
+ print("**** Start red_ranking... ****")
127
+ redis_name = 'red_ranking'
128
+ print(
129
+ f"redisname: '{redis_name}' / expect_earn: {expect_earn} / refresh : {refresh} / expire_time : {expire_time / 3600}h")
130
+
131
+ def fetch_ranking(refresh_in: bool) -> dict:
132
+ data = {}
133
+ red = eval.Red(code='005930', expect_earn=expect_earn)
134
+ for i, code in enumerate(myredis.Corps.list_all_codes()):
135
+ red.code = code
136
+ red_score = red.get(refresh=refresh_in, verbose=False).score
137
+ if red_score > 0:
138
+ data[code] = red_score
139
+ print(f"{i}: {red} - {red_score}")
140
+ return data
141
+
142
+ data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, refresh, timer=expire_time)
143
+
144
+ return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
145
+
@@ -9,6 +9,46 @@ mylogger = setup_logger(__name__,'WARNING')
9
9
 
10
10
 
11
11
  class Tools:
12
+ @staticmethod
13
+ def sigmoid_score(deviation, a=1.0, b=2.0):
14
+ """
15
+ Calculates a normalized score using a sigmoid function based on the provided deviation value.
16
+
17
+ This method applies the sigmoid function to a logarithmically transformed deviation value
18
+ to map it to a range between 0 and 100. The shape of the sigmoid curve can be adjusted
19
+ with parameters `a` and `b`.
20
+
21
+ Parameters:
22
+ deviation (float): The deviation value to be transformed. Must be a non-negative value.
23
+ a (float): The steepness of the sigmoid curve. Default is 1.0.
24
+ b (float): The x-offset for the sigmoid curve. Default is 2.0.
25
+
26
+ Returns:
27
+ float: A score between 0 and 100 derived from the provided deviation value.
28
+ """
29
+ # 예: x = log10(deviation + 1)
30
+ x = math.log10(deviation + 1)
31
+ s = 1 / (1 + math.exp(-a * (x - b))) # 0~1 범위
32
+ return s * 100 # 0~100 범위
33
+
34
+ @staticmethod
35
+ def log_score(deviation):
36
+ """
37
+ Compute and return the logarithmic score scaled by a constant factor.
38
+
39
+ This method takes a numerical deviation value, adds one to it, computes its
40
+ base-10 logarithm, and then multiplies the result by a constant factor of 33
41
+ to scale the resulting logarithmic score.
42
+
43
+ Parameters:
44
+ deviation (float): The numerical deviation value to calculate the
45
+ logarithmic score for. Should be a non-negative number.
46
+
47
+ Returns:
48
+ float: The scaled logarithmic score computed based on the input deviation.
49
+ """
50
+ return math.log10(deviation + 1) * 33
51
+
12
52
  @staticmethod
13
53
  def cal_deviation(v1: float, v2: float) -> float:
14
54
  """
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  from dataclasses import dataclass, asdict
3
- from collections import OrderedDict
4
3
  from typing import Tuple
5
4
  import math
6
5
 
@@ -68,7 +67,7 @@ class RedData:
68
67
  발행주식수: int
69
68
 
70
69
  date: list
71
-
70
+ 주가: float
72
71
  red_price: float
73
72
  score: int
74
73
 
@@ -98,6 +97,7 @@ class Red:
98
97
  self.c103 = myredis.C103(code, 'c103재무상태표q')
99
98
 
100
99
  self.name = self.c101.get_name()
100
+ self.recent_price = tools.to_float(self.c101.get_recent()['주가'])
101
101
  self._code = code
102
102
 
103
103
  self.expect_earn = expect_earn
@@ -117,6 +117,7 @@ class Red:
117
117
  self.c103.code = code
118
118
 
119
119
  self.name = self.c101.get_name()
120
+ self.recent_price = tools.to_float(self.c101.get_recent()['주가'])
120
121
  self._code = code
121
122
 
122
123
  def _calc비유동부채(self, refresh: bool) -> Tuple[str, float]:
@@ -152,24 +153,23 @@ class Red:
152
153
  else:
153
154
  return d, 비유동부채
154
155
 
155
- def _score(self, red_price: int, refresh: bool) -> int:
156
+ def _score(self, red_price: int) -> int:
156
157
  """red price와 최근 주가의 괴리율 파악
157
158
 
158
159
  Returns:
159
160
  int : 주가와 red price 비교한 괴리율
160
161
  """
161
- try:
162
- recent_price = tools.to_float(self.c101.get_recent(refresh)['주가'])
163
- except KeyError:
162
+ if math.isnan(self.recent_price):
164
163
  return 0
165
164
 
166
- deviation = Tools.cal_deviation(recent_price, red_price)
167
- if red_price < 0 or (recent_price >= red_price):
168
- score = 0
169
- else:
170
- score = tools.to_int(math.log10(deviation + 1) * 33) # desmos그래프상 33이 제일 적당한듯(최대100점에 가깝게)
165
+ deviation = Tools.cal_deviation(self.recent_price, red_price)
171
166
 
172
- mylogger.debug(f"최근주가 : {recent_price} red가격 : {red_price} 괴리율 : {tools.to_int(deviation)} score : {score}")
167
+ score = tools.to_int(Tools.sigmoid_score(deviation))
168
+ #score = tools.to_int(Tools.log_score(deviation))
169
+ if self.recent_price >= red_price:
170
+ score = -score
171
+
172
+ mylogger.debug(f"최근주가 : {self.recent_price} red가격 : {red_price} 괴리율 : {tools.to_int(deviation)} score : {score}")
173
173
 
174
174
  return score
175
175
 
@@ -201,7 +201,7 @@ class Red:
201
201
  except (ZeroDivisionError, ValueError):
202
202
  red_price = math.nan
203
203
 
204
- score = self._score(red_price, refresh)
204
+ score = self._score(red_price)
205
205
 
206
206
  try:
207
207
  date_list = Tools.date_set(d1, d2, d3, d4)
@@ -224,6 +224,7 @@ class Red:
224
224
  발행주식수 = 발행주식수,
225
225
  date = date_list,
226
226
  red_price = red_price,
227
+ 주가 = self.recent_price,
227
228
  score = score,
228
229
  )
229
230
 
@@ -261,35 +262,3 @@ class Red:
261
262
 
262
263
  return RedData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time))
263
264
 
264
- @classmethod
265
- def ranking(cls, expect_earn: float = 0.06, refresh = False) -> OrderedDict:
266
- # 이전 expect earn 과 비교하여 다르거나 없으면 강제 refresh 설정
267
- redis_name = 'red_ranking_prev_expect_earn'
268
- pee = tools.to_float(myredis.Base.get_value(redis_name))
269
- if pee != expect_earn:
270
- # expect earn의 이전 계산값이 없거나 이전 값과 다르면 새로 계산
271
- mylogger.warning(
272
- f"expect earn : {expect_earn} / prev expect earn : {pee} 두 값이 달라 refresh = True"
273
- )
274
- myredis.Base.set_value(redis_name, str(expect_earn))
275
- refresh = True
276
-
277
- print("**** Start red_ranking... ****")
278
- redis_name = 'red_ranking'
279
- print(f"redisname: '{redis_name}' / expect_earn: {expect_earn} / refresh : {refresh} / expire_time : {expire_time/3600}h")
280
-
281
- def fetch_ranking(refresh_in: bool) -> dict:
282
- data = {}
283
- red = Red(code='005930', expect_earn=expect_earn)
284
- for i, code in enumerate(myredis.Corps.list_all_codes()):
285
- red.code = code
286
- red_score = red.get(refresh=refresh_in, verbose=False).score
287
- if red_score > 0:
288
- data[code] = red_score
289
- print(f"{i}: {red} - {red_score}")
290
- return data
291
-
292
- data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, refresh, timer=expire_time)
293
-
294
- return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
295
-
@@ -20,8 +20,6 @@ from dataclasses import dataclass
20
20
 
21
21
  from utils_hj3415 import tools, setup_logger
22
22
  from db_hj3415 import myredis
23
- from analyser_hj3415.analyser import score
24
-
25
23
 
26
24
  mylogger = setup_logger(__name__,'WARNING')
27
25
  expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
@@ -108,6 +106,9 @@ class MyLSTM:
108
106
 
109
107
  def initializing(self):
110
108
  """
109
+ LSTM 분석을 위해 데이터를 준비하는 과정
110
+ get_final_predictions(refresh=True)를 시행하기전에 반드시 먼저 실행해줘아 한다.
111
+
111
112
  Fetches stock price data for the last four years from Yahoo Finance and prepares
112
113
  it for use in an LSTM model by normalizing, splitting into training and testing datasets,
113
114
  and reshaping the data.
@@ -380,25 +381,30 @@ class MyLSTM:
380
381
 
381
382
  def get_final_predictions(self, refresh: bool, num=5) -> Tuple[dict, LSTMGrade]:
382
383
  """
383
- Fetches final predictions based on an ensemble method using deep learning models. This process averages the
384
- predictions to forecast future data, primarily for debugging purposes as it is challenging to utilize
385
- Redis cache for evaluation.
384
+ Fetch and process predictions for future data.
385
+
386
+ This function fetches predictions from a Redis cache or calculates predictions if the data is
387
+ not found or if a refresh is requested. Predictions are determined using an ensemble training
388
+ method which averages predictions to forecast future trends. Additionally, the function checks
389
+ and caches whether the predicted data demonstrates an increasing trend over time.
390
+
391
+ Attributes:
392
+ redis_name (str): f'{self.code}_mylstm_predictions'
393
+ future_data (dict): Dictionary containing dates (as keys) and predicted prices (as values).
394
+ lstm_grade (LSTMGrade): Performance evaluation grade for the LSTM prediction model.
386
395
 
387
- Parameters:
388
- refresh (bool): Flag to indicate whether to refresh cached data.
389
- num (int, optional): Number of iterations for predictions. Defaults to 5.
396
+ Args:
397
+ refresh (bool): If True, forces recalculation and cache refresh of predictions.
398
+ num (int): Number of times to repeat ensemble training for more consistent predictions.
399
+ Defaults to 5.
390
400
 
391
401
  Returns:
392
- Tuple[dict, LSTMGrade]: A tuple containing a dictionary of future predictions and their corresponding
393
- evaluation grade.
402
+ Tuple[dict, LSTMGrade]: A tuple containing a dictionary of future date-price pairs and the
403
+ evaluation grade of the LSTM prediction model.
394
404
 
395
405
  Raises:
396
- AssertionError: If the lengths of future dates and predicted values do not match.
397
-
398
- Notes:
399
- - This function integrates ensemble training and caching of predictive data.
400
- - The future prediction keys correspond to the dates in "YYYY-MM-DD" format.
401
- - Makes use of Redis for data retrieval and caching mechanisms.
406
+ AssertionError: Raised if the lengths of 'future_dates' and 'final_future_predictions' do
407
+ not match during the data preparation.
402
408
  """
403
409
  print("**** Start get_final_predictions... ****")
404
410
  redis_name = f'{self.code}_mylstm_predictions'
@@ -406,6 +412,51 @@ class MyLSTM:
406
412
  print(
407
413
  f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
408
414
 
415
+ def caching_is_lstm_up(future_data_in: dict):
416
+ """
417
+ 날짜(str)를 키, 수치(float)를 값으로 갖는 딕셔너리를
418
+ 선형회귀분석(최소제곱법)을 통해 추세가 우상향인지 판별.
419
+
420
+ Returns:
421
+ bool: 기울기가 양수이면 True, 아니면 False
422
+ """
423
+
424
+ print("**** Caching is_lstm_up ... ****")
425
+ redis_name = f'{self.code}_is_lstm_up'
426
+ print(f"redisname: '{redis_name}' / expire_time : {expire_time / 3600}h")
427
+
428
+
429
+ if not future_data_in:
430
+ # 데이터가 비어있으면 추세를 판단할 수 없음
431
+ return False
432
+
433
+ # 1) 날짜(키) 기준 오름차순 정렬
434
+ sorted_dates = sorted(future_data_in.keys())
435
+ values = [future_data_in[d] for d in sorted_dates]
436
+
437
+ # 2) x 축을 0,1,2... 형태로 부여 (날짜 간격을 동일하게 가정)
438
+ x = np.arange(len(values), dtype=float)
439
+ y = np.array(values, dtype=float)
440
+
441
+ # 3) 선형 회귀(최소제곱법)로 기울기(slope) 계산
442
+ x_mean = np.mean(x)
443
+ y_mean = np.mean(y)
444
+
445
+ # 분자: sum((xi - x_mean) * (yi - y_mean))
446
+ numerator = np.sum((x - x_mean) * (y - y_mean))
447
+ # 분모: sum((xi - x_mean)^2)
448
+ denominator = np.sum((x - x_mean) ** 2)
449
+
450
+ if denominator == 0:
451
+ # 데이터가 1개 이하인 경우 등
452
+ return False
453
+
454
+ slope = numerator / denominator
455
+
456
+ # 4) 기울기가 양수면 "우상향 추세"로 판별
457
+ is_up = slope > 0
458
+ myredis.Base.set_value(redis_name, is_up, expire_time)
459
+
409
460
  def fetch_final_predictions(num_in) -> tuple:
410
461
  """
411
462
  앙상블법으로 딥러닝을 모델을 반복해서 평균을 내서 미래를 예측한다. 평가는 래시스 캐시로 반환하기 어려워 일단 디버그 용도로만 사용하기로
@@ -443,7 +494,12 @@ class MyLSTM:
443
494
 
444
495
  return future_data, lstm_grade
445
496
 
446
- return myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time)
497
+ future_data, lstm_grade = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time)
498
+
499
+ # 증가 추세인지 아닌지 레디스 캐시에 저장
500
+ caching_is_lstm_up(future_data)
501
+
502
+ return future_data, lstm_grade
447
503
 
448
504
  def export(self, refresh=False, to="str", num=5) -> Optional[str]:
449
505
  """
@@ -621,49 +677,17 @@ class MyLSTM:
621
677
  plt.title('Stock Price Prediction with LSTM Ensemble')
622
678
  plt.show()"""
623
679
 
624
- def is_up(self) -> bool:
680
+ def is_lstm_up(self) -> bool:
625
681
  """
626
- Determines if the predicted data indicates an increasing trend.
682
+ Determines whether the LSTM model is active or not.
627
683
 
628
- This method evaluates the results of future predictions and checks if all the
629
- subsequent values in the prediction data increase compared to their predecessors.
684
+ This method checks the status of an LSTM model by querying a Redis
685
+ value identified by a specific code indicating LSTM activity.
630
686
 
631
687
  Returns:
632
- bool: True if all future predicted values increase in sequence, False otherwise.
633
-
634
- Raises:
635
- None: This method does not raise any exceptions.
636
- """
637
- # 튜플의 [0]은 날짜 [1]은 값 배열
638
- future_data, _ = self.get_final_predictions(refresh=False)
639
- # 데이터를 1D 배열로 변환
640
- flattened_data = list(future_data.values())
641
- mylogger.debug(f"flattened_data : {flattened_data}")
642
- # 증가 여부 확인
643
- return all(flattened_data[i] < flattened_data[i + 1] for i in range(len(flattened_data) - 1))
644
-
645
- @staticmethod
646
- def caching_based_on_prophet_ranking(refresh: bool, top=20):
647
- """
648
- This method utilizes a ranking system generated by the Score class for
649
- predictive caching.
650
-
651
- Parameters
652
- ----------
653
- refresh : bool
654
- Whether to refresh the predictions for the selected items.
655
- top : int, optional
656
- The number of top-ranked items to process, by default 20.
688
+ bool: True if the LSTM model is active (up), False otherwise.
657
689
  """
658
- ranking_topn = score.Score.ranking(refresh=False, top=top)
659
- mylogger.info(ranking_topn)
660
- mylstm = MyLSTM('005930')
661
- print(f"*** LSTM prediction redis cashing top{top} items ***")
662
- for i, (code, _) in enumerate(ranking_topn.items()):
663
- mylstm.code = code
664
- print(f"{i + 1}. {mylstm.code}/{mylstm.name}")
665
- mylstm.initializing()
666
- mylstm.get_final_predictions(refresh=refresh, num=5)
690
+ return myredis.Base.get_value(f'{self.code}_is_lstm_up')
667
691
 
668
692
 
669
693
 
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime, timedelta
2
- from typing import Optional
2
+ from typing import Optional, Tuple
3
3
  import yfinance as yf
4
4
  import pandas as pd
5
5
  from prophet import Prophet
@@ -177,31 +177,58 @@ class MyProphet:
177
177
  else:
178
178
  Exception("to 인자가 맞지 않습니다.")
179
179
 
180
- def scoring(self) -> int:
180
+ def scoring(self) -> Tuple[str, int]:
181
181
  """
182
- prophet의 yhat_lower 예측치와 주가를 비교하여 주가가 낮으면 양의 점수를 높으면 음의 점수를 준다.
182
+ Calculate and return a trading action and associated score based on recent market data and forecasted values.
183
+
184
+ The method computes deviations between the recent price and predefined forecasted bounds (`yhat_lower` and `yhat_upper`)
185
+ to determine whether to buy, sell, or hold a financial instrument. The calculation involves generating a deviation score
186
+ that is transformed using a sigmoid function. The scores are then adjusted based on the position of the recent price relative
187
+ to the forecasted bounds. Logging is used extensively throughout the method to record the values and decisions for debugging
188
+ and traceability purposes.
183
189
 
184
190
  Returns:
185
- int: The calculated score based on the deviation between the recent price
186
- and the expected lower limit.
191
+ A tuple containing a string indicating the recommended trading action ('buy', 'sell', 'hold')
192
+ and an integer score associated with that action.
187
193
 
188
194
  Raises:
189
- AttributeError: Raised if the necessary attributes like `df_real` or methods like `get_yhat`
190
- are not correctly set or implemented.
191
- KeyError: Raised if the expected keys (`'yhat_lower'` or `'y'`) are not found in the data involved.
192
- ValueError: Raised if the format of data does not conform to expected structure for calculations.
195
+ KeyError: If required keys are missing in the `yhat_dict` or `last_real_data` dictionary-like structures.
193
196
  """
194
197
  last_real_data = self.df_real.iloc[-1]
195
198
  recent_price = last_real_data['y']
196
199
  recent_date = datetime.strftime(last_real_data['ds'], '%Y-%m-%d')
197
200
  yhat_dict = self.get_yhat()
198
201
  mylogger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
199
- yhat_lower = int(yhat_dict['yhat_lower'])
200
- deviation = int(eval.Tools.cal_deviation(recent_price, yhat_lower))
201
- if recent_price > yhat_lower:
202
- score = -deviation
202
+
203
+ yhat_lower = tools.to_int(yhat_dict['yhat_lower'])
204
+ yhat_upper = tools.to_int(yhat_dict['yhat_upper'])
205
+ #yhat = tools.to_int(yhat_dict['yhat'])
206
+
207
+ buying_deviation = eval.Tools.cal_deviation(recent_price, yhat_lower)
208
+
209
+ buying_score = tools.to_int(eval.Tools.sigmoid_score(buying_deviation))
210
+ # score = tools.to_int(Tools.log_score(deviation))
211
+
212
+ if recent_price >= yhat_lower:
213
+ buying_score = -buying_score
214
+
215
+ selling_deviation = eval.Tools.cal_deviation(recent_price, yhat_upper)
216
+
217
+ selling_score = tools.to_int(eval.Tools.sigmoid_score(selling_deviation))
218
+ # score = tools.to_int(Tools.log_score(deviation))
219
+
220
+ if recent_price <= yhat_upper:
221
+ selling_score = -selling_score
222
+
223
+ mylogger.info(f"{self.code}/{self.name} date: {recent_date} 가격: {recent_price}"
224
+ f" yhat_lower:{yhat_lower} yhat_upper:{yhat_upper}"
225
+ f" buying_score:{buying_score} selling_score:{selling_score}")
226
+
227
+ if buying_score > 0:
228
+ return 'buy', buying_score
229
+ elif selling_score > 0:
230
+ return 'sell', selling_score
203
231
  else:
204
- score = deviation
205
- mylogger.info(f"{self.code}/{self.name} date: {recent_date} 가격:{recent_price} 기대하한값:{yhat_lower} 편차:{deviation} score:{score}")
206
- return score
232
+ return 'hold', 0
233
+
207
234
 
@@ -2,7 +2,7 @@ import argparse
2
2
  import pprint
3
3
 
4
4
  from utils_hj3415 import tools
5
- from analyser_hj3415.analyser import eval, tsa, score
5
+ from analyser_hj3415.analyser import eval, tsa, compile
6
6
  from db_hj3415 import myredis, mymongo
7
7
 
8
8
 
@@ -92,9 +92,9 @@ def analyser_manager():
92
92
  elif args.command == 'ranking':
93
93
  mymongo.Logs.save('cli','INFO', 'run >> analyser red ranking')
94
94
  if args.expect_earn is None:
95
- result = eval.Red.ranking(refresh=args.refresh)
95
+ result = compile.Compile.red_ranking(refresh=args.refresh)
96
96
  else:
97
- result = eval.Red.ranking(expect_earn=args.expect_earn, refresh=args.refresh)
97
+ result = compile.Compile.red_ranking(expect_earn=args.expect_earn, refresh=args.refresh)
98
98
  print(result)
99
99
 
100
100
  elif args.type == 'mil':
@@ -147,16 +147,16 @@ def analyser_manager():
147
147
 
148
148
  elif args.type == 'prophet':
149
149
  if args.command == 'ranking':
150
- result = score.Score.ranking(refresh=args.refresh)
150
+ result = compile.Compile.prophet_ranking(refresh=args.refresh)
151
151
  print(result)
152
152
  mymongo.Logs.save('cli','INFO', 'run >> analyser prophet ranking')
153
153
 
154
154
  elif args.type == 'lstm':
155
155
  if args.command == 'caching':
156
156
  if args.top:
157
- tsa.MyLSTM.caching_based_on_prophet_ranking(refresh=args.refresh, top=args.top)
157
+ compile.Compile.analyse_lstm_topn(refresh=args.refresh, top=args.top)
158
158
  else:
159
- tsa.MyLSTM.caching_based_on_prophet_ranking(refresh=args.refresh)
159
+ compile.Compile.analyse_lstm_topn(refresh=args.refresh)
160
160
  mymongo.Logs.save('cli','INFO', f'run >> analyser lstm caching / top={args.top if args.top else 20}')
161
161
  elif args.command == 'get':
162
162
  assert tools.is_6digit(args.code), "code 인자는 6자리 숫자이어야 합니다."
@@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi"
5
5
 
6
6
  [project]
7
7
  name = "analyser_hj3415"
8
- version = "3.0.3"
8
+ version = "3.0.5"
9
9
  description = "Stock analyser and database processing programs"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.6"
@@ -20,7 +20,7 @@ requires-python = ">=3.6"
20
20
  # "Operating System :: OS Independent"
21
21
  #]
22
22
  dependencies = [
23
- "utils-hj3415>=3.0.8",
23
+ "utils-hj3415>=3.0.10",
24
24
  "db-hj3415>=4.3.0",
25
25
  "scikit-learn>=1.5.2",
26
26
  "plotly>=5.24.1",
@@ -1,164 +0,0 @@
1
- import os
2
- import datetime
3
- from collections import OrderedDict
4
-
5
- from db_hj3415 import myredis
6
- from utils_hj3415 import tools, setup_logger
7
-
8
- from analyser_hj3415.analyser import tsa
9
- from analyser_hj3415.analyser import eval
10
-
11
- mylogger = setup_logger(__name__,'WARNING')
12
- expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
13
-
14
-
15
- def is_within_last_three_days(date_to_check: datetime.datetime.date) -> bool:
16
- today = datetime.datetime.now().date() # 현재 날짜 (시간은 무시)
17
- # print('today - ', today)
18
- three_days_ago = today - datetime.timedelta(days=3) # 3일 전 날짜
19
- return three_days_ago <= date_to_check <= today
20
-
21
-
22
- class Score:
23
- def __init__(self, code):
24
- self._code = code
25
- self.c101 = myredis.C101(code)
26
- self.name = self.c101.get_name()
27
- self.c108 = myredis.C108(code)
28
- self.dart = myredis.Dart(code)
29
- self.red = eval.Red(code)
30
- self.mil = eval.Mil(code)
31
- self.lstm = tsa.MyLSTM(code)
32
- self.prophet = tsa.MyProphet(code)
33
-
34
- @property
35
- def code(self) -> str:
36
- return self._code
37
-
38
- @code.setter
39
- def code(self, code: str):
40
- assert tools.is_6digit(code), f'Invalid value : {code}'
41
- mylogger.info(f'change code : {self.code} -> {code}')
42
- self._code = code
43
- self.c101.code = code
44
- self.name = self.c101.get_name()
45
- self.c108.code = code
46
- self.dart.code = code
47
- self.red.code = code
48
- self.mil.code = code
49
- self.lstm.code = code
50
- self.prophet.code = code
51
-
52
- def get(self, refresh=False) -> dict:
53
- """
54
- 한 종목의 각분야 평가를 모아서 딕셔너리 형태로 반환함.
55
- redis_name = self.code + '_score'
56
-
57
- Returns:
58
- dict: A dictionary containing the following key-value pairs:
59
- - 'name': str - 종목명
60
- - '시가총액': str - 시가총액
61
- - 'is_update_c108': bool - 최근 3일 이내에 c108이 없데이트 되었는가
62
- - 'red_score': float - Red score
63
- - '이익지표': float - Mil의 이익지표
64
- - '주주수익률': float - Mil의 주주수익률
65
- - 'is_lstm_up': Union[bool, None] - lstm 예측치가 상승인지 아닌지, returns None - 데이터가 없으면..
66
- - 'prophet_score': int - prophet score
67
- """
68
- print(f"{self.code}/{self.name}의 scoring을 시작합니다.")
69
- redis_name = self.code + '_score'
70
- print(
71
- f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
72
-
73
- def fetch_score() -> dict:
74
- mylogger.info("시가총액 데이터 추출중..")
75
- 시가총액 = tools.format_large_number(int(self.c101.get_recent()['시가총액']))
76
-
77
- mylogger.info("C108 최근 데이터 추출중..")
78
- # c108이 최근에 업데이트 되었는지...
79
- c108_recent_date = self.c108.get_recent_date()
80
- # print('code - ', code, ' | c108 recent date - ', c108_recent_date.date())
81
- if c108_recent_date is None:
82
- is_update_c108 = False
83
- else:
84
- is_update_c108 = is_within_last_three_days(c108_recent_date.date())
85
-
86
- mylogger.info("Red score 계산중..")
87
- red_score = self.red.get(verbose=False).score
88
-
89
- mylogger.info("Mil data 계산중..")
90
- mil_data = self.mil.get(verbose=False)
91
-
92
- mylogger.info("Lstm 최근 데이터 조회중..")
93
- if myredis.Base.exists(f'{self.code}_mylstm_predictions'):
94
- is_lstm_up = self.lstm.is_up()
95
- else:
96
- is_lstm_up = None
97
-
98
- mylogger.info("\tProphet 최근 데이터 조회중..")
99
- prophet_score = self.prophet.scoring()
100
-
101
- return {
102
- 'name': self.name,
103
- '시가총액': 시가총액,
104
- 'is_update_c108': is_update_c108,
105
- 'red_score': red_score,
106
- '이익지표': mil_data.이익지표,
107
- '주주수익률': mil_data.주주수익률,
108
- 'is_lstm_up': is_lstm_up,
109
- 'prophet_score': prophet_score,
110
- }
111
- data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_score, timer=expire_time)
112
- return data_dict
113
-
114
- @classmethod
115
- def ranking(self, refresh=False, top='all') -> OrderedDict:
116
- """
117
- prophet score 기준으로 정렬하여 ordered dict로 반환함
118
-
119
- Parameters:
120
- refresh (bool): Specifies whether to refresh the ranking data. Defaults
121
- to `False`.
122
- top (Union[str, int]): Determines how many top rankings to return.
123
- Defaults to `'all'`. If an integer is provided, it limits the
124
- ranking to the specified count.
125
-
126
- Returns:
127
- OrderedDict: A dictionary containing the rankings, sorted in
128
- descending order by `prophet_score`.
129
-
130
- Raises:
131
- ValueError: Raised if the parameter `top` is neither `'all'` nor an
132
- integer.
133
- """
134
- print("**** Start score_ranking... ****")
135
- redis_name = 'score_ranking'
136
-
137
- print(
138
- f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
139
-
140
- def fetch_ranking() -> dict:
141
- data = {}
142
- s = Score('005930')
143
- for code in myredis.Corps.list_all_codes():
144
- try:
145
- s.code = code
146
- except ValueError:
147
- mylogger.error(f'score ranking error : {code}')
148
- continue
149
- score = s.get(refresh=refresh)
150
- data[code] = score
151
- return data
152
-
153
- data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, timer=expire_time)
154
-
155
- # prophet_score를 기준으로 정렬
156
- ranking = OrderedDict(sorted(data_dict.items(), key=lambda x: x[1]['prophet_score'], reverse=True))
157
-
158
- if top == 'all':
159
- return ranking
160
- else:
161
- if isinstance(top, int):
162
- return OrderedDict(list(ranking.items())[:top])
163
- else:
164
- raise ValueError("top 인자는 'all' 이나 int형 이어야 합니다.")