analyser_hj3415 3.0.4__tar.gz → 3.0.5__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/PKG-INFO +1 -1
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/__init__.py +1 -1
- analyser_hj3415-3.0.5/analyser_hj3415/analyser/compile.py +145 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/common.py +40 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/red.py +14 -45
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/lstm.py +79 -55
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/prophet.py +43 -16
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/cli.py +6 -6
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/pyproject.toml +1 -1
- analyser_hj3415-3.0.4/analyser_hj3415/analyser/score.py +0 -157
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/.gitignore +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/README.md +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/__init__.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/__init__.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/blue.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/growth.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/eval/mil.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/analyser/tsa/__init__.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/__init__.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/mysklearn.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/mysklearn2.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/score.py +0 -0
- {analyser_hj3415-3.0.4 → analyser_hj3415-3.0.5}/analyser_hj3415/workroom/trash.py +0 -0
@@ -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
|
|
@@ -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,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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|