analyser_hj3415 3.0.4__py3-none-any.whl → 3.0.5__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/__init__.py +1 -1
- analyser_hj3415/analyser/compile.py +145 -0
- analyser_hj3415/analyser/eval/common.py +40 -0
- analyser_hj3415/analyser/eval/red.py +14 -45
- analyser_hj3415/analyser/tsa/lstm.py +79 -55
- analyser_hj3415/analyser/tsa/prophet.py +43 -16
- analyser_hj3415/cli.py +6 -6
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.0.5.dist-info}/METADATA +1 -1
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.0.5.dist-info}/RECORD +11 -11
- analyser_hj3415/analyser/score.py +0 -157
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.0.5.dist-info}/WHEEL +0 -0
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.0.5.dist-info}/entry_points.txt +0 -0
analyser_hj3415/__init__.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
384
|
-
|
385
|
-
Redis cache
|
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
|
-
|
388
|
-
refresh (bool):
|
389
|
-
num (int
|
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
|
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:
|
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
|
-
|
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
|
680
|
+
def is_lstm_up(self) -> bool:
|
625
681
|
"""
|
626
|
-
Determines
|
682
|
+
Determines whether the LSTM model is active or not.
|
627
683
|
|
628
|
-
This method
|
629
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
186
|
-
and
|
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
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
205
|
-
|
206
|
-
return score
|
232
|
+
return 'hold', 0
|
233
|
+
|
207
234
|
|
analyser_hj3415/cli.py
CHANGED
@@ -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,
|
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 =
|
95
|
+
result = compile.Compile.red_ranking(refresh=args.refresh)
|
96
96
|
else:
|
97
|
-
result =
|
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 =
|
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
|
-
|
157
|
+
compile.Compile.analyse_lstm_topn(refresh=args.refresh, top=args.top)
|
158
158
|
else:
|
159
|
-
|
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자리 숫자이어야 합니다."
|
@@ -1,22 +1,22 @@
|
|
1
|
-
analyser_hj3415/__init__.py,sha256=
|
2
|
-
analyser_hj3415/cli.py,sha256=
|
1
|
+
analyser_hj3415/__init__.py,sha256=f2E9Neh7Nzkhvdj7HWWlgxZK2sB95rBtaIgWEHzxK9E,450
|
2
|
+
analyser_hj3415/cli.py,sha256=s3dsT4T3xqIP4D7FYMPCVhRP20WxZzAb0znR2P6UyYA,9264
|
3
3
|
analyser_hj3415/analyser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
analyser_hj3415/analyser/
|
4
|
+
analyser_hj3415/analyser/compile.py,sha256=JNDJNDV1MbHEqx2TzmBY7skJ_AFqOE6xIqx_yx-M-H8,5741
|
5
5
|
analyser_hj3415/analyser/eval/__init__.py,sha256=IP1d0Q3nOCAD3zK1qxrC685MkJQfUh-qaXc7xptTxk8,80
|
6
6
|
analyser_hj3415/analyser/eval/blue.py,sha256=fq_eln7-EdwICNQ2sMXAfZKXGhQ29EaJwsGvn7xA29U,7632
|
7
|
-
analyser_hj3415/analyser/eval/common.py,sha256=
|
7
|
+
analyser_hj3415/analyser/eval/common.py,sha256=wcWJg_jNgr02i1U62oZGgLt2iscdle9X-u4aMnZl89Q,14127
|
8
8
|
analyser_hj3415/analyser/eval/growth.py,sha256=OXuBuPzVw3q6kpelNigiX7xvJNUP1gv26DNetc4aews,3315
|
9
9
|
analyser_hj3415/analyser/eval/mil.py,sha256=wN1jPu5zLJjoDk3kL7piL-4jWhpsAMeQpsTZll0a2dw,10568
|
10
|
-
analyser_hj3415/analyser/eval/red.py,sha256=
|
10
|
+
analyser_hj3415/analyser/eval/red.py,sha256=rJbidKlo2s2EJHBGn4HDbfCDcEGP5Rst8iQeJ6jlzNA,10988
|
11
11
|
analyser_hj3415/analyser/tsa/__init__.py,sha256=rPnn4b8aEym2O9l_sAaIRUpOeXJw6sDj2XfUzy-mpvg,42
|
12
|
-
analyser_hj3415/analyser/tsa/lstm.py,sha256=
|
13
|
-
analyser_hj3415/analyser/tsa/prophet.py,sha256=
|
12
|
+
analyser_hj3415/analyser/tsa/lstm.py,sha256=nyjWP763-DahraD_fT3Spb-B9HQbCHy-b8Pnhkb3kz0,29283
|
13
|
+
analyser_hj3415/analyser/tsa/prophet.py,sha256=bWyogx3CiJ-rmghQejI8njZZ7yNT0JsHkXdXuI7jH2U,9121
|
14
14
|
analyser_hj3415/workroom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
analyser_hj3415/workroom/mysklearn.py,sha256=wJXKz5MqqTzADdG2mqRMMzc_G9RzwYjj5_j4gyOopxQ,2030
|
16
16
|
analyser_hj3415/workroom/mysklearn2.py,sha256=1lIy6EWEQHkOzDS-av8U0zQH6DuCLKWMI73dnJx5KRs,1495
|
17
17
|
analyser_hj3415/workroom/score.py,sha256=P6nHBJYmyhigGtT4qna4BmNtvt4B93b7SKyzdstJK24,17376
|
18
18
|
analyser_hj3415/workroom/trash.py,sha256=zF-W0piqkGr66UP6-iybo9EXh2gO0RP6R1FnIpsGkl8,12262
|
19
|
-
analyser_hj3415-3.0.
|
20
|
-
analyser_hj3415-3.0.
|
21
|
-
analyser_hj3415-3.0.
|
22
|
-
analyser_hj3415-3.0.
|
19
|
+
analyser_hj3415-3.0.5.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
|
20
|
+
analyser_hj3415-3.0.5.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
21
|
+
analyser_hj3415-3.0.5.dist-info/METADATA,sha256=HiSGPw-FrG2eKOIpKcEUJ71Ao_hL3LivsoAABdTe4Zw,6777
|
22
|
+
analyser_hj3415-3.0.5.dist-info/RECORD,,
|
@@ -1,157 +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
|
-
class Score:
|
16
|
-
def __init__(self, code):
|
17
|
-
self._code = code
|
18
|
-
self.c101 = myredis.C101(code)
|
19
|
-
self.name = self.c101.get_name()
|
20
|
-
self.c108 = myredis.C108(code)
|
21
|
-
self.dart = myredis.Dart(code)
|
22
|
-
self.red = eval.Red(code)
|
23
|
-
self.mil = eval.Mil(code)
|
24
|
-
self.lstm = tsa.MyLSTM(code)
|
25
|
-
self.prophet = tsa.MyProphet(code)
|
26
|
-
|
27
|
-
@property
|
28
|
-
def code(self) -> str:
|
29
|
-
return self._code
|
30
|
-
|
31
|
-
@code.setter
|
32
|
-
def code(self, code: str):
|
33
|
-
assert tools.is_6digit(code), f'Invalid value : {code}'
|
34
|
-
mylogger.info(f'change code : {self.code} -> {code}')
|
35
|
-
self._code = code
|
36
|
-
self.c101.code = code
|
37
|
-
self.name = self.c101.get_name()
|
38
|
-
self.c108.code = code
|
39
|
-
self.dart.code = code
|
40
|
-
self.red.code = code
|
41
|
-
self.mil.code = code
|
42
|
-
self.lstm.code = code
|
43
|
-
self.prophet.code = code
|
44
|
-
|
45
|
-
def get(self, refresh=False) -> dict:
|
46
|
-
"""
|
47
|
-
한 종목의 각분야 평가를 모아서 딕셔너리 형태로 반환함.
|
48
|
-
redis_name = self.code + '_score'
|
49
|
-
|
50
|
-
Returns:
|
51
|
-
dict: A dictionary containing the following key-value pairs:
|
52
|
-
- 'name': str - 종목명
|
53
|
-
- '시가총액': str - 시가총액
|
54
|
-
- 'is_update_c108': bool - 최근 3일 이내에 c108이 없데이트 되었는가
|
55
|
-
- 'red_score': float - Red score
|
56
|
-
- '이익지표': float - Mil의 이익지표
|
57
|
-
- '주주수익률': float - Mil의 주주수익률
|
58
|
-
- 'is_lstm_up': Union[bool, None] - lstm 예측치가 상승인지 아닌지, returns None - 데이터가 없으면..
|
59
|
-
- 'prophet_score': int - prophet score
|
60
|
-
"""
|
61
|
-
print(f"{self.code}/{self.name}의 scoring을 시작합니다.")
|
62
|
-
redis_name = self.code + '_score'
|
63
|
-
print(
|
64
|
-
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
|
65
|
-
|
66
|
-
def fetch_score() -> dict:
|
67
|
-
mylogger.info("시가총액 데이터 추출중..")
|
68
|
-
시가총액 = tools.format_large_number(int(self.c101.get_recent()['시가총액']))
|
69
|
-
|
70
|
-
mylogger.info("C108 최근 데이터 추출중..")
|
71
|
-
# c108이 최근에 업데이트 되었는지...
|
72
|
-
c108_recent_date = self.c108.get_recent_date()
|
73
|
-
# print('code - ', code, ' | c108 recent date - ', c108_recent_date.date())
|
74
|
-
if c108_recent_date is None:
|
75
|
-
is_update_c108 = False
|
76
|
-
else:
|
77
|
-
is_update_c108 = tools.is_within_last_n_days(c108_recent_date,3)
|
78
|
-
|
79
|
-
mylogger.info("Red score 계산중..")
|
80
|
-
red_score = self.red.get(verbose=False).score
|
81
|
-
|
82
|
-
mylogger.info("Mil data 계산중..")
|
83
|
-
mil_data = self.mil.get(verbose=False)
|
84
|
-
|
85
|
-
mylogger.info("Lstm 최근 데이터 조회중..")
|
86
|
-
if myredis.Base.exists(f'{self.code}_mylstm_predictions'):
|
87
|
-
is_lstm_up = self.lstm.is_up()
|
88
|
-
else:
|
89
|
-
is_lstm_up = None
|
90
|
-
|
91
|
-
mylogger.info("\tProphet 최근 데이터 조회중..")
|
92
|
-
prophet_score = self.prophet.scoring()
|
93
|
-
|
94
|
-
return {
|
95
|
-
'name': self.name,
|
96
|
-
'시가총액': 시가총액,
|
97
|
-
'is_update_c108': is_update_c108,
|
98
|
-
'red_score': red_score,
|
99
|
-
'이익지표': mil_data.이익지표,
|
100
|
-
'주주수익률': mil_data.주주수익률,
|
101
|
-
'is_lstm_up': is_lstm_up,
|
102
|
-
'prophet_score': prophet_score,
|
103
|
-
}
|
104
|
-
data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_score, timer=expire_time)
|
105
|
-
return data_dict
|
106
|
-
|
107
|
-
@classmethod
|
108
|
-
def ranking(self, refresh=False, top='all') -> OrderedDict:
|
109
|
-
"""
|
110
|
-
prophet score 기준으로 정렬하여 ordered dict로 반환함
|
111
|
-
|
112
|
-
Parameters:
|
113
|
-
refresh (bool): Specifies whether to refresh the ranking data. Defaults
|
114
|
-
to `False`.
|
115
|
-
top (Union[str, int]): Determines how many top rankings to return.
|
116
|
-
Defaults to `'all'`. If an integer is provided, it limits the
|
117
|
-
ranking to the specified count.
|
118
|
-
|
119
|
-
Returns:
|
120
|
-
OrderedDict: A dictionary containing the rankings, sorted in
|
121
|
-
descending order by `prophet_score`.
|
122
|
-
|
123
|
-
Raises:
|
124
|
-
ValueError: Raised if the parameter `top` is neither `'all'` nor an
|
125
|
-
integer.
|
126
|
-
"""
|
127
|
-
print("**** Start score_ranking... ****")
|
128
|
-
redis_name = 'score_ranking'
|
129
|
-
|
130
|
-
print(
|
131
|
-
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
|
132
|
-
|
133
|
-
def fetch_ranking() -> dict:
|
134
|
-
data = {}
|
135
|
-
s = Score('005930')
|
136
|
-
for code in myredis.Corps.list_all_codes():
|
137
|
-
try:
|
138
|
-
s.code = code
|
139
|
-
except ValueError:
|
140
|
-
mylogger.error(f'score ranking error : {code}')
|
141
|
-
continue
|
142
|
-
score = s.get(refresh=refresh)
|
143
|
-
data[code] = score
|
144
|
-
return data
|
145
|
-
|
146
|
-
data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, timer=expire_time)
|
147
|
-
|
148
|
-
# prophet_score를 기준으로 정렬
|
149
|
-
ranking = OrderedDict(sorted(data_dict.items(), key=lambda x: x[1]['prophet_score'], reverse=True))
|
150
|
-
|
151
|
-
if top == 'all':
|
152
|
-
return ranking
|
153
|
-
else:
|
154
|
-
if isinstance(top, int):
|
155
|
-
return OrderedDict(list(ranking.items())[:top])
|
156
|
-
else:
|
157
|
-
raise ValueError("top 인자는 'all' 이나 int형 이어야 합니다.")
|
File without changes
|
File without changes
|