analyser_hj3415 2.10.5__py3-none-any.whl → 3.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,274 @@
1
+ import os
2
+ from dataclasses import dataclass, asdict
3
+ from typing import Tuple
4
+ import math
5
+
6
+ from utils_hj3415 import tools, setup_logger
7
+ from db_hj3415 import myredis, mymongo
8
+
9
+ from analyser_hj3415.analyser.eval.common import Tools
10
+
11
+
12
+ mylogger = setup_logger(__name__,'WARNING')
13
+ expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
14
+
15
+
16
+ @dataclass
17
+ class MilData:
18
+ code: str
19
+ name: str
20
+
21
+ 시가총액억: float
22
+
23
+ 주주수익률: float
24
+ 재무활동현금흐름: float
25
+
26
+ 이익지표: float
27
+ 영업활동현금흐름: float
28
+ 지배주주당기순이익: float
29
+
30
+ #투자수익률
31
+ roic_r: float
32
+ roic_dict: dict
33
+ roe_r: float
34
+ roe_106: dict
35
+ roa_r: float
36
+
37
+ #가치지표
38
+ fcf_dict: dict
39
+ pfcf_dict: dict
40
+ pcr_dict: dict
41
+
42
+ score: list
43
+ date: list
44
+
45
+
46
+ class Mil:
47
+ def __init__(self, code: str):
48
+ assert tools.is_6digit(code), f'Invalid value : {code}'
49
+ mylogger.debug(f"Mil : 종목코드 ({code})")
50
+
51
+ self.c101 = myredis.C101(code)
52
+ self.c103 = myredis.C103(code, 'c103현금흐름표q')
53
+ self.c104 = myredis.C104(code, 'c104q')
54
+ self.c106 = myredis.C106(code, 'c106q')
55
+
56
+ self.name = self.c101.get_name()
57
+ self._code = code
58
+
59
+ def __str__(self):
60
+ return f"Mil({self.code}/{self.name})"
61
+
62
+ @property
63
+ def code(self) -> str:
64
+ return self._code
65
+
66
+ @code.setter
67
+ def code(self, code: str):
68
+ assert tools.is_6digit(code), f'Invalid value : {code}'
69
+ mylogger.debug(f"Mil : 종목코드 변경({self.code} -> {code})")
70
+
71
+ self.c101.code = code
72
+ self.c103.code = code
73
+ self.c104.code = code
74
+ self.c106.code = code
75
+
76
+ self.name = self.c101.get_name()
77
+ self._code = code
78
+
79
+ def get_marketcap억(self, refresh: bool) -> float:
80
+ """
81
+ 시가총액(억원) 반환
82
+ :return:
83
+ """
84
+ c101r = self.c101.get_recent(refresh)
85
+ 시가총액 = tools.to_int(tools.to_float(c101r.get('시가총액', math.nan)) / 100000000)
86
+ mylogger.debug(f"시가총액: {시가총액}억원")
87
+ return 시가총액
88
+
89
+ def _calc주주수익률(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float]:
90
+ self.c103.page = 'c103현금흐름표q'
91
+ d, 재무활동현금흐름 = self.c103.sum_recent_4q('재무활동으로인한현금흐름', refresh)
92
+ try:
93
+ 주주수익률 = round((재무활동현금흐름 / 시가총액_억 * -100), 2)
94
+ except ZeroDivisionError:
95
+ 주주수익률 = math.nan
96
+ mylogger.warning(f'{self} 주주수익률: {주주수익률} 재무활동현금흐름: {재무활동현금흐름}')
97
+ return d, 주주수익률, 재무활동현금흐름
98
+
99
+ def _calc이익지표(self, 시가총액_억: float, refresh: bool) -> Tuple[str, float, float, float]:
100
+ d1, 지배주주당기순이익 = Tools.calc당기순이익(self.c103, refresh)
101
+ self.c103.page = 'c103현금흐름표q'
102
+ d2, 영업활동현금흐름 = self.c103.sum_recent_4q('영업활동으로인한현금흐름', refresh)
103
+ try:
104
+ 이익지표 = round(((지배주주당기순이익 - 영업활동현금흐름) / 시가총액_억) * 100, 2)
105
+ except ZeroDivisionError:
106
+ 이익지표 = math.nan
107
+ mylogger.warning(f'{self} 이익지표: {이익지표} 영업활동현금흐름: {영업활동현금흐름} 지배주주당기순이익: {지배주주당기순이익}')
108
+ try:
109
+ date, *_ = Tools.date_set(d1, d2)
110
+ except ValueError:
111
+ # 날짜 데이터가 없는경우
112
+ date = ''
113
+ return date , 이익지표, 영업활동현금흐름, 지배주주당기순이익
114
+
115
+ def _calc투자수익률(self, refresh: bool) -> tuple:
116
+ self.c104.page = 'c104q'
117
+ self.c106.page = 'c106q'
118
+ d1, roic_r = self.c104.sum_recent_4q('ROIC', refresh)
119
+ _, roic_dict = self.c104.find('ROIC', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
120
+ d2, roe_r = self.c104.latest_value_pop2('ROE', refresh)
121
+ roe106 = self.c106.find('ROE', refresh)
122
+ d3, roa_r = self.c104.latest_value_pop2('ROA', refresh)
123
+
124
+ try:
125
+ date, *_ = Tools.date_set(d1, d2, d3)
126
+ except ValueError:
127
+ # 날짜 데이터가 없는경우
128
+ date = ''
129
+
130
+ return date, roic_r, roic_dict, roe_r, roe106, roa_r
131
+
132
+ def _calcFCF(self, refresh: bool) -> dict:
133
+ """
134
+ FCF 계산
135
+ Returns:
136
+ dict: 계산된 fcf 딕셔너리 또는 영업현금흐름 없는 경우 - {}
137
+
138
+ Note:
139
+ CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.\n
140
+
141
+ """
142
+ self.c103.page = 'c103현금흐름표y'
143
+ _, 영업활동현금흐름_dict = self.c103.find('영업활동으로인한현금흐름', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
144
+
145
+ self.c103.page = 'c103재무상태표y'
146
+ _, capex = self.c103.find('*CAPEX', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
147
+
148
+ mylogger.debug(f'영업활동현금흐름 {영업활동현금흐름_dict}')
149
+ mylogger.debug(f'CAPEX {capex}')
150
+
151
+ if len(영업활동현금흐름_dict) == 0:
152
+ return {}
153
+
154
+ if len(capex) == 0:
155
+ # CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.
156
+ mylogger.warning(f"{self} - CAPEX가 없는 업종으로 영업현금흐름을 그대로 사용합니다..")
157
+ return 영업활동현금흐름_dict
158
+
159
+ # 영업 활동으로 인한 현금 흐름에서 CAPEX 를 각 연도별로 빼주어 fcf 를 구하고 리턴값으로 fcf 딕셔너리를 반환한다.
160
+ fcf_dict = {}
161
+ for i in range(len(영업활동현금흐름_dict)):
162
+ # 영업활동현금흐름에서 아이템을 하나씩 꺼내서 CAPEX 전체와 비교하여 같으면 차를 구해서 fcf_dict 에 추가한다.
163
+ 영업활동현금흐름date, 영업활동현금흐름value = 영업활동현금흐름_dict.popitem()
164
+ # 해당 연도의 capex 가 없는 경우도 있어 일단 capex를 0으로 치고 먼저 추가한다.
165
+ fcf_dict[영업활동현금흐름date] = 영업활동현금흐름value
166
+ for CAPEXdate, CAPEXvalue in capex.items():
167
+ if 영업활동현금흐름date == CAPEXdate:
168
+ fcf_dict[영업활동현금흐름date] = round(영업활동현금흐름value - CAPEXvalue, 2)
169
+
170
+ mylogger.debug(f'fcf_dict {fcf_dict}')
171
+ # 연도순으로 정렬해서 딕셔너리로 반환한다.
172
+ return dict(sorted(fcf_dict.items(), reverse=False))
173
+
174
+ def _calcPFCF(self, 시가총액_억: float, fcf_dict: dict) -> dict:
175
+ """Price to Free Cash Flow Ratio(주가 대비 자유 현금 흐름 비율)계산
176
+
177
+ PFCF = 시가총액 / FCF
178
+
179
+ Note:
180
+ https://www.investopedia.com/terms/p/pricetofreecashflow.asp
181
+ """
182
+ if math.isnan(시가총액_억):
183
+ mylogger.warning(f"{self} - 시가총액이 nan으로 pFCF를 계산할수 없습니다.")
184
+ return {}
185
+
186
+ # pfcf 계산
187
+ pfcf_dict = {}
188
+ for FCFdate, FCFvalue in fcf_dict.items():
189
+ if FCFvalue == 0:
190
+ pfcf_dict[FCFdate] = math.nan
191
+ else:
192
+ pfcf_dict[FCFdate] = round(시가총액_억 / FCFvalue, 2)
193
+
194
+ pfcf_dict = mymongo.C1034.del_unnamed_key(pfcf_dict)
195
+
196
+ mylogger.debug(f'pfcf_dict : {pfcf_dict}')
197
+ return pfcf_dict
198
+
199
+ def _calc가치지표(self, 시가총액_억: float, refresh: bool) -> tuple:
200
+ self.c104.page = 'c104q'
201
+
202
+ fcf_dict = self._calcFCF(refresh)
203
+ pfcf_dict = self._calcPFCF(시가총액_억, fcf_dict)
204
+
205
+ d, pcr_dict = self.c104.find('PCR', remove_yoy=True, del_unnamed_key=True, refresh=refresh)
206
+ return d, fcf_dict, pfcf_dict, pcr_dict
207
+
208
+ def _score(self) -> list:
209
+ return [0,]
210
+
211
+ def _generate_data(self, refresh: bool) -> MilData:
212
+ mylogger.info(f"In generate_data..refresh : {refresh}")
213
+ 시가총액_억 = self.get_marketcap억(refresh)
214
+ mylogger.info(f"{self} 시가총액(억) : {시가총액_억}")
215
+
216
+ d1, 주주수익률, 재무활동현금흐름 = self._calc주주수익률(시가총액_억, refresh)
217
+ mylogger.info(f"{self} 주주수익률 : {주주수익률}, {d1}")
218
+
219
+ d2, 이익지표, 영업활동현금흐름, 지배주주당기순이익 = self._calc이익지표(시가총액_억, refresh)
220
+ mylogger.info(f"{self} 이익지표 : {이익지표}, {d2}")
221
+
222
+ d3, roic_r, roic_dict, roe_r, roe106, roa_r = self._calc투자수익률(refresh)
223
+ d4, fcf_dict, pfcf_dict, pcr_dict = self._calc가치지표(시가총액_억, refresh)
224
+
225
+ score = self._score()
226
+
227
+ try:
228
+ date_list = Tools.date_set(d1, d2, d3, d4)
229
+ except ValueError:
230
+ # 날짜 데이터가 없는경우
231
+ date_list = ['',]
232
+
233
+ return MilData(
234
+ code= self.code,
235
+ name= self.name,
236
+
237
+ 시가총액억= 시가총액_억,
238
+
239
+ 주주수익률= 주주수익률,
240
+ 재무활동현금흐름= 재무활동현금흐름,
241
+
242
+ 이익지표= 이익지표,
243
+ 영업활동현금흐름= 영업활동현금흐름,
244
+ 지배주주당기순이익= 지배주주당기순이익,
245
+
246
+ roic_r= roic_r,
247
+ roic_dict= roic_dict,
248
+ roe_r= roe_r,
249
+ roe_106= roe106,
250
+ roa_r= roa_r,
251
+
252
+ fcf_dict= fcf_dict,
253
+ pfcf_dict= pfcf_dict,
254
+ pcr_dict= pcr_dict,
255
+
256
+ score= score,
257
+ date = date_list,
258
+ )
259
+
260
+ def get(self, refresh = False, verbose = True) -> MilData:
261
+ """
262
+ MilData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
263
+ :param refresh:
264
+ :return:
265
+ """
266
+ redis_name = f"{self.code}_mil"
267
+ mylogger.info(f"{self} MilData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
268
+ if verbose:
269
+ print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
270
+
271
+ def fetch_generate_data(refresh_in: bool) -> dict:
272
+ return asdict(self._generate_data(refresh_in))
273
+
274
+ return MilData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time))
@@ -0,0 +1,295 @@
1
+ import os
2
+ from dataclasses import dataclass, asdict
3
+ from collections import OrderedDict
4
+ from typing import Tuple
5
+ import math
6
+
7
+ from utils_hj3415 import tools, setup_logger
8
+ from db_hj3415 import myredis
9
+
10
+ from analyser_hj3415.analyser.eval.common import Tools
11
+
12
+
13
+ mylogger = setup_logger(__name__,'WARNING')
14
+ expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
15
+
16
+
17
+ @dataclass
18
+ class RedData:
19
+ """
20
+ A data structure for financial data representation and calculations.
21
+
22
+ This class is designed to encapsulate financial details related to a company,
23
+ including calculations for business value, property value, debt evaluation, and
24
+ associated metrics. It validates specific attributes upon initialization and is
25
+ useful for financial data analysis.
26
+
27
+ Attributes:
28
+ code (str): A 6-digit numeric string representing the company or entity's code.
29
+ name (str): The name of the company or entity.
30
+ 사업가치 (float): Business value calculated as net income attributable to controlling
31
+ shareholders divided by expected return rate.
32
+ 지배주주당기순이익 (float): Net income attributable to controlling shareholders.
33
+ expect_earn (float): Expected return rate.
34
+ 재산가치 (float): Property value calculated as current assets minus 1.2
35
+ times current liabilities, plus fixed assets under investment properties.
36
+ 유동자산 (float): Current assets of the company.
37
+ 유동부채 (float): Current liabilities of the company.
38
+ 투자자산 (float): Investment assets within fixed assets.
39
+ 투자부동산 (float): Investment real estate property.
40
+ 부채평가 (float): Debt evaluation, specifically focusing on non-current liabilities.
41
+ 발행주식수 (int): Number of issued shares by the company.
42
+ date (list): List of dates relevant to the financial data.
43
+ red_price (float): Red price associated with the company or entity.
44
+ score (int): Score or rating given to the company or entity.
45
+
46
+ Raises:
47
+ ValueError: If the 'code' attribute is not a 6-digit numeric string.
48
+ """
49
+ code: str
50
+ name: str
51
+
52
+ # 사업가치 계산 - 지배주주지분 당기순이익 / 기대수익률
53
+ 사업가치: float
54
+ 지배주주당기순이익: float
55
+ expect_earn: float
56
+
57
+ # 재산가치 계산 - 유동자산 - (유동부채*1.2) + 고정자산중 투자자산
58
+ 재산가치: float
59
+ 유동자산: float
60
+ 유동부채: float
61
+ 투자자산: float
62
+ 투자부동산: float
63
+
64
+ # 부채평가 - 비유동부채
65
+ 부채평가: float
66
+
67
+ # 발행주식수
68
+ 발행주식수: int
69
+
70
+ date: list
71
+
72
+ red_price: float
73
+ score: int
74
+
75
+ def __post_init__(self):
76
+ if not tools.is_6digit(self.code):
77
+ raise ValueError(f"code는 6자리 숫자형 문자열이어야합니다. (입력값: {self.code})")
78
+
79
+
80
+ class Red:
81
+ """
82
+ Represents a financial analysis object with methods to calculate metrics
83
+ and gather data related to a specific code.
84
+
85
+ The Red class is designed to interact with specific data sources and provide
86
+ tools for financial calculations and analysis. This includes fetching and
87
+ processing information related to liabilities, assets, stock prices, and
88
+ other financial indicators. The class facilitates both specific calculations
89
+ such as 비유동부채(Non-current Liability) and the generation of comprehensive
90
+ financial datasets.
91
+ """
92
+
93
+
94
+ def __init__(self, code: str, expect_earn: float = 0.06):
95
+ assert tools.is_6digit(code), f'Invalid value : {code}'
96
+ mylogger.debug(f"Red : 초기화 ({code})")
97
+ self.c101 = myredis.C101(code)
98
+ self.c103 = myredis.C103(code, 'c103재무상태표q')
99
+
100
+ self.name = self.c101.get_name()
101
+ self._code = code
102
+
103
+ self.expect_earn = expect_earn
104
+
105
+ def __str__(self):
106
+ return f"Red({self.code}/{self.name})"
107
+
108
+ @property
109
+ def code(self) -> str:
110
+ return self._code
111
+
112
+ @code.setter
113
+ def code(self, code: str):
114
+ assert tools.is_6digit(code), f'Invalid value : {code}'
115
+ mylogger.debug(f"Red : 종목코드 변경({self.code} -> {code})")
116
+ self.c101.code = code
117
+ self.c103.code = code
118
+
119
+ self.name = self.c101.get_name()
120
+ self._code = code
121
+
122
+ def _calc비유동부채(self, refresh: bool) -> Tuple[str, float]:
123
+ """유효한 비유동부채 계산
124
+
125
+ 일반적인 경우로 비유동부채를 찾아서 반환한다.\n
126
+ 금융기관의 경우는 간접적으로 계산한다.\n
127
+ """
128
+ mylogger.info(f'In the calc비유동부채... refresh : {refresh}')
129
+ self.c103.page = 'c103재무상태표q'
130
+
131
+ d, 비유동부채 = self.c103.sum_recent_4q('비유동부채', refresh)
132
+ if math.isnan(비유동부채):
133
+ mylogger.warning(f"{self} - 비유동부채가 없는 종목. 수동으로 계산합니다.")
134
+ # 보험관련업종은 예수부채가 없는대신 보험계약부채가 있다...
135
+ d1, v1 = self.c103.latest_value_pop2('예수부채', refresh)
136
+ d2, v2 = self.c103.latest_value_pop2('보험계약부채(책임준비금)', refresh)
137
+ d3, v3 = self.c103.latest_value_pop2('차입부채', refresh)
138
+ d4, v4 = self.c103.latest_value_pop2('기타부채', refresh)
139
+ mylogger.debug(f'예수부채 : {d1}, {v1}')
140
+ mylogger.debug(f'보험계약부채(책임준비금) : {d2}, {v2}')
141
+ mylogger.debug(f'차입부채 : {d3}, {v3}')
142
+ mylogger.debug(f'기타부채 : {d4}, {v4}')
143
+
144
+ try:
145
+ date, *_ = Tools.date_set(d1, d2, d3, d4)
146
+ except ValueError:
147
+ # 날짜 데이터가 없는경우
148
+ date = ''
149
+ 계산된비유동부채value = round(tools.nan_to_zero(v1) + tools.nan_to_zero(v2) + tools.nan_to_zero(v3) + tools.nan_to_zero(v4),1)
150
+ mylogger.info(f"{self} - 계산된 비유동부채 : {계산된비유동부채value}")
151
+ return date, 계산된비유동부채value
152
+ else:
153
+ return d, 비유동부채
154
+
155
+ def _score(self, red_price: int, refresh: bool) -> int:
156
+ """red price와 최근 주가의 괴리율 파악
157
+
158
+ Returns:
159
+ int : 주가와 red price 비교한 괴리율
160
+ """
161
+ try:
162
+ recent_price = tools.to_float(self.c101.get_recent(refresh)['주가'])
163
+ except KeyError:
164
+ return 0
165
+
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점에 가깝게)
171
+
172
+ mylogger.debug(f"최근주가 : {recent_price} red가격 : {red_price} 괴리율 : {tools.to_int(deviation)} score : {score}")
173
+
174
+ return score
175
+
176
+ def _generate_data(self, refresh: bool) -> RedData:
177
+ d1, 지배주주당기순이익 = Tools.calc당기순이익(self.c103, refresh)
178
+ mylogger.debug(f"{self} 지배주주당기순이익: {지배주주당기순이익}")
179
+ d2, 유동자산 = Tools.calc유동자산(self.c103, refresh)
180
+ d3, 유동부채 = Tools.calc유동부채(self.c103, refresh)
181
+ d4, 부채평가 = self._calc비유동부채(refresh)
182
+
183
+ self.c103.page = 'c103재무상태표q'
184
+ d5, 투자자산 = self.c103.latest_value_pop2('투자자산', refresh)
185
+ d6, 투자부동산 = self.c103.latest_value_pop2('투자부동산', refresh)
186
+
187
+ # 사업가치 계산 - 지배주주지분 당기순이익 / 기대수익률
188
+ 사업가치 = round(지배주주당기순이익 / self.expect_earn, 2)
189
+
190
+ # 재산가치 계산 - 유동자산 - (유동부채*1.2) + 고정자산중 투자자산
191
+ 재산가치 = round(유동자산 - (유동부채 * 1.2) + tools.nan_to_zero(투자자산) + tools.nan_to_zero(투자부동산), 2)
192
+
193
+ _, 발행주식수 = self.c103.latest_value_pop2('발행주식수', refresh)
194
+ if math.isnan(발행주식수):
195
+ 발행주식수 = tools.to_int(self.c101.get_recent(refresh).get('발행주식'))
196
+ else:
197
+ 발행주식수 = 발행주식수 * 1000
198
+
199
+ try:
200
+ red_price = round(((사업가치 + 재산가치 - 부채평가) * 100000000) / 발행주식수)
201
+ except (ZeroDivisionError, ValueError):
202
+ red_price = math.nan
203
+
204
+ score = self._score(red_price, refresh)
205
+
206
+ try:
207
+ date_list = Tools.date_set(d1, d2, d3, d4)
208
+ except ValueError:
209
+ # 날짜 데이터가 없는경우
210
+ date_list = ['',]
211
+
212
+ return RedData(
213
+ code = self.code,
214
+ name = self.name,
215
+ 사업가치 = 사업가치,
216
+ 지배주주당기순이익 = 지배주주당기순이익,
217
+ expect_earn = self.expect_earn,
218
+ 재산가치 = 재산가치,
219
+ 유동자산 = 유동자산,
220
+ 유동부채 = 유동부채,
221
+ 투자자산 = 투자자산,
222
+ 투자부동산 = 투자부동산,
223
+ 부채평가 = 부채평가,
224
+ 발행주식수 = 발행주식수,
225
+ date = date_list,
226
+ red_price = red_price,
227
+ score = score,
228
+ )
229
+
230
+ def get(self, refresh = False, verbose = True) -> RedData:
231
+ """
232
+ RedData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
233
+
234
+ redis_name = f"{self.code}_red_data"
235
+
236
+ Fetch or create RedData from Redis cache.
237
+
238
+ This function attempts to retrieve the RedData from a Redis cache. If the data is
239
+ not available or if a refresh is requested, it generates new data and caches
240
+ them back in Redis. The function logs its operations and can provide
241
+ verbose output when specified.
242
+
243
+ Parameters:
244
+ refresh : bool, optional
245
+ Whether to refresh and generate new data instead of using the cached data.
246
+ verbose : bool, optional
247
+ Whether to enable verbose logging/display of additional runtime information.
248
+
249
+ Returns:
250
+ RedData
251
+ The RedData object either retrieved from the cache or newly generated.
252
+
253
+ """
254
+ redis_name = f"{self.code}_red_data"
255
+ mylogger.info(f"{self} RedData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
256
+ if verbose:
257
+ print(f"{self} redisname: '{redis_name}' / expect_earn: {self.expect_earn} / refresh : {refresh} / expire_time : {expire_time/3600}h")
258
+
259
+ def fetch_generate_data(refresh_in: bool) -> dict:
260
+ return asdict(self._generate_data(refresh_in))
261
+
262
+ return RedData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time))
263
+
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
+