analyser_hj3415 3.0.4__py3-none-any.whl → 3.1.0__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/__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
|