analyser_hj3415 3.0.4__py3-none-any.whl → 3.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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/__init__.py +7 -2
- analyser_hj3415/analyser/tsa/lstm.py +134 -84
- analyser_hj3415/analyser/tsa/prophet.py +94 -32
- analyser_hj3415/cli.py +37 -16
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.1.0.dist-info}/METADATA +1 -1
- analyser_hj3415-3.1.0.dist-info/RECORD +22 -0
- analyser_hj3415/analyser/score.py +0 -157
- analyser_hj3415-3.0.4.dist-info/RECORD +0 -22
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.1.0.dist-info}/WHEEL +0 -0
- {analyser_hj3415-3.0.4.dist-info → analyser_hj3415-3.1.0.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.CorpProphet(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
|
+
corp_lstm = tsa.CorpLSTM('005930')
|
106
|
+
print(f"*** LSTM prediction redis cashing top{top} items ***")
|
107
|
+
for i, (code, _) in enumerate(ranking_topn.items()):
|
108
|
+
corp_lstm.code = code
|
109
|
+
print(f"{i + 1}. {corp_lstm.code}/{corp_lstm.name}")
|
110
|
+
corp_lstm.initializing()
|
111
|
+
corp_lstm.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,16 +20,15 @@ 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
23
|
|
25
24
|
|
26
|
-
mylogger = setup_logger(__name__,'
|
25
|
+
mylogger = setup_logger(__name__,'INFO')
|
27
26
|
expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
|
28
27
|
|
29
28
|
|
30
29
|
@dataclass
|
31
30
|
class LSTMData:
|
32
|
-
|
31
|
+
ticker: str
|
33
32
|
|
34
33
|
data_2d: np.ndarray
|
35
34
|
train_size: int
|
@@ -47,7 +46,7 @@ class LSTMGrade:
|
|
47
46
|
"""
|
48
47
|
딥러닝 모델의 학습 결과를 평가하기 위해 사용하는 데이터 클래스
|
49
48
|
"""
|
50
|
-
|
49
|
+
ticker: str
|
51
50
|
train_mse: float
|
52
51
|
train_mae: float
|
53
52
|
train_r2: float
|
@@ -57,20 +56,17 @@ class LSTMGrade:
|
|
57
56
|
|
58
57
|
|
59
58
|
class MyLSTM:
|
60
|
-
"""
|
61
|
-
LSTM(Long Short-Term Memory)
|
62
|
-
"""
|
63
59
|
# 미래 몇일을 예측할 것인가?
|
64
60
|
future_days = 30
|
65
61
|
|
66
|
-
def __init__(self,
|
67
|
-
|
68
|
-
self._code = code
|
69
|
-
self.name = myredis.Corps(code, 'c101').get_name()
|
62
|
+
def __init__(self, ticker: str):
|
63
|
+
mylogger.info(f'set up ticker : {ticker}')
|
70
64
|
self.scaler = MinMaxScaler(feature_range=(0, 1))
|
65
|
+
self._ticker = ticker
|
66
|
+
|
71
67
|
self.raw_data = pd.DataFrame()
|
72
68
|
self.lstm_data = LSTMData(
|
73
|
-
|
69
|
+
ticker=self.ticker,
|
74
70
|
data_2d=np.array([]),
|
75
71
|
train_size=0,
|
76
72
|
train_data_2d=np.array([]),
|
@@ -82,20 +78,18 @@ class MyLSTM:
|
|
82
78
|
)
|
83
79
|
|
84
80
|
@property
|
85
|
-
def
|
86
|
-
return self.
|
81
|
+
def ticker(self) -> str:
|
82
|
+
return self._ticker
|
87
83
|
|
88
|
-
@
|
89
|
-
def
|
90
|
-
|
91
|
-
mylogger.debug(f'change code : {self.code} -> {code}')
|
92
|
-
|
93
|
-
self._code = code
|
94
|
-
self.name = myredis.Corps(code, 'c101').get_name()
|
84
|
+
@ticker.setter
|
85
|
+
def ticker(self, ticker: str):
|
86
|
+
mylogger.info(f'change ticker : {self.ticker} -> {ticker}')
|
95
87
|
self.scaler = MinMaxScaler(feature_range=(0, 1))
|
88
|
+
self._ticker = ticker
|
89
|
+
|
96
90
|
self.raw_data = pd.DataFrame()
|
97
91
|
self.lstm_data = LSTMData(
|
98
|
-
|
92
|
+
ticker=self.ticker,
|
99
93
|
data_2d=np.array([]),
|
100
94
|
train_size=0,
|
101
95
|
train_data_2d=np.array([]),
|
@@ -108,6 +102,9 @@ class MyLSTM:
|
|
108
102
|
|
109
103
|
def initializing(self):
|
110
104
|
"""
|
105
|
+
LSTM 분석을 위해 데이터를 준비하는 과정
|
106
|
+
get_final_predictions(refresh=True)를 시행하기전에 반드시 먼저 실행해줘아 한다.
|
107
|
+
|
111
108
|
Fetches stock price data for the last four years from Yahoo Finance and prepares
|
112
109
|
it for use in an LSTM model by normalizing, splitting into training and testing datasets,
|
113
110
|
and reshaping the data.
|
@@ -126,7 +123,7 @@ class MyLSTM:
|
|
126
123
|
f"Get raw data from yfinance - start: {four_years_ago.strftime('%Y-%m-%d')}, end: {today.strftime('%Y-%m-%d')}")
|
127
124
|
|
128
125
|
df = yf.download(
|
129
|
-
self.
|
126
|
+
tickers=self.ticker,
|
130
127
|
start=four_years_ago.strftime('%Y-%m-%d'),
|
131
128
|
end=today.strftime('%Y-%m-%d')
|
132
129
|
)
|
@@ -174,7 +171,7 @@ class MyLSTM:
|
|
174
171
|
X_test_3d = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
|
175
172
|
except IndexError:
|
176
173
|
return LSTMData(
|
177
|
-
|
174
|
+
ticker=self.ticker,
|
178
175
|
data_2d=np.array([]),
|
179
176
|
train_size=0,
|
180
177
|
train_data_2d=np.array([]),
|
@@ -191,7 +188,7 @@ class MyLSTM:
|
|
191
188
|
f'len - X_train_3d : {len(X_train_3d)}, X_test_3d : {len(X_test_3d)}, y_train : {len(y_train_1d)}, y_test : {len(y_test_1d)}')
|
192
189
|
|
193
190
|
return LSTMData(
|
194
|
-
|
191
|
+
ticker=self.ticker,
|
195
192
|
data_2d=data_2d,
|
196
193
|
train_size=train_size,
|
197
194
|
train_data_2d=train_data_2d,
|
@@ -288,7 +285,7 @@ class MyLSTM:
|
|
288
285
|
if len(train_predictions) == 0 or len(test_predictions) == 0:
|
289
286
|
mylogger.warning("딥러닝 결과가 없어서 LSTMGrade 데이터를 비워서 반환합니다.")
|
290
287
|
return LSTMGrade(
|
291
|
-
|
288
|
+
ticker=self.ticker,
|
292
289
|
train_mse=float('nan'),
|
293
290
|
train_mae=float('nan'),
|
294
291
|
train_r2=float('nan'),
|
@@ -323,7 +320,7 @@ class MyLSTM:
|
|
323
320
|
# 과적합에 대한 평가는 train 과 test를 비교하여 test가 너무 않좋으면 과적합 의심.
|
324
321
|
|
325
322
|
return LSTMGrade(
|
326
|
-
|
323
|
+
ticker=self.ticker,
|
327
324
|
train_mse=train_mse,
|
328
325
|
train_mae=train_mae,
|
329
326
|
train_r2=train_r2,
|
@@ -380,32 +377,77 @@ class MyLSTM:
|
|
380
377
|
|
381
378
|
def get_final_predictions(self, refresh: bool, num=5) -> Tuple[dict, LSTMGrade]:
|
382
379
|
"""
|
383
|
-
|
384
|
-
|
385
|
-
Redis cache
|
380
|
+
Fetch and process predictions for future data.
|
381
|
+
|
382
|
+
This function fetches predictions from a Redis cache or calculates predictions if the data is
|
383
|
+
not found or if a refresh is requested. Predictions are determined using an ensemble training
|
384
|
+
method which averages predictions to forecast future trends. Additionally, the function checks
|
385
|
+
and caches whether the predicted data demonstrates an increasing trend over time.
|
386
386
|
|
387
|
-
|
388
|
-
refresh (bool):
|
389
|
-
num (int
|
387
|
+
Args:
|
388
|
+
refresh (bool): If True, forces recalculation and cache refresh of predictions.
|
389
|
+
num (int): Number of times to repeat ensemble training for more consistent predictions.
|
390
|
+
Defaults to 5.
|
390
391
|
|
391
392
|
Returns:
|
392
|
-
Tuple[dict, LSTMGrade]: A tuple containing a dictionary of future
|
393
|
-
evaluation grade.
|
393
|
+
Tuple[dict, LSTMGrade]: A tuple containing a dictionary of future date-price pairs and the
|
394
|
+
evaluation grade of the LSTM prediction model.
|
394
395
|
|
395
396
|
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.
|
397
|
+
AssertionError: Raised if the lengths of 'future_dates' and 'final_future_predictions' do
|
398
|
+
not match during the data preparation.
|
402
399
|
"""
|
403
400
|
print("**** Start get_final_predictions... ****")
|
404
|
-
redis_name = f'{self.
|
401
|
+
redis_name = f'{self.ticker}_mylstm_predictions'
|
405
402
|
|
406
403
|
print(
|
407
404
|
f"redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
|
408
405
|
|
406
|
+
def caching_is_lstm_up(future_data_in: dict):
|
407
|
+
"""
|
408
|
+
날짜(str)를 키, 수치(float)를 값으로 갖는 딕셔너리를
|
409
|
+
선형회귀분석(최소제곱법)을 통해 추세가 우상향인지 판별.
|
410
|
+
|
411
|
+
Returns:
|
412
|
+
bool: 기울기가 양수이면 True, 아니면 False
|
413
|
+
"""
|
414
|
+
|
415
|
+
print("**** Caching is_lstm_up ... ****")
|
416
|
+
redis_name = f'{self.ticker}_is_lstm_up'
|
417
|
+
print(f"redisname: '{redis_name}' / expire_time : {expire_time / 3600}h")
|
418
|
+
|
419
|
+
|
420
|
+
if not future_data_in:
|
421
|
+
# 데이터가 비어있으면 추세를 판단할 수 없음
|
422
|
+
return False
|
423
|
+
|
424
|
+
# 1) 날짜(키) 기준 오름차순 정렬
|
425
|
+
sorted_dates = sorted(future_data_in.keys())
|
426
|
+
values = [future_data_in[d] for d in sorted_dates]
|
427
|
+
|
428
|
+
# 2) x 축을 0,1,2... 형태로 부여 (날짜 간격을 동일하게 가정)
|
429
|
+
x = np.arange(len(values), dtype=float)
|
430
|
+
y = np.array(values, dtype=float)
|
431
|
+
|
432
|
+
# 3) 선형 회귀(최소제곱법)로 기울기(slope) 계산
|
433
|
+
x_mean = np.mean(x)
|
434
|
+
y_mean = np.mean(y)
|
435
|
+
|
436
|
+
# 분자: sum((xi - x_mean) * (yi - y_mean))
|
437
|
+
numerator = np.sum((x - x_mean) * (y - y_mean))
|
438
|
+
# 분모: sum((xi - x_mean)^2)
|
439
|
+
denominator = np.sum((x - x_mean) ** 2)
|
440
|
+
|
441
|
+
if denominator == 0:
|
442
|
+
# 데이터가 1개 이하인 경우 등
|
443
|
+
return False
|
444
|
+
|
445
|
+
slope = numerator / denominator
|
446
|
+
|
447
|
+
# 4) 기울기가 양수면 "우상향 추세"로 판별
|
448
|
+
is_up = slope > 0
|
449
|
+
myredis.Base.set_value(redis_name, is_up, expire_time)
|
450
|
+
|
409
451
|
def fetch_final_predictions(num_in) -> tuple:
|
410
452
|
"""
|
411
453
|
앙상블법으로 딥러닝을 모델을 반복해서 평균을 내서 미래를 예측한다. 평가는 래시스 캐시로 반환하기 어려워 일단 디버그 용도로만 사용하기로
|
@@ -443,7 +485,12 @@ class MyLSTM:
|
|
443
485
|
|
444
486
|
return future_data, lstm_grade
|
445
487
|
|
446
|
-
|
488
|
+
future_data, lstm_grade = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_final_predictions, num, timer=expire_time)
|
489
|
+
|
490
|
+
# 증가 추세인지 아닌지 레디스 캐시에 저장
|
491
|
+
caching_is_lstm_up(future_data)
|
492
|
+
|
493
|
+
return future_data, lstm_grade
|
447
494
|
|
448
495
|
def export(self, refresh=False, to="str", num=5) -> Optional[str]:
|
449
496
|
"""
|
@@ -558,11 +605,11 @@ class MyLSTM:
|
|
558
605
|
return graph_html
|
559
606
|
elif to == 'png':
|
560
607
|
# 그래프를 PNG 파일로 저장
|
561
|
-
fig.write_image(f"myLSTM_{self.
|
608
|
+
fig.write_image(f"myLSTM_{self.ticker}.png")
|
562
609
|
return None
|
563
610
|
elif to == 'htmlfile':
|
564
611
|
# 그래프를 HTML로 저장
|
565
|
-
plot(fig, filename=f'myLSTM_{self.
|
612
|
+
plot(fig, filename=f'myLSTM_{self.ticker}.html', auto_open=False)
|
566
613
|
return None
|
567
614
|
else:
|
568
615
|
Exception("to 인자가 맞지 않습니다.")
|
@@ -608,7 +655,7 @@ class MyLSTM:
|
|
608
655
|
plt.xlabel('Date')
|
609
656
|
plt.ylabel('Stock Price')
|
610
657
|
plt.legend()
|
611
|
-
plt.title(f'{self.
|
658
|
+
plt.title(f'{self.ticker} Stock Price Prediction with LSTM')
|
612
659
|
plt.show()
|
613
660
|
|
614
661
|
"""# 시각화2
|
@@ -621,50 +668,53 @@ class MyLSTM:
|
|
621
668
|
plt.title('Stock Price Prediction with LSTM Ensemble')
|
622
669
|
plt.show()"""
|
623
670
|
|
624
|
-
def
|
671
|
+
def is_lstm_up(self) -> bool:
|
625
672
|
"""
|
626
|
-
Determines
|
673
|
+
Determines whether the LSTM model is active or not.
|
627
674
|
|
628
|
-
This method
|
629
|
-
|
675
|
+
This method checks the status of an LSTM model by querying a Redis
|
676
|
+
value identified by a specific code indicating LSTM activity.
|
630
677
|
|
631
678
|
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.
|
679
|
+
bool: True if the LSTM model is active (up), False otherwise.
|
657
680
|
"""
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
681
|
+
return myredis.Base.get_value(f'{self.ticker}_is_lstm_up')
|
682
|
+
|
683
|
+
|
684
|
+
class CorpLSTM(MyLSTM):
|
685
|
+
def __init__(self, code: str):
|
686
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
687
|
+
self._code = code
|
688
|
+
self.name = myredis.Corps(code, 'c101').get_name()
|
689
|
+
super().__init__(ticker=self.code + '.KS')
|
690
|
+
|
691
|
+
@property
|
692
|
+
def code(self) -> str:
|
693
|
+
return self._code
|
694
|
+
|
695
|
+
@code.setter
|
696
|
+
def code(self, code: str):
|
697
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
698
|
+
self._code = code
|
699
|
+
self.name = myredis.Corps(code, 'c101').get_name()
|
700
|
+
self.ticker = self.code + '.KS'
|
667
701
|
|
668
702
|
|
703
|
+
class MILSTM(MyLSTM):
|
704
|
+
def __init__(self, mi_type: str):
|
705
|
+
from analyser_hj3415.analyser.tsa import MIs
|
706
|
+
assert mi_type in MIs.keys(), f"Invalid MI type ({MIs.keys()})"
|
707
|
+
self._mi_type = mi_type
|
708
|
+
super().__init__(ticker=MIs[mi_type])
|
669
709
|
|
710
|
+
@property
|
711
|
+
def mi_type(self) -> str:
|
712
|
+
return self._mi_type
|
713
|
+
|
714
|
+
@mi_type.setter
|
715
|
+
def mi_type(self, mi_type: str):
|
716
|
+
from analyser_hj3415.analyser.tsa import MIs
|
717
|
+
assert mi_type in MIs.keys(), f"Invalid MI type ({MIs.keys()})"
|
718
|
+
self._mi_type = mi_type
|
719
|
+
self.ticker = MIs[mi_type]
|
670
720
|
|
@@ -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
|
@@ -17,28 +17,27 @@ mylogger = setup_logger(__name__,'WARNING')
|
|
17
17
|
|
18
18
|
|
19
19
|
class MyProphet:
|
20
|
-
def __init__(self,
|
21
|
-
|
20
|
+
def __init__(self, ticker: str):
|
21
|
+
mylogger.info(f'set up ticker : {ticker}')
|
22
22
|
self.scaler = StandardScaler()
|
23
|
-
|
24
23
|
self.model = Prophet()
|
25
|
-
self.
|
26
|
-
|
24
|
+
self._ticker = ticker
|
25
|
+
|
27
26
|
self.raw_data = self._get_raw_data()
|
28
27
|
self.df_real = self._preprocessing_for_prophet()
|
29
28
|
self.df_forecast = self._make_forecast()
|
30
29
|
|
31
30
|
@property
|
32
|
-
def
|
33
|
-
return self.
|
31
|
+
def ticker(self) -> str:
|
32
|
+
return self._ticker
|
34
33
|
|
35
|
-
@
|
36
|
-
def
|
37
|
-
|
38
|
-
|
34
|
+
@ticker.setter
|
35
|
+
def ticker(self, ticker: str):
|
36
|
+
mylogger.info(f'change ticker : {self.ticker} -> {ticker}')
|
37
|
+
self.scaler = StandardScaler()
|
39
38
|
self.model = Prophet()
|
40
|
-
self.
|
41
|
-
|
39
|
+
self._ticker = ticker
|
40
|
+
|
42
41
|
self.raw_data = self._get_raw_data()
|
43
42
|
self.df_real = self._preprocessing_for_prophet()
|
44
43
|
self.df_forecast = self._make_forecast()
|
@@ -65,7 +64,7 @@ class MyProphet:
|
|
65
64
|
four_years_ago = today - timedelta(days=365 * 4)
|
66
65
|
|
67
66
|
return yf.download(
|
68
|
-
self.
|
67
|
+
tickers=self.ticker,
|
69
68
|
start=four_years_ago.strftime('%Y-%m-%d'),
|
70
69
|
end=today.strftime('%Y-%m-%d')
|
71
70
|
)
|
@@ -168,40 +167,103 @@ class MyProphet:
|
|
168
167
|
return graph_html
|
169
168
|
elif to == 'png':
|
170
169
|
# 그래프를 PNG 파일로 저장
|
171
|
-
fig.write_image(f"myprophet_{self.
|
170
|
+
fig.write_image(f"myprophet_{self.ticker}.png")
|
172
171
|
return None
|
173
172
|
elif to == 'htmlfile':
|
174
173
|
# 그래프를 HTML로 저장
|
175
|
-
plot(fig, filename=f'myprophet_{self.
|
174
|
+
plot(fig, filename=f'myprophet_{self.ticker}.html', auto_open=False)
|
176
175
|
return None
|
177
176
|
else:
|
178
177
|
Exception("to 인자가 맞지 않습니다.")
|
179
178
|
|
180
|
-
def scoring(self) -> int:
|
179
|
+
def scoring(self) -> Tuple[str, int]:
|
181
180
|
"""
|
182
|
-
|
181
|
+
Calculate and return a trading action and associated score based on recent market data and forecasted values.
|
182
|
+
|
183
|
+
The method computes deviations between the recent price and predefined forecasted bounds (`yhat_lower` and `yhat_upper`)
|
184
|
+
to determine whether to buy, sell, or hold a financial instrument. The calculation involves generating a deviation score
|
185
|
+
that is transformed using a sigmoid function. The scores are then adjusted based on the position of the recent price relative
|
186
|
+
to the forecasted bounds. Logging is used extensively throughout the method to record the values and decisions for debugging
|
187
|
+
and traceability purposes.
|
183
188
|
|
184
189
|
Returns:
|
185
|
-
|
186
|
-
and
|
190
|
+
A tuple containing a string indicating the recommended trading action ('buy', 'sell', 'hold')
|
191
|
+
and an integer score associated with that action.
|
187
192
|
|
188
193
|
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.
|
194
|
+
KeyError: If required keys are missing in the `yhat_dict` or `last_real_data` dictionary-like structures.
|
193
195
|
"""
|
194
196
|
last_real_data = self.df_real.iloc[-1]
|
195
197
|
recent_price = last_real_data['y']
|
196
198
|
recent_date = datetime.strftime(last_real_data['ds'], '%Y-%m-%d')
|
197
199
|
yhat_dict = self.get_yhat()
|
198
200
|
mylogger.info(f'recent_price: {recent_price}, yhat_dict: {yhat_dict}')
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
201
|
+
|
202
|
+
yhat_lower = tools.to_int(yhat_dict['yhat_lower'])
|
203
|
+
yhat_upper = tools.to_int(yhat_dict['yhat_upper'])
|
204
|
+
#yhat = tools.to_int(yhat_dict['yhat'])
|
205
|
+
|
206
|
+
buying_deviation = eval.Tools.cal_deviation(recent_price, yhat_lower)
|
207
|
+
|
208
|
+
buying_score = tools.to_int(eval.Tools.sigmoid_score(buying_deviation))
|
209
|
+
# score = tools.to_int(Tools.log_score(deviation))
|
210
|
+
|
211
|
+
if recent_price >= yhat_lower:
|
212
|
+
buying_score = -buying_score
|
213
|
+
|
214
|
+
selling_deviation = eval.Tools.cal_deviation(recent_price, yhat_upper)
|
215
|
+
|
216
|
+
selling_score = tools.to_int(eval.Tools.sigmoid_score(selling_deviation))
|
217
|
+
# score = tools.to_int(Tools.log_score(deviation))
|
218
|
+
|
219
|
+
if recent_price <= yhat_upper:
|
220
|
+
selling_score = -selling_score
|
221
|
+
|
222
|
+
mylogger.info(f"{self.ticker} date: {recent_date} 가격: {recent_price}"
|
223
|
+
f" yhat_lower:{yhat_lower} yhat_upper:{yhat_upper}"
|
224
|
+
f" buying_score:{buying_score} selling_score:{selling_score}")
|
225
|
+
|
226
|
+
if buying_score > 0:
|
227
|
+
return 'buy', buying_score
|
228
|
+
elif selling_score > 0:
|
229
|
+
return 'sell', selling_score
|
203
230
|
else:
|
204
|
-
|
205
|
-
|
206
|
-
|
231
|
+
return 'hold', 0
|
232
|
+
|
233
|
+
|
234
|
+
class CorpProphet(MyProphet):
|
235
|
+
def __init__(self, code: str):
|
236
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
237
|
+
self._code = code
|
238
|
+
self.name = myredis.Corps(code, 'c101').get_name()
|
239
|
+
super().__init__(ticker=self.code + '.KS')
|
240
|
+
|
241
|
+
@property
|
242
|
+
def code(self) -> str:
|
243
|
+
return self._code
|
244
|
+
|
245
|
+
@code.setter
|
246
|
+
def code(self, code: str):
|
247
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
248
|
+
self._code = code
|
249
|
+
self.name = myredis.Corps(code, 'c101').get_name()
|
250
|
+
self.ticker = self.code + '.KS'
|
251
|
+
|
252
|
+
|
253
|
+
class MIProphet(MyProphet):
|
254
|
+
def __init__(self, mi_type: str):
|
255
|
+
from analyser_hj3415.analyser.tsa import MIs
|
256
|
+
assert mi_type in MIs.keys(), f"Invalid MI type ({MIs.keys()})"
|
257
|
+
self._mi_type = mi_type
|
258
|
+
super().__init__(ticker=MIs[mi_type])
|
259
|
+
|
260
|
+
@property
|
261
|
+
def mi_type(self) -> str:
|
262
|
+
return self._mi_type
|
207
263
|
|
264
|
+
@mi_type.setter
|
265
|
+
def mi_type(self, mi_type: str):
|
266
|
+
from analyser_hj3415.analyser.tsa import MIs
|
267
|
+
assert mi_type in MIs.keys(), f"Invalid MI type ({MIs.keys()})"
|
268
|
+
self._mi_type = mi_type
|
269
|
+
self.ticker = MIs[mi_type]
|
analyser_hj3415/cli.py
CHANGED
@@ -2,31 +2,37 @@ 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
|
|
9
9
|
def analyser_manager():
|
10
|
+
from analyser_hj3415.analyser.tsa import MIs
|
10
11
|
parser = argparse.ArgumentParser(description="Analyser Commands")
|
11
12
|
type_subparsers = parser.add_subparsers(dest='type', help='분석 타입')
|
12
13
|
|
13
14
|
# prophet 명령어 서브파서
|
14
15
|
prophet_parser = type_subparsers.add_parser('prophet', help='MyProphet 타입')
|
15
16
|
prophet_subparser = prophet_parser.add_subparsers(dest='command', help='prophet 관련된 명령')
|
16
|
-
# ranking 파서
|
17
|
+
# prophet - ranking 파서
|
17
18
|
ranking_parser = prophet_subparser.add_parser('ranking', help='prophet 랭킹 책정 및 레디스 저장')
|
18
19
|
ranking_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
20
|
+
# prophet - score 파서
|
21
|
+
prophet_get_parser = prophet_subparser.add_parser('score', help='prophet score 계산')
|
22
|
+
prophet_get_parser.add_argument('target', type=str, help=f'종목코드 or {list(MIs.keys())}')
|
23
|
+
prophet_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
19
24
|
|
20
25
|
# lstm 명령어 서브파서
|
21
26
|
lstm_parser = type_subparsers.add_parser('lstm', help='MyLSTM 타입')
|
22
27
|
lstm_subparser = lstm_parser.add_subparsers(dest='command', help='lstm 관련된 명령')
|
23
|
-
# caching 파서
|
28
|
+
# lstm - caching 파서
|
24
29
|
caching_parser = lstm_subparser.add_parser('caching', help='lstm 랭킹 책정 및 레디스 저장')
|
25
30
|
caching_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
26
31
|
caching_parser.add_argument('-t', '--top', type=int, help='prophet ranking 몇위까지 작업을 할지')
|
27
|
-
#
|
28
|
-
lstm_get_parser = lstm_subparser.add_parser('
|
29
|
-
lstm_get_parser.add_argument('
|
32
|
+
# lstm - predict 파서
|
33
|
+
lstm_get_parser = lstm_subparser.add_parser('predict', help='lstm get final prediction 시행')
|
34
|
+
lstm_get_parser.add_argument('target', type=str, help=f'종목코드 or {list(MIs.keys())}')
|
35
|
+
lstm_get_parser.add_argument('-n', '--num', type=int, default=5, help='ensemble training 횟수 설정')
|
30
36
|
lstm_get_parser.add_argument('-r', '--refresh', action='store_true', help='래디스 캐시를 사용하지 않고 강제로 재계산 할지')
|
31
37
|
|
32
38
|
# red 명령어 서브파서
|
@@ -92,9 +98,9 @@ def analyser_manager():
|
|
92
98
|
elif args.command == 'ranking':
|
93
99
|
mymongo.Logs.save('cli','INFO', 'run >> analyser red ranking')
|
94
100
|
if args.expect_earn is None:
|
95
|
-
result =
|
101
|
+
result = compile.Compile.red_ranking(refresh=args.refresh)
|
96
102
|
else:
|
97
|
-
result =
|
103
|
+
result = compile.Compile.red_ranking(expect_earn=args.expect_earn, refresh=args.refresh)
|
98
104
|
print(result)
|
99
105
|
|
100
106
|
elif args.type == 'mil':
|
@@ -147,22 +153,37 @@ def analyser_manager():
|
|
147
153
|
|
148
154
|
elif args.type == 'prophet':
|
149
155
|
if args.command == 'ranking':
|
150
|
-
result =
|
156
|
+
result = compile.Compile.prophet_ranking(refresh=args.refresh)
|
151
157
|
print(result)
|
152
158
|
mymongo.Logs.save('cli','INFO', 'run >> analyser prophet ranking')
|
159
|
+
elif args.command == 'score':
|
160
|
+
if args.target in MIs.keys():
|
161
|
+
myprophet = tsa.MIProphet(args.target)
|
162
|
+
elif tools.is_6digit(args.target):
|
163
|
+
myprophet = tsa.CorpProphet(args.target)
|
164
|
+
else:
|
165
|
+
raise Exception("Invalid target")
|
166
|
+
print(myprophet.scoring())
|
167
|
+
# mymongo.Logs.save('cli','INFO', f'run >> analyser prophet get {args.target}')
|
153
168
|
|
154
169
|
elif args.type == 'lstm':
|
155
170
|
if args.command == 'caching':
|
156
171
|
if args.top:
|
157
|
-
|
172
|
+
compile.Compile.analyse_lstm_topn(refresh=args.refresh, top=args.top)
|
158
173
|
else:
|
159
|
-
|
174
|
+
compile.Compile.analyse_lstm_topn(refresh=args.refresh)
|
160
175
|
mymongo.Logs.save('cli','INFO', f'run >> analyser lstm caching / top={args.top if args.top else 20}')
|
161
|
-
elif args.command == '
|
162
|
-
|
163
|
-
|
176
|
+
elif args.command == 'predict':
|
177
|
+
if args.target in MIs.keys():
|
178
|
+
mylstm = tsa.MILSTM(args.target)
|
179
|
+
elif tools.is_6digit(args.target):
|
180
|
+
mylstm = tsa.CorpLSTM(args.target)
|
181
|
+
else:
|
182
|
+
raise Exception("Invalid target")
|
164
183
|
mylstm.initializing()
|
165
|
-
mylstm.get_final_predictions(refresh=args.refresh, num=
|
166
|
-
|
184
|
+
future_data, grade = mylstm.get_final_predictions(refresh=args.refresh, num=args.num)
|
185
|
+
print(future_data)
|
186
|
+
print(grade)
|
187
|
+
# mymongo.Logs.save('cli','INFO', f'run >> analyser lstm get {args.target}')
|
167
188
|
else:
|
168
189
|
parser.print_help()
|
@@ -0,0 +1,22 @@
|
|
1
|
+
analyser_hj3415/__init__.py,sha256=f2E9Neh7Nzkhvdj7HWWlgxZK2sB95rBtaIgWEHzxK9E,450
|
2
|
+
analyser_hj3415/cli.py,sha256=2lwbI8GD-AEXO89Ma2khWOOJWeL4HLxnDyW5fVZZvmM,10486
|
3
|
+
analyser_hj3415/analyser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
analyser_hj3415/analyser/compile.py,sha256=e3ziVf8SX0K6rk9XFPAhi7cpxTaFlKYCQ2dHACzLlPo,5763
|
5
|
+
analyser_hj3415/analyser/eval/__init__.py,sha256=IP1d0Q3nOCAD3zK1qxrC685MkJQfUh-qaXc7xptTxk8,80
|
6
|
+
analyser_hj3415/analyser/eval/blue.py,sha256=fq_eln7-EdwICNQ2sMXAfZKXGhQ29EaJwsGvn7xA29U,7632
|
7
|
+
analyser_hj3415/analyser/eval/common.py,sha256=wcWJg_jNgr02i1U62oZGgLt2iscdle9X-u4aMnZl89Q,14127
|
8
|
+
analyser_hj3415/analyser/eval/growth.py,sha256=OXuBuPzVw3q6kpelNigiX7xvJNUP1gv26DNetc4aews,3315
|
9
|
+
analyser_hj3415/analyser/eval/mil.py,sha256=wN1jPu5zLJjoDk3kL7piL-4jWhpsAMeQpsTZll0a2dw,10568
|
10
|
+
analyser_hj3415/analyser/eval/red.py,sha256=rJbidKlo2s2EJHBGn4HDbfCDcEGP5Rst8iQeJ6jlzNA,10988
|
11
|
+
analyser_hj3415/analyser/tsa/__init__.py,sha256=ZmhZmdhxKv3zfh4byDROZOIWl8jBmfJQMo8phJTXkSg,160
|
12
|
+
analyser_hj3415/analyser/tsa/lstm.py,sha256=6T_JzlcDV8l-8-delE9zlbl-JeWF6zO0gg-RRNV5noE,29972
|
13
|
+
analyser_hj3415/analyser/tsa/prophet.py,sha256=Xeb1GxvQQAacFtTFYN0fmzk6iYQkFSex2HDaY2Ek7OY,10154
|
14
|
+
analyser_hj3415/workroom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
analyser_hj3415/workroom/mysklearn.py,sha256=wJXKz5MqqTzADdG2mqRMMzc_G9RzwYjj5_j4gyOopxQ,2030
|
16
|
+
analyser_hj3415/workroom/mysklearn2.py,sha256=1lIy6EWEQHkOzDS-av8U0zQH6DuCLKWMI73dnJx5KRs,1495
|
17
|
+
analyser_hj3415/workroom/score.py,sha256=P6nHBJYmyhigGtT4qna4BmNtvt4B93b7SKyzdstJK24,17376
|
18
|
+
analyser_hj3415/workroom/trash.py,sha256=zF-W0piqkGr66UP6-iybo9EXh2gO0RP6R1FnIpsGkl8,12262
|
19
|
+
analyser_hj3415-3.1.0.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
|
20
|
+
analyser_hj3415-3.1.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
21
|
+
analyser_hj3415-3.1.0.dist-info/METADATA,sha256=wSqUbHt-HM5qoipE-zictBm6Z2g3hfqugpEsLc-GzMw,6777
|
22
|
+
analyser_hj3415-3.1.0.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형 이어야 합니다.")
|
@@ -1,22 +0,0 @@
|
|
1
|
-
analyser_hj3415/__init__.py,sha256=huF0tm8fFC96sODN9TmVRmPhDizQ0yBTKNOw8miOBh0,448
|
2
|
-
analyser_hj3415/cli.py,sha256=RHMz1JYSVPVkWU6xdrZo-fdRB3tBW3oSC2XrpI8Q2nk,9248
|
3
|
-
analyser_hj3415/analyser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
analyser_hj3415/analyser/score.py,sha256=RmHWp7fSBkpgKd68SeSyg7oY3TT-nvPQGK36I5P6dk0,6100
|
5
|
-
analyser_hj3415/analyser/eval/__init__.py,sha256=IP1d0Q3nOCAD3zK1qxrC685MkJQfUh-qaXc7xptTxk8,80
|
6
|
-
analyser_hj3415/analyser/eval/blue.py,sha256=fq_eln7-EdwICNQ2sMXAfZKXGhQ29EaJwsGvn7xA29U,7632
|
7
|
-
analyser_hj3415/analyser/eval/common.py,sha256=JBTVvzO6YoeMjKl6X2MF7wgB6fvPTEToHLJSv-V7V4k,12425
|
8
|
-
analyser_hj3415/analyser/eval/growth.py,sha256=OXuBuPzVw3q6kpelNigiX7xvJNUP1gv26DNetc4aews,3315
|
9
|
-
analyser_hj3415/analyser/eval/mil.py,sha256=wN1jPu5zLJjoDk3kL7piL-4jWhpsAMeQpsTZll0a2dw,10568
|
10
|
-
analyser_hj3415/analyser/eval/red.py,sha256=SegWvklGFCCFpzg9qwKDEU0-iSq-d_lM6XRNhth8VWM,12529
|
11
|
-
analyser_hj3415/analyser/tsa/__init__.py,sha256=rPnn4b8aEym2O9l_sAaIRUpOeXJw6sDj2XfUzy-mpvg,42
|
12
|
-
analyser_hj3415/analyser/tsa/lstm.py,sha256=jrJlugPTugSH9liNGpiRMaigr3mq3FeGxDNVvcll3N8,28302
|
13
|
-
analyser_hj3415/analyser/tsa/prophet.py,sha256=zL6RNSiwFKKliQVsR3yOHz13S2SrCfuXvBj62lqP80Y,7984
|
14
|
-
analyser_hj3415/workroom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
analyser_hj3415/workroom/mysklearn.py,sha256=wJXKz5MqqTzADdG2mqRMMzc_G9RzwYjj5_j4gyOopxQ,2030
|
16
|
-
analyser_hj3415/workroom/mysklearn2.py,sha256=1lIy6EWEQHkOzDS-av8U0zQH6DuCLKWMI73dnJx5KRs,1495
|
17
|
-
analyser_hj3415/workroom/score.py,sha256=P6nHBJYmyhigGtT4qna4BmNtvt4B93b7SKyzdstJK24,17376
|
18
|
-
analyser_hj3415/workroom/trash.py,sha256=zF-W0piqkGr66UP6-iybo9EXh2gO0RP6R1FnIpsGkl8,12262
|
19
|
-
analyser_hj3415-3.0.4.dist-info/entry_points.txt,sha256=ZfjPnJuH8SzvhE9vftIPMBIofsc65IAWYOhqOC_L5ck,65
|
20
|
-
analyser_hj3415-3.0.4.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
21
|
-
analyser_hj3415-3.0.4.dist-info/METADATA,sha256=bR36Tb4IyoDHDhTRx5sS7hQ7sIvfzLn39NeP2e6dxEg,6777
|
22
|
-
analyser_hj3415-3.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|