analyser_hj3415 2.6.8__py2.py3-none-any.whl → 2.7.1__py2.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/cli.py +115 -45
- analyser_hj3415/eval.py +948 -0
- analyser_hj3415/workroom/__init__.py +0 -0
- analyser_hj3415/workroom/lstm.py +113 -0
- analyser_hj3415/workroom/myprophet.py +58 -0
- analyser_hj3415/workroom/mysklearn.py +50 -0
- analyser_hj3415/workroom/mysklearn2.py +39 -0
- analyser_hj3415/{analysers → workroom}/score.py +0 -38
- {analyser_hj3415-2.6.8.dist-info → analyser_hj3415-2.7.1.dist-info}/METADATA +3 -3
- analyser_hj3415-2.7.1.dist-info/RECORD +16 -0
- analyser_hj3415/analysers/eval.py +0 -276
- analyser_hj3415/analysers/report.py +0 -182
- analyser_hj3415/myredis.py +0 -186
- analyser_hj3415/tools.py +0 -247
- analyser_hj3415-2.6.8.dist-info/RECORD +0 -14
- /analyser_hj3415/{trash.py → workroom/trash.py} +0 -0
- {analyser_hj3415-2.6.8.dist-info → analyser_hj3415-2.7.1.dist-info}/LICENSE +0 -0
- {analyser_hj3415-2.6.8.dist-info → analyser_hj3415-2.7.1.dist-info}/WHEEL +0 -0
- {analyser_hj3415-2.6.8.dist-info → analyser_hj3415-2.7.1.dist-info}/entry_points.txt +0 -0
analyser_hj3415/eval.py
ADDED
@@ -0,0 +1,948 @@
|
|
1
|
+
from dataclasses import dataclass, asdict
|
2
|
+
|
3
|
+
from utils_hj3415 import utils, helpers
|
4
|
+
from typing import Tuple
|
5
|
+
from db_hj3415 import myredis, mymongo
|
6
|
+
import math
|
7
|
+
from analyser_hj3415.cli import AnalyserSettingsManager
|
8
|
+
from collections import OrderedDict
|
9
|
+
import logging
|
10
|
+
|
11
|
+
analyser_logger = helpers.setup_logger('analyser_logger', logging.INFO)
|
12
|
+
|
13
|
+
|
14
|
+
class Tools:
|
15
|
+
@staticmethod
|
16
|
+
def cal_deviation(v1: float, v2: float) -> float:
|
17
|
+
"""
|
18
|
+
괴리율 구하는 공식
|
19
|
+
:param v1:
|
20
|
+
:param v2:
|
21
|
+
:return:
|
22
|
+
"""
|
23
|
+
try:
|
24
|
+
deviation = abs((v1 - v2) / v1) * 100
|
25
|
+
except ZeroDivisionError:
|
26
|
+
deviation = math.nan
|
27
|
+
return deviation
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def date_set(*args) -> list:
|
31
|
+
"""
|
32
|
+
비유효한 내용 제거(None,nan)하고 중복된 항목 제거하고 리스트로 반환한다.
|
33
|
+
여기서 set의 의미는 집합을 뜻함
|
34
|
+
:param args:
|
35
|
+
:return:
|
36
|
+
"""
|
37
|
+
return [i for i in {*args} if i != "" and i is not math.nan and i is not None]
|
38
|
+
|
39
|
+
@staticmethod
|
40
|
+
def calc당기순이익(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
41
|
+
"""
|
42
|
+
지배지분 당기순이익 계산
|
43
|
+
|
44
|
+
일반적인 경우로는 직전 지배주주지분 당기순이익을 찾아서 반환한다.\n
|
45
|
+
금융기관의 경우는 지배당기순이익이 없기 때문에\n
|
46
|
+
계산을 통해서 간접적으로 구한다.\n
|
47
|
+
"""
|
48
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
49
|
+
|
50
|
+
analyser_logger.info(f'{c103.code} / {name} Tools : 당기순이익 계산.. refresh : {refresh}')
|
51
|
+
c103.page = 'c103재무상태표q'
|
52
|
+
|
53
|
+
d1, 지배당기순이익 = c103.latest_value_pop2('*(지배)당기순이익', refresh)
|
54
|
+
analyser_logger.debug(f"*(지배)당기순이익: {지배당기순이익}")
|
55
|
+
|
56
|
+
if math.isnan(지배당기순이익):
|
57
|
+
analyser_logger.warning(f"{c103.code} / {name} - (지배)당기순이익이 없는 종목. 수동으로 계산합니다.")
|
58
|
+
c103.page = 'c103손익계산서q'
|
59
|
+
d2, 최근4분기당기순이익 = c103.sum_recent_4q('당기순이익', refresh)
|
60
|
+
analyser_logger.debug(f"{c103.code} / {name} - 최근4분기당기순이익 : {최근4분기당기순이익}")
|
61
|
+
c103.page = 'c103재무상태표y'
|
62
|
+
d3, 비지배당기순이익 = c103.latest_value_pop2('*(비지배)당기순이익', refresh)
|
63
|
+
analyser_logger.debug(f"{c103.code} / {name} - 비지배당기순이익y : {비지배당기순이익}")
|
64
|
+
# 가변리스트 언패킹으로 하나의 날짜만 사용하고 나머지는 버린다.
|
65
|
+
# 여기서 *_는 “나머지 값을 다 무시하겠다”는 의미
|
66
|
+
analyser_logger.debug(f"d2:{d2}, d3: {d3}")
|
67
|
+
try:
|
68
|
+
date, *_ = Tools.date_set(d2, d3)
|
69
|
+
except ValueError:
|
70
|
+
# 날짜 데이터가 없는경우
|
71
|
+
date = ''
|
72
|
+
계산된지배당기순이익= round(최근4분기당기순이익 - utils.nan_to_zero(비지배당기순이익), 1)
|
73
|
+
analyser_logger.debug(f"{c103.code} / {name} - 계산된 지배당기순이익 : {계산된지배당기순이익}")
|
74
|
+
return date, 계산된지배당기순이익
|
75
|
+
else:
|
76
|
+
return d1, 지배당기순이익
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def calc유동자산(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
80
|
+
"""유효한 유동자산 계산
|
81
|
+
|
82
|
+
일반적인 경우로 유동자산을 찾아서 반환한다.\n
|
83
|
+
금융기관의 경우는 간접적으로 계산한다.\n
|
84
|
+
"""
|
85
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
86
|
+
|
87
|
+
analyser_logger.info(f'{c103.code} / {name} Tools : 유동자산계산... refresh : {refresh}')
|
88
|
+
c103.page = 'c103재무상태표q'
|
89
|
+
|
90
|
+
d, 유동자산 = c103.sum_recent_4q('유동자산', refresh)
|
91
|
+
if math.isnan(유동자산):
|
92
|
+
analyser_logger.warning(f"{c103.code} / {name} - 유동자산이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
93
|
+
d1, v1 = c103.latest_value_pop2('현금및예치금', refresh)
|
94
|
+
d2, v2 = c103.latest_value_pop2('단기매매금융자산', refresh)
|
95
|
+
d3, v3 = c103.latest_value_pop2('매도가능금융자산', refresh)
|
96
|
+
d4, v4 = c103.latest_value_pop2('만기보유금융자산', refresh)
|
97
|
+
analyser_logger.debug(f'{c103.code} / {name} 현금및예치금 : {d1}, {v1}')
|
98
|
+
analyser_logger.debug(f'{c103.code} / {name} 단기매매금융자산 : {d2}, {v2}')
|
99
|
+
analyser_logger.debug(f'{c103.code} / {name} 매도가능금융자산 : {d3}, {v3}')
|
100
|
+
analyser_logger.debug(f'{c103.code} / {name} 만기보유금융자산 : {d4}, {v4}')
|
101
|
+
|
102
|
+
try:
|
103
|
+
date, *_ = Tools.date_set(d1, d2, d3, d4)
|
104
|
+
except ValueError:
|
105
|
+
# 날짜 데이터가 없는경우
|
106
|
+
date = ''
|
107
|
+
계산된유동자산value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4),1)
|
108
|
+
|
109
|
+
analyser_logger.info(f"{c103.code} / {name} - 계산된 유동자산 : {계산된유동자산value}")
|
110
|
+
return date, 계산된유동자산value
|
111
|
+
else:
|
112
|
+
return d, 유동자산
|
113
|
+
|
114
|
+
@staticmethod
|
115
|
+
def calc유동부채(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
116
|
+
"""유효한 유동부채 계산
|
117
|
+
|
118
|
+
일반적인 경우로 유동부채를 찾아서 반환한다.\n
|
119
|
+
금융기관의 경우는 간접적으로 계산한다.\n
|
120
|
+
"""
|
121
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
122
|
+
|
123
|
+
analyser_logger.info(f'{c103.code} / {name} Tools : 유동부채계산... refresh : {refresh}')
|
124
|
+
c103.page = 'c103재무상태표q'
|
125
|
+
|
126
|
+
d, 유동부채 = c103.sum_recent_4q('유동부채', refresh)
|
127
|
+
if math.isnan(유동부채):
|
128
|
+
analyser_logger.warning(f"{c103.code} / {name} - 유동부채가 없는 종목. 수동으로 계산합니다.")
|
129
|
+
d1, v1 = c103.latest_value_pop2('당기손익인식(지정)금융부채', refresh)
|
130
|
+
d2, v2 = c103.latest_value_pop2('당기손익-공정가치측정금융부채', refresh)
|
131
|
+
d3, v3 = c103.latest_value_pop2('매도파생결합증권', refresh)
|
132
|
+
d4, v4 = c103.latest_value_pop2('단기매매금융부채', refresh)
|
133
|
+
analyser_logger.debug(f'{c103.code} / {name} 당기손익인식(지정)금융부채 : {d1}, {v1}')
|
134
|
+
analyser_logger.debug(f'{c103.code} / {name} 당기손익-공정가치측정금융부채 : {d2}, {v2}')
|
135
|
+
analyser_logger.debug(f'{c103.code} / {name} 매도파생결합증권 : {d3}, {v3}')
|
136
|
+
analyser_logger.debug(f'{c103.code} / {name} 단기매매금융부채 : {d4}, {v4}')
|
137
|
+
|
138
|
+
try:
|
139
|
+
date, *_ = Tools.date_set(d1, d2, d3, d4)
|
140
|
+
except ValueError:
|
141
|
+
# 날짜 데이터가 없는경우
|
142
|
+
date = ''
|
143
|
+
계산된유동부채value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4), 1)
|
144
|
+
|
145
|
+
analyser_logger.info(f"{c103.code} / {name} - 계산된 유동부채 : {계산된유동부채value}")
|
146
|
+
return date, 계산된유동부채value
|
147
|
+
else:
|
148
|
+
return d, 유동부채
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
@dataclass
|
157
|
+
class RedData:
|
158
|
+
code: str
|
159
|
+
name: str
|
160
|
+
|
161
|
+
# 사업가치 계산 - 지배주주지분 당기순이익 / 기대수익률
|
162
|
+
사업가치: float
|
163
|
+
지배주주당기순이익: float
|
164
|
+
expect_earn: float
|
165
|
+
|
166
|
+
# 재산가치 계산 - 유동자산 - (유동부채*1.2) + 고정자산중 투자자산
|
167
|
+
재산가치: float
|
168
|
+
유동자산: float
|
169
|
+
유동부채: float
|
170
|
+
투자자산: float
|
171
|
+
투자부동산: float
|
172
|
+
|
173
|
+
# 부채평가 - 비유동부채
|
174
|
+
부채평가: float
|
175
|
+
|
176
|
+
# 발행주식수
|
177
|
+
발행주식수: int
|
178
|
+
|
179
|
+
date: list
|
180
|
+
|
181
|
+
red_price: float
|
182
|
+
score: int
|
183
|
+
|
184
|
+
def __post_init__(self):
|
185
|
+
if not utils.is_6digit(self.code):
|
186
|
+
raise ValueError(f"code는 6자리 숫자형 문자열이어야합니다. (입력값: {self.code})")
|
187
|
+
|
188
|
+
|
189
|
+
class Red:
|
190
|
+
expect_earn = AnalyserSettingsManager().get_value('EXPECT_EARN')
|
191
|
+
|
192
|
+
def __init__(self, code: str):
|
193
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
194
|
+
analyser_logger.debug(f"Red : 초기화 ({code})")
|
195
|
+
self.c101 = myredis.C101(code)
|
196
|
+
self.c103 = myredis.C103(code, 'c103재무상태표q')
|
197
|
+
|
198
|
+
self.name = self.c101.get_name()
|
199
|
+
self._code = code
|
200
|
+
|
201
|
+
def __str__(self):
|
202
|
+
return f"Red({self.code}/{self.name})"
|
203
|
+
|
204
|
+
@property
|
205
|
+
def code(self) -> str:
|
206
|
+
return self._code
|
207
|
+
|
208
|
+
@code.setter
|
209
|
+
def code(self, code: str):
|
210
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
211
|
+
analyser_logger.debug(f"Red : 종목코드 변경({self.code} -> {code})")
|
212
|
+
self.c101.code = code
|
213
|
+
self.c103.code = code
|
214
|
+
|
215
|
+
self.name = self.c101.get_name()
|
216
|
+
self._code = code
|
217
|
+
|
218
|
+
def _calc비유동부채(self, refresh: bool) -> Tuple[str, float]:
|
219
|
+
"""유효한 비유동부채 계산
|
220
|
+
|
221
|
+
일반적인 경우로 비유동부채를 찾아서 반환한다.\n
|
222
|
+
금융기관의 경우는 간접적으로 계산한다.\n
|
223
|
+
"""
|
224
|
+
analyser_logger.info(f'In the calc비유동부채... refresh : {refresh}')
|
225
|
+
self.c103.page = 'c103재무상태표q'
|
226
|
+
|
227
|
+
d, 비유동부채 = self.c103.sum_recent_4q('비유동부채', refresh)
|
228
|
+
if math.isnan(비유동부채):
|
229
|
+
analyser_logger.warning(f"{self} - 비유동부채가 없는 종목. 수동으로 계산합니다.")
|
230
|
+
# 보험관련업종은 예수부채가 없는대신 보험계약부채가 있다...
|
231
|
+
d1, v1 = self.c103.latest_value_pop2('예수부채', refresh)
|
232
|
+
d2, v2 = self.c103.latest_value_pop2('보험계약부채(책임준비금)', refresh)
|
233
|
+
d3, v3 = self.c103.latest_value_pop2('차입부채', refresh)
|
234
|
+
d4, v4 = self.c103.latest_value_pop2('기타부채', refresh)
|
235
|
+
analyser_logger.debug(f'예수부채 : {d1}, {v1}')
|
236
|
+
analyser_logger.debug(f'보험계약부채(책임준비금) : {d2}, {v2}')
|
237
|
+
analyser_logger.debug(f'차입부채 : {d3}, {v3}')
|
238
|
+
analyser_logger.debug(f'기타부채 : {d4}, {v4}')
|
239
|
+
|
240
|
+
try:
|
241
|
+
date, *_ = Tools.date_set(d1, d2, d3, d4)
|
242
|
+
except ValueError:
|
243
|
+
# 날짜 데이터가 없는경우
|
244
|
+
date = ''
|
245
|
+
계산된비유동부채value = round(utils.nan_to_zero(v1) + utils.nan_to_zero(v2) + utils.nan_to_zero(v3) + utils.nan_to_zero(v4),1)
|
246
|
+
analyser_logger.info(f"{self} - 계산된 비유동부채 : {계산된비유동부채value}")
|
247
|
+
return date, 계산된비유동부채value
|
248
|
+
else:
|
249
|
+
return d, 비유동부채
|
250
|
+
|
251
|
+
def _score(self, red_price: int, refresh: bool) -> int:
|
252
|
+
"""red price와 최근 주가의 괴리율 파악
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
int : 주가와 red price 비교한 괴리율
|
256
|
+
"""
|
257
|
+
try:
|
258
|
+
recent_price = utils.to_int(self.c101.get_recent(refresh)['주가'])
|
259
|
+
except KeyError:
|
260
|
+
return 0
|
261
|
+
|
262
|
+
deviation = Tools.cal_deviation(recent_price, red_price)
|
263
|
+
if red_price < 0 or (recent_price >= red_price):
|
264
|
+
score = 0
|
265
|
+
else:
|
266
|
+
score = utils.to_int(math.log10(deviation + 1) * 33) # desmos그래프상 33이 제일 적당한듯(최대100점에 가깝게)
|
267
|
+
|
268
|
+
analyser_logger.debug(f"최근주가 : {recent_price} red가격 : {red_price} 괴리율 : {utils.to_int(deviation)} score : {score}")
|
269
|
+
|
270
|
+
return score
|
271
|
+
|
272
|
+
def _generate_data(self, refresh: bool) -> RedData:
|
273
|
+
d1, 지배주주당기순이익 = Tools.calc당기순이익(self.c103, refresh)
|
274
|
+
analyser_logger.debug(f"{self} 지배주주당기순이익: {지배주주당기순이익}")
|
275
|
+
d2, 유동자산 = Tools.calc유동자산(self.c103, refresh)
|
276
|
+
d3, 유동부채 = Tools.calc유동부채(self.c103, refresh)
|
277
|
+
d4, 부채평가 = self._calc비유동부채(refresh)
|
278
|
+
|
279
|
+
self.c103.page = 'c103재무상태표q'
|
280
|
+
d5, 투자자산 = self.c103.latest_value_pop2('투자자산', refresh)
|
281
|
+
d6, 투자부동산 = self.c103.latest_value_pop2('투자부동산', refresh)
|
282
|
+
|
283
|
+
# 사업가치 계산 - 지배주주지분 당기순이익 / 기대수익률
|
284
|
+
사업가치 = round(지배주주당기순이익 / Red.expect_earn, 2)
|
285
|
+
|
286
|
+
# 재산가치 계산 - 유동자산 - (유동부채*1.2) + 고정자산중 투자자산
|
287
|
+
재산가치 = round(유동자산 - (유동부채 * 1.2) + utils.nan_to_zero(투자자산) + utils.nan_to_zero(투자부동산), 2)
|
288
|
+
|
289
|
+
_, 발행주식수 = self.c103.latest_value_pop2('발행주식수', refresh)
|
290
|
+
if math.isnan(발행주식수):
|
291
|
+
발행주식수 = utils.to_int(self.c101.get_recent(refresh).get('발행주식'))
|
292
|
+
else:
|
293
|
+
발행주식수 = 발행주식수 * 1000
|
294
|
+
|
295
|
+
try:
|
296
|
+
red_price = round(((사업가치 + 재산가치 - 부채평가) * 100000000) / 발행주식수)
|
297
|
+
except (ZeroDivisionError, ValueError):
|
298
|
+
red_price = math.nan
|
299
|
+
|
300
|
+
score = self._score(red_price, refresh)
|
301
|
+
|
302
|
+
try:
|
303
|
+
date_list = Tools.date_set(d1, d2, d3, d4)
|
304
|
+
except ValueError:
|
305
|
+
# 날짜 데이터가 없는경우
|
306
|
+
date_list = ['',]
|
307
|
+
|
308
|
+
return RedData(
|
309
|
+
code = self.code,
|
310
|
+
name = self.name,
|
311
|
+
사업가치 = 사업가치,
|
312
|
+
지배주주당기순이익 = 지배주주당기순이익,
|
313
|
+
expect_earn = Red.expect_earn,
|
314
|
+
재산가치 = 재산가치,
|
315
|
+
유동자산 = 유동자산,
|
316
|
+
유동부채 = 유동부채,
|
317
|
+
투자자산 = 투자자산,
|
318
|
+
투자부동산 = 투자부동산,
|
319
|
+
부채평가 = 부채평가,
|
320
|
+
발행주식수 = 발행주식수,
|
321
|
+
date = date_list,
|
322
|
+
red_price = red_price,
|
323
|
+
score = score,
|
324
|
+
)
|
325
|
+
|
326
|
+
def get(self, refresh = False) -> RedData:
|
327
|
+
"""
|
328
|
+
RedData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
329
|
+
:param refresh:
|
330
|
+
:return:
|
331
|
+
"""
|
332
|
+
redis_name = f"{self.code}_red"
|
333
|
+
analyser_logger.info(f"{self} RedData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
334
|
+
|
335
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
336
|
+
return asdict(self._generate_data(refresh_in))
|
337
|
+
|
338
|
+
return RedData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh))
|
339
|
+
|
340
|
+
@classmethod
|
341
|
+
def ranking(cls, expect_earn: float = None, refresh = False) -> OrderedDict:
|
342
|
+
"""
|
343
|
+
redis를 사용하며 red score를 계산해서 0이상의 값을 가지는 종목을 순서대로 저장하여 반환한다.(캐시 유효시간 12시간)
|
344
|
+
:param expect_earn: 기대수익률(일반적으로 0.06 - 0.10)
|
345
|
+
:param refresh: 캐시를 사용하지 않고 강제로 다시 계산
|
346
|
+
:return: OrderedDict([('023590', 101),
|
347
|
+
('010060', 91),...]), 레디스이름
|
348
|
+
"""
|
349
|
+
|
350
|
+
print("**** Start red_ranking... ****")
|
351
|
+
# expect_earn 및 refresh 설정
|
352
|
+
if expect_earn is None:
|
353
|
+
expect_earn = cls.expect_earn
|
354
|
+
analyser_logger.info(f"기대수익률을 {expect_earn}으로 설정합니다.")
|
355
|
+
previous_expect_earn = float(AnalyserSettingsManager().get_value('RED_RANKING_EXPECT_EARN'))
|
356
|
+
analyser_logger.debug(f"previous red ranking expect earn : {previous_expect_earn}")
|
357
|
+
if previous_expect_earn != expect_earn:
|
358
|
+
analyser_logger.warning(f"expect earn : {expect_earn} / RED_RANKING_EXPECT_EARN : {previous_expect_earn} 두 값이 달라 refresh = True")
|
359
|
+
refresh = True
|
360
|
+
|
361
|
+
redis_name = 'red_ranking'
|
362
|
+
analyser_logger.info(f"redisname: '{redis_name}' / refresh : {refresh}")
|
363
|
+
|
364
|
+
def fetch_ranking(expect_earn_in: float, refresh_in: bool) -> dict:
|
365
|
+
data = {}
|
366
|
+
# 저장된 기대수익률을 불러서 임시저장
|
367
|
+
ee_orig = Red.expect_earn
|
368
|
+
# 원하는 기대수익률로 클래스 세팅
|
369
|
+
Red.expect_earn = expect_earn_in
|
370
|
+
AnalyserSettingsManager().set_value('RED_RANKING_EXPECT_EARN', str(expect_earn_in))
|
371
|
+
for i, code in enumerate(myredis.Corps.list_all_codes()):
|
372
|
+
red = Red(code)
|
373
|
+
red_score = red.get(refresh=refresh_in).score
|
374
|
+
if red_score > 0:
|
375
|
+
data[code] = red_score
|
376
|
+
print(f"{i}: {red} - {red_score}")
|
377
|
+
# 원래 저장되었던 기대수익률로 다시 복원
|
378
|
+
Red.expect_earn = ee_orig
|
379
|
+
return data
|
380
|
+
|
381
|
+
data_dict = myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_ranking, expect_earn, refresh, timer=3600 * 12)
|
382
|
+
|
383
|
+
return OrderedDict(sorted(data_dict.items(), key=lambda item: item[1], reverse=True))
|
384
|
+
|
385
|
+
|
386
|
+
@dataclass
|
387
|
+
class MilData:
|
388
|
+
code: str
|
389
|
+
name: str
|
390
|
+
|
391
|
+
시가총액억: float
|
392
|
+
|
393
|
+
주주수익률: float
|
394
|
+
재무활동현금흐름: float
|
395
|
+
|
396
|
+
이익지표: float
|
397
|
+
영업활동현금흐름: float
|
398
|
+
지배주주당기순이익: float
|
399
|
+
|
400
|
+
#투자수익률
|
401
|
+
roic_r: float
|
402
|
+
roic_dict: dict
|
403
|
+
roe_r: float
|
404
|
+
roe_106: dict
|
405
|
+
roa_r: float
|
406
|
+
|
407
|
+
#가치지표
|
408
|
+
fcf_dict: dict
|
409
|
+
pfcf_dict: dict
|
410
|
+
pcr_dict: dict
|
411
|
+
|
412
|
+
score: list
|
413
|
+
date: list
|
414
|
+
|
415
|
+
|
416
|
+
class Mil:
|
417
|
+
def __init__(self, code: str):
|
418
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
419
|
+
analyser_logger.debug(f"Mil : 종목코드 ({code})")
|
420
|
+
|
421
|
+
self.c101 = myredis.C101(code)
|
422
|
+
self.c103 = myredis.C103(code, 'c103현금흐름표q')
|
423
|
+
self.c104 = myredis.C104(code, 'c104q')
|
424
|
+
self.c106 = myredis.C106(code, 'c106q')
|
425
|
+
|
426
|
+
self.name = self.c101.get_name()
|
427
|
+
self._code = code
|
428
|
+
|
429
|
+
def __str__(self):
|
430
|
+
return f"Mil({self.code}/{self.name})"
|
431
|
+
|
432
|
+
@property
|
433
|
+
def code(self) -> str:
|
434
|
+
return self._code
|
435
|
+
|
436
|
+
@code.setter
|
437
|
+
def code(self, code: str):
|
438
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
439
|
+
analyser_logger.debug(f"Mil : 종목코드 변경({self.code} -> {code})")
|
440
|
+
|
441
|
+
self.c101.code = code
|
442
|
+
self.c103.code = code
|
443
|
+
self.c104.code = code
|
444
|
+
self.c106.code = code
|
445
|
+
|
446
|
+
self.name = self.c101.get_name()
|
447
|
+
self._code = code
|
448
|
+
|
449
|
+
def get_marketcap억(self, refresh: bool) -> float:
|
450
|
+
"""
|
451
|
+
시가총액(억원) 반환
|
452
|
+
:return:
|
453
|
+
"""
|
454
|
+
c101r = self.c101.get_recent(refresh)
|
455
|
+
시가총액 = int(utils.to_int(c101r.get('시가총액', math.nan)) / 100000000)
|
456
|
+
analyser_logger.debug(f"시가총액: {시가총액}억원")
|
457
|
+
return 시가총액
|
458
|
+
|
459
|
+
def _calc주주수익률(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float]:
|
460
|
+
self.c103.page = 'c103현금흐름표q'
|
461
|
+
d, 재무활동현금흐름 = self.c103.sum_recent_4q('재무활동으로인한현금흐름', refresh)
|
462
|
+
try:
|
463
|
+
주주수익률 = round((재무활동현금흐름 / 시가총액_억 * -100), 2)
|
464
|
+
except ZeroDivisionError:
|
465
|
+
주주수익률 = math.nan
|
466
|
+
analyser_logger.warning(f'{self} 주주수익률: {주주수익률} 재무활동현금흐름: {재무활동현금흐름}')
|
467
|
+
return d, 주주수익률, 재무활동현금흐름
|
468
|
+
|
469
|
+
def _calc이익지표(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float, float]:
|
470
|
+
d1, 지배주주당기순이익 = Tools.calc당기순이익(self.c103, refresh)
|
471
|
+
self.c103.page = 'c103현금흐름표q'
|
472
|
+
d2, 영업활동현금흐름 = self.c103.sum_recent_4q('영업활동으로인한현금흐름', refresh)
|
473
|
+
try:
|
474
|
+
이익지표 = round(((지배주주당기순이익 - 영업활동현금흐름) / 시가총액_억) * 100, 2)
|
475
|
+
except ZeroDivisionError:
|
476
|
+
이익지표 = math.nan
|
477
|
+
analyser_logger.warning(f'{self} 이익지표: {이익지표} 영업활동현금흐름: {영업활동현금흐름} 지배주주당기순이익: {지배주주당기순이익}')
|
478
|
+
try:
|
479
|
+
date, *_ = Tools.date_set(d1, d2)
|
480
|
+
except ValueError:
|
481
|
+
# 날짜 데이터가 없는경우
|
482
|
+
date = ''
|
483
|
+
return date , 이익지표, 영업활동현금흐름, 지배주주당기순이익
|
484
|
+
|
485
|
+
def _calc투자수익률(self, refresh: bool) -> tuple:
|
486
|
+
self.c104.page = 'c104q'
|
487
|
+
self.c106.page = 'c106q'
|
488
|
+
d1, roic_r = self.c104.sum_recent_4q('ROIC', refresh)
|
489
|
+
_, roic_dict = self.c104.find('ROIC', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
|
490
|
+
d2, roe_r = self.c104.latest_value_pop2('ROE', refresh)
|
491
|
+
roe106 = self.c106.find('ROE', refresh)
|
492
|
+
d3, roa_r = self.c104.latest_value_pop2('ROA', refresh)
|
493
|
+
|
494
|
+
try:
|
495
|
+
date, *_ = Tools.date_set(d1, d2, d3)
|
496
|
+
except ValueError:
|
497
|
+
# 날짜 데이터가 없는경우
|
498
|
+
date = ''
|
499
|
+
|
500
|
+
return date, roic_r, roic_dict, roe_r, roe106, roa_r
|
501
|
+
|
502
|
+
def _calcFCF(self, refresh: bool) -> dict:
|
503
|
+
"""
|
504
|
+
FCF 계산
|
505
|
+
Returns:
|
506
|
+
dict: 계산된 fcf 딕셔너리 또는 영업현금흐름 없는 경우 - {}
|
507
|
+
|
508
|
+
Note:
|
509
|
+
CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.\n
|
510
|
+
|
511
|
+
"""
|
512
|
+
self.c103.page = 'c103현금흐름표y'
|
513
|
+
_, 영업활동현금흐름_dict = self.c103.find('영업활동으로인한현금흐름', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
|
514
|
+
|
515
|
+
self.c103.page = 'c103재무상태표y'
|
516
|
+
_, capex = self.c103.find('*CAPEX', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
|
517
|
+
|
518
|
+
analyser_logger.debug(f'영업활동현금흐름 {영업활동현금흐름_dict}')
|
519
|
+
analyser_logger.debug(f'CAPEX {capex}')
|
520
|
+
|
521
|
+
if len(영업활동현금흐름_dict) == 0:
|
522
|
+
return {}
|
523
|
+
|
524
|
+
if len(capex) == 0:
|
525
|
+
# CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.
|
526
|
+
analyser_logger.warning(f"{self} - CAPEX가 없는 업종으로 영업현금흐름을 그대로 사용합니다..")
|
527
|
+
return 영업활동현금흐름_dict
|
528
|
+
|
529
|
+
# 영업 활동으로 인한 현금 흐름에서 CAPEX 를 각 연도별로 빼주어 fcf 를 구하고 리턴값으로 fcf 딕셔너리를 반환한다.
|
530
|
+
fcf_dict = {}
|
531
|
+
for i in range(len(영업활동현금흐름_dict)):
|
532
|
+
# 영업활동현금흐름에서 아이템을 하나씩 꺼내서 CAPEX 전체와 비교하여 같으면 차를 구해서 fcf_dict 에 추가한다.
|
533
|
+
영업활동현금흐름date, 영업활동현금흐름value = 영업활동현금흐름_dict.popitem()
|
534
|
+
# 해당 연도의 capex 가 없는 경우도 있어 일단 capex를 0으로 치고 먼저 추가한다.
|
535
|
+
fcf_dict[영업활동현금흐름date] = 영업활동현금흐름value
|
536
|
+
for CAPEXdate, CAPEXvalue in capex.items():
|
537
|
+
if 영업활동현금흐름date == CAPEXdate:
|
538
|
+
fcf_dict[영업활동현금흐름date] = round(영업활동현금흐름value - CAPEXvalue, 2)
|
539
|
+
|
540
|
+
analyser_logger.debug(f'fcf_dict {fcf_dict}')
|
541
|
+
# 연도순으로 정렬해서 딕셔너리로 반환한다.
|
542
|
+
return dict(sorted(fcf_dict.items(), reverse=False))
|
543
|
+
|
544
|
+
def _calcPFCF(self, 시가총액_억: float, fcf_dict: dict) -> dict:
|
545
|
+
"""Price to Free Cash Flow Ratio(주가 대비 자유 현금 흐름 비율)계산
|
546
|
+
|
547
|
+
PFCF = 시가총액 / FCF
|
548
|
+
|
549
|
+
Note:
|
550
|
+
https://www.investopedia.com/terms/p/pricetofreecashflow.asp
|
551
|
+
"""
|
552
|
+
if math.isnan(시가총액_억):
|
553
|
+
analyser_logger.warning(f"{self} - 시가총액이 nan으로 pFCF를 계산할수 없습니다.")
|
554
|
+
return {}
|
555
|
+
|
556
|
+
# pfcf 계산
|
557
|
+
pfcf_dict = {}
|
558
|
+
for FCFdate, FCFvalue in fcf_dict.items():
|
559
|
+
if FCFvalue == 0:
|
560
|
+
pfcf_dict[FCFdate] = math.nan
|
561
|
+
else:
|
562
|
+
pfcf_dict[FCFdate] = round(시가총액_억 / FCFvalue, 2)
|
563
|
+
|
564
|
+
pfcf_dict = mymongo.C1034.del_unnamed_key(pfcf_dict)
|
565
|
+
|
566
|
+
analyser_logger.debug(f'pfcf_dict : {pfcf_dict}')
|
567
|
+
return pfcf_dict
|
568
|
+
|
569
|
+
def _calc가치지표(self, 시가총액_억: float, refresh: bool) -> tuple:
|
570
|
+
self.c104.page = 'c104q'
|
571
|
+
|
572
|
+
fcf_dict = self._calcFCF(refresh)
|
573
|
+
pfcf_dict = self._calcPFCF(시가총액_억, fcf_dict)
|
574
|
+
|
575
|
+
d, pcr_dict = self.c104.find('PCR', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
|
576
|
+
return d, fcf_dict, pfcf_dict, pcr_dict
|
577
|
+
|
578
|
+
def _score(self) -> list:
|
579
|
+
return [0,]
|
580
|
+
|
581
|
+
def _generate_data(self, refresh: bool) -> MilData:
|
582
|
+
analyser_logger.info(f"In generate_data..refresh : {refresh}")
|
583
|
+
시가총액_억 = self.get_marketcap억(refresh)
|
584
|
+
analyser_logger.info(f"{self} 시가총액(억) : {시가총액_억}")
|
585
|
+
|
586
|
+
d1, 주주수익률, 재무활동현금흐름 = self._calc주주수익률(시가총액_억, refresh)
|
587
|
+
analyser_logger.info(f"{self} 주주수익률 : {주주수익률}, {d1}")
|
588
|
+
|
589
|
+
d2, 이익지표, 영업활동현금흐름, 지배주주당기순이익 = self._calc이익지표(시가총액_억, refresh)
|
590
|
+
analyser_logger.info(f"{self} 이익지표 : {이익지표}, {d2}")
|
591
|
+
|
592
|
+
d3, roic_r, roic_dict, roe_r, roe106, roa_r = self._calc투자수익률(refresh)
|
593
|
+
d4, fcf_dict, pfcf_dict, pcr_dict = self._calc가치지표(시가총액_억, refresh)
|
594
|
+
|
595
|
+
score = self._score()
|
596
|
+
|
597
|
+
try:
|
598
|
+
date_list = Tools.date_set(d1, d2, d3, d4)
|
599
|
+
except ValueError:
|
600
|
+
# 날짜 데이터가 없는경우
|
601
|
+
date_list = ['',]
|
602
|
+
|
603
|
+
return MilData(
|
604
|
+
code= self.code,
|
605
|
+
name= self.name,
|
606
|
+
|
607
|
+
시가총액억= 시가총액_억,
|
608
|
+
|
609
|
+
주주수익률= 주주수익률,
|
610
|
+
재무활동현금흐름= 재무활동현금흐름,
|
611
|
+
|
612
|
+
이익지표= 이익지표,
|
613
|
+
영업활동현금흐름= 영업활동현금흐름,
|
614
|
+
지배주주당기순이익= 지배주주당기순이익,
|
615
|
+
|
616
|
+
roic_r= roic_r,
|
617
|
+
roic_dict= roic_dict,
|
618
|
+
roe_r= roe_r,
|
619
|
+
roe_106= roe106,
|
620
|
+
roa_r= roa_r,
|
621
|
+
|
622
|
+
fcf_dict= fcf_dict,
|
623
|
+
pfcf_dict= pfcf_dict,
|
624
|
+
pcr_dict= pcr_dict,
|
625
|
+
|
626
|
+
score= score,
|
627
|
+
date = date_list,
|
628
|
+
)
|
629
|
+
|
630
|
+
def get(self, refresh = False) -> MilData:
|
631
|
+
"""
|
632
|
+
MilData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
633
|
+
:param refresh:
|
634
|
+
:return:
|
635
|
+
"""
|
636
|
+
redis_name = f"{self.code}_mil"
|
637
|
+
analyser_logger.info(f"{self} MilData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
638
|
+
|
639
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
640
|
+
return asdict(self._generate_data(refresh_in))
|
641
|
+
|
642
|
+
return MilData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh))
|
643
|
+
|
644
|
+
|
645
|
+
@dataclass()
|
646
|
+
class BlueData:
|
647
|
+
code: str
|
648
|
+
name: str
|
649
|
+
|
650
|
+
유동비율: float
|
651
|
+
|
652
|
+
이자보상배율_r: float
|
653
|
+
이자보상배율_dict: dict
|
654
|
+
|
655
|
+
순운전자본회전율_r: float
|
656
|
+
순운전자본회전율_dict: dict
|
657
|
+
|
658
|
+
재고자산회전율_r: float
|
659
|
+
재고자산회전율_dict: dict
|
660
|
+
재고자산회전율_c106: dict
|
661
|
+
|
662
|
+
순부채비율_r: float
|
663
|
+
순부채비율_dict: dict
|
664
|
+
|
665
|
+
score: list
|
666
|
+
date: list
|
667
|
+
|
668
|
+
|
669
|
+
class Blue:
|
670
|
+
def __init__(self, code: str):
|
671
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
672
|
+
analyser_logger.debug(f"Blue : 종목코드 ({code})")
|
673
|
+
|
674
|
+
self.c101 = myredis.C101(code)
|
675
|
+
self.c103 = myredis.C103(code, 'c103재무상태표q')
|
676
|
+
self.c104 = myredis.C104(code, 'c104q')
|
677
|
+
|
678
|
+
self.name = self.c101.get_name()
|
679
|
+
self._code = code
|
680
|
+
|
681
|
+
def __str__(self):
|
682
|
+
return f"Blue({self.code}/{self.name})"
|
683
|
+
|
684
|
+
@property
|
685
|
+
def code(self) -> str:
|
686
|
+
return self._code
|
687
|
+
|
688
|
+
@code.setter
|
689
|
+
def code(self, code: str):
|
690
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
691
|
+
analyser_logger.debug(f"Blue : 종목코드 변경({self.code} -> {code})")
|
692
|
+
|
693
|
+
self.c101.code = code
|
694
|
+
self.c103.code = code
|
695
|
+
self.c104.code = code
|
696
|
+
|
697
|
+
self.name = self.c101.get_name()
|
698
|
+
self._code = code
|
699
|
+
|
700
|
+
def _calc유동비율(self, pop_count: int, refresh: bool) -> Tuple[str, float]:
|
701
|
+
"""유동비율계산 - Blue에서 사용
|
702
|
+
|
703
|
+
c104q에서 최근유동비율 찾아보고 유효하지 않거나 \n
|
704
|
+
100이하인 경우에는수동으로 계산해서 다시 한번 평가해 본다.\n
|
705
|
+
"""
|
706
|
+
analyser_logger.info(f'In the calc유동비율... refresh : {refresh}')
|
707
|
+
self.c104.page = 'c104q'
|
708
|
+
|
709
|
+
유동비율date, 유동비율value = self.c104.latest_value('유동비율', pop_count=pop_count)
|
710
|
+
analyser_logger.info(f'{self} 유동비율 : {유동비율value}/({유동비율date})')
|
711
|
+
|
712
|
+
if math.isnan(유동비율value) or 유동비율value < 100:
|
713
|
+
유동자산date, 유동자산value = Tools.calc유동자산(self.c103, refresh)
|
714
|
+
유동부채date, 유동부채value = Tools.calc유동부채(self.c103, refresh)
|
715
|
+
|
716
|
+
self.c103.page = 'c103현금흐름표q'
|
717
|
+
추정영업현금흐름date, 추정영업현금흐름value = self.c103.sum_recent_4q('영업활동으로인한현금흐름', refresh)
|
718
|
+
analyser_logger.debug(f'{self} 계산전 유동비율 : {유동비율value} / ({유동비율date})')
|
719
|
+
|
720
|
+
계산된유동비율 = 0
|
721
|
+
try:
|
722
|
+
계산된유동비율 = round(((유동자산value + 추정영업현금흐름value) / 유동부채value) * 100, 2)
|
723
|
+
except ZeroDivisionError:
|
724
|
+
analyser_logger.info(f'유동자산: {유동자산value} + 추정영업현금흐름: {추정영업현금흐름value} / 유동부채: {유동부채value}')
|
725
|
+
계산된유동비율 = float('inf')
|
726
|
+
finally:
|
727
|
+
analyser_logger.debug(f'{self} 계산된 유동비율 : {계산된유동비율}')
|
728
|
+
|
729
|
+
try:
|
730
|
+
date, *_ = Tools.date_set(유동자산date, 유동부채date, 추정영업현금흐름date)
|
731
|
+
except ValueError:
|
732
|
+
# 날짜 데이터가 없는경우
|
733
|
+
date = ''
|
734
|
+
analyser_logger.warning(f'{self} 유동비율 이상(100 이하 또는 nan) : {유동비율value} -> 재계산 : {계산된유동비율}')
|
735
|
+
return date, 계산된유동비율
|
736
|
+
else:
|
737
|
+
return 유동비율date, 유동비율value
|
738
|
+
|
739
|
+
def _score(self) -> list:
|
740
|
+
return [0,]
|
741
|
+
|
742
|
+
def _generate_data(self, refresh: bool) -> BlueData:
|
743
|
+
d1, 유동비율 = self._calc유동비율(pop_count=3, refresh=refresh)
|
744
|
+
analyser_logger.info(f'유동비율 {유동비율} / [{d1}]')
|
745
|
+
|
746
|
+
재고자산회전율_c106 = myredis.C106.make_like_c106(self.code, 'c104q', '재고자산회전율', refresh)
|
747
|
+
|
748
|
+
self.c104.page = 'c104y'
|
749
|
+
_, 이자보상배율_dict = self.c104.find('이자보상배율', remove_yoy=True, refresh=refresh)
|
750
|
+
_, 순운전자본회전율_dict = self.c104.find('순운전자본회전율', remove_yoy=True, refresh=refresh)
|
751
|
+
_, 재고자산회전율_dict = self.c104.find('재고자산회전율', remove_yoy=True, refresh=refresh)
|
752
|
+
_, 순부채비율_dict = self.c104.find('순부채비율', remove_yoy=True, refresh=refresh)
|
753
|
+
|
754
|
+
self.c104.page = 'c104q'
|
755
|
+
d6, 이자보상배율_r = self.c104.latest_value_pop2('이자보상배율', refresh)
|
756
|
+
d7, 순운전자본회전율_r = self.c104.latest_value_pop2('순운전자본회전율', refresh)
|
757
|
+
d8, 재고자산회전율_r = self.c104.latest_value_pop2('재고자산회전율', refresh)
|
758
|
+
d9, 순부채비율_r = self.c104.latest_value_pop2('순부채비율', refresh)
|
759
|
+
|
760
|
+
if len(이자보상배율_dict) == 0:
|
761
|
+
analyser_logger.warning(f'empty dict - 이자보상배율 : {이자보상배율_r} / {이자보상배율_dict}')
|
762
|
+
|
763
|
+
if len(순운전자본회전율_dict) == 0:
|
764
|
+
analyser_logger.warning(f'empty dict - 순운전자본회전율 : {순운전자본회전율_r} / {순운전자본회전율_dict}')
|
765
|
+
|
766
|
+
if len(재고자산회전율_dict) == 0:
|
767
|
+
analyser_logger.warning(f'empty dict - 재고자산회전율 : {재고자산회전율_r} / {재고자산회전율_dict}')
|
768
|
+
|
769
|
+
if len(순부채비율_dict) == 0:
|
770
|
+
analyser_logger.warning(f'empty dict - 순부채비율 : {순부채비율_r} / {순부채비율_dict}')
|
771
|
+
|
772
|
+
score = self._score()
|
773
|
+
|
774
|
+
try:
|
775
|
+
date_list = Tools.date_set(d1, d6, d7, d8, d9)
|
776
|
+
except ValueError:
|
777
|
+
# 날짜 데이터가 없는경우
|
778
|
+
date_list = ['',]
|
779
|
+
|
780
|
+
return BlueData(
|
781
|
+
code= self.code,
|
782
|
+
name= self.name,
|
783
|
+
유동비율= 유동비율,
|
784
|
+
이자보상배율_r= 이자보상배율_r,
|
785
|
+
이자보상배율_dict= 이자보상배율_dict,
|
786
|
+
|
787
|
+
순운전자본회전율_r= 순운전자본회전율_r,
|
788
|
+
순운전자본회전율_dict= 순운전자본회전율_dict,
|
789
|
+
|
790
|
+
재고자산회전율_r= 재고자산회전율_r,
|
791
|
+
재고자산회전율_dict= 재고자산회전율_dict,
|
792
|
+
재고자산회전율_c106= 재고자산회전율_c106,
|
793
|
+
|
794
|
+
순부채비율_r= 순부채비율_r,
|
795
|
+
순부채비율_dict= 순부채비율_dict,
|
796
|
+
|
797
|
+
score= score,
|
798
|
+
date= date_list,
|
799
|
+
)
|
800
|
+
|
801
|
+
def get(self, refresh = False) -> BlueData:
|
802
|
+
"""
|
803
|
+
BlueData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
804
|
+
:param refresh:
|
805
|
+
:return:
|
806
|
+
"""
|
807
|
+
redis_name = f"{self.code}_blue"
|
808
|
+
analyser_logger.info(f"{self} BlueData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
809
|
+
|
810
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
811
|
+
return asdict(self._generate_data(refresh_in))
|
812
|
+
|
813
|
+
return BlueData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh))
|
814
|
+
|
815
|
+
|
816
|
+
|
817
|
+
@dataclass()
|
818
|
+
class GrowthData:
|
819
|
+
code: str
|
820
|
+
name: str
|
821
|
+
|
822
|
+
매출액증가율_r: float
|
823
|
+
매출액증가율_dict: dict
|
824
|
+
|
825
|
+
영업이익률_c106: dict
|
826
|
+
|
827
|
+
score: list
|
828
|
+
date: list
|
829
|
+
|
830
|
+
|
831
|
+
class Growth:
|
832
|
+
def __init__(self, code: str):
|
833
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
834
|
+
analyser_logger.debug(f"Growth : 종목코드 ({code})")
|
835
|
+
|
836
|
+
self.c101 = myredis.C101(code)
|
837
|
+
self.c104 = myredis.C104(code, 'c104q')
|
838
|
+
self.c106 = myredis.C106(code, 'c106q')
|
839
|
+
|
840
|
+
self.name = self.c101.get_name()
|
841
|
+
self._code = code
|
842
|
+
|
843
|
+
def __str__(self):
|
844
|
+
return f"Growth({self.code}/{self.name})"
|
845
|
+
|
846
|
+
@property
|
847
|
+
def code(self) -> str:
|
848
|
+
return self._code
|
849
|
+
|
850
|
+
@code.setter
|
851
|
+
def code(self, code: str):
|
852
|
+
assert utils.is_6digit(code), f'Invalid value : {code}'
|
853
|
+
analyser_logger.debug(f"Growth : 종목코드 변경({self.code} -> {code})")
|
854
|
+
|
855
|
+
self.c101.code = code
|
856
|
+
self.c104.code = code
|
857
|
+
self.c106.code = code
|
858
|
+
|
859
|
+
self.name = self.c101.get_name()
|
860
|
+
self._code = code
|
861
|
+
|
862
|
+
def _score(self) -> list:
|
863
|
+
return [0,]
|
864
|
+
|
865
|
+
def _generate_data(self, refresh=False) -> GrowthData:
|
866
|
+
self.c104.page = 'c104y'
|
867
|
+
_, 매출액증가율_dict = self.c104.find('매출액증가율', remove_yoy=True, refresh=refresh)
|
868
|
+
|
869
|
+
self.c104.page = 'c104q'
|
870
|
+
d2, 매출액증가율_r = self.c104.latest_value_pop2('매출액증가율')
|
871
|
+
|
872
|
+
analyser_logger.info(f'매출액증가율 : {매출액증가율_r} {매출액증가율_dict}')
|
873
|
+
|
874
|
+
# c106 에서 타 기업과 영업이익률 비교
|
875
|
+
self.c106.page = 'c106y'
|
876
|
+
영업이익률_c106 = self.c106.find('영업이익률', refresh)
|
877
|
+
|
878
|
+
score = self._score()
|
879
|
+
|
880
|
+
try:
|
881
|
+
date_list = Tools.date_set(d2)
|
882
|
+
except ValueError:
|
883
|
+
# 날짜 데이터가 없는경우
|
884
|
+
date_list = ['', ]
|
885
|
+
|
886
|
+
return GrowthData(
|
887
|
+
code= self.code,
|
888
|
+
name= self.name,
|
889
|
+
|
890
|
+
매출액증가율_r= 매출액증가율_r,
|
891
|
+
매출액증가율_dict= 매출액증가율_dict,
|
892
|
+
|
893
|
+
영업이익률_c106= 영업이익률_c106,
|
894
|
+
|
895
|
+
score= score,
|
896
|
+
date= date_list,
|
897
|
+
)
|
898
|
+
|
899
|
+
def get(self, refresh = False) -> GrowthData:
|
900
|
+
"""
|
901
|
+
GrowthData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
902
|
+
:param refresh:
|
903
|
+
:return:
|
904
|
+
"""
|
905
|
+
redis_name = f"{self.code}_growth"
|
906
|
+
analyser_logger.info(f"{self} GrowthData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
907
|
+
|
908
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
909
|
+
return asdict(self._generate_data(refresh_in))
|
910
|
+
|
911
|
+
return GrowthData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh))
|
912
|
+
|
913
|
+
|
914
|
+
|
915
|
+
|
916
|
+
"""
|
917
|
+
- 각분기의 합이 연이 아닌 타이틀(즉 sum_4q를 사용하면 안됨)
|
918
|
+
'*(지배)당기순이익'
|
919
|
+
'*(비지배)당기순이익'
|
920
|
+
'장기차입금'
|
921
|
+
'현금및예치금'
|
922
|
+
'매도가능금융자산'
|
923
|
+
'매도파생결합증권'
|
924
|
+
'만기보유금융자산'
|
925
|
+
'당기손익-공정가치측정금융부채'
|
926
|
+
'당기손익인식(지정)금융부채'
|
927
|
+
'단기매매금융자산'
|
928
|
+
'단기매매금융부채'
|
929
|
+
'예수부채'
|
930
|
+
'차입부채'
|
931
|
+
'기타부채'
|
932
|
+
'보험계약부채(책임준비금)'
|
933
|
+
'*CAPEX'
|
934
|
+
'ROE'
|
935
|
+
"""
|
936
|
+
|
937
|
+
"""
|
938
|
+
- sum_4q를 사용해도 되는 타이틀
|
939
|
+
'자산총계'
|
940
|
+
'당기순이익'
|
941
|
+
'유동자산'
|
942
|
+
'유동부채'
|
943
|
+
'비유동부채'
|
944
|
+
|
945
|
+
'영업활동으로인한현금흐름'
|
946
|
+
'재무활동으로인한현금흐름'
|
947
|
+
'ROIC'
|
948
|
+
"""
|