analyser_hj3415 2.10.6__py3-none-any.whl → 3.0.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- analyser_hj3415/__init__.py +13 -0
- analyser_hj3415/analyser/__init__.py +0 -0
- analyser_hj3415/analyser/eval/__init__.py +4 -0
- analyser_hj3415/analyser/eval/blue.py +187 -0
- analyser_hj3415/analyser/eval/common.py +267 -0
- analyser_hj3415/analyser/eval/growth.py +110 -0
- analyser_hj3415/analyser/eval/mil.py +274 -0
- analyser_hj3415/analyser/eval/red.py +295 -0
- analyser_hj3415/{score.py → analyser/score.py} +24 -23
- analyser_hj3415/analyser/tsa/__init__.py +2 -0
- analyser_hj3415/analyser/tsa/lstm.py +670 -0
- analyser_hj3415/analyser/tsa/prophet.py +207 -0
- analyser_hj3415/cli.py +20 -89
- {analyser_hj3415-2.10.6.dist-info → analyser_hj3415-3.0.1.dist-info}/METADATA +3 -3
- analyser_hj3415-3.0.1.dist-info/RECORD +22 -0
- analyser_hj3415/eval.py +0 -960
- analyser_hj3415/tsa.py +0 -708
- analyser_hj3415-2.10.6.dist-info/RECORD +0 -14
- {analyser_hj3415-2.10.6.dist-info → analyser_hj3415-3.0.1.dist-info}/WHEEL +0 -0
- {analyser_hj3415-2.10.6.dist-info → analyser_hj3415-3.0.1.dist-info}/entry_points.txt +0 -0
analyser_hj3415/__init__.py
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
from dotenv import load_dotenv
|
2
|
+
from utils_hj3415.tools import get_env_path
|
3
|
+
from utils_hj3415.logger import mylogger
|
4
|
+
|
5
|
+
env_path = get_env_path()
|
6
|
+
if env_path is None:
|
7
|
+
mylogger.warning(f"환경변수 파일(.env)를 찾을수 없습니다. 기본 설정값으로 프로그램을 실행합니다.")
|
8
|
+
load_dotenv(env_path)
|
9
|
+
|
10
|
+
from analyser_hj3415.analyser import eval
|
11
|
+
from analyser_hj3415.analyser import score
|
12
|
+
from analyser_hj3415.analyser import tsa
|
13
|
+
|
File without changes
|
@@ -0,0 +1,187 @@
|
|
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
|
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 BlueData:
|
18
|
+
code: str
|
19
|
+
name: str
|
20
|
+
|
21
|
+
유동비율: float
|
22
|
+
|
23
|
+
이자보상배율_r: float
|
24
|
+
이자보상배율_dict: dict
|
25
|
+
|
26
|
+
순운전자본회전율_r: float
|
27
|
+
순운전자본회전율_dict: dict
|
28
|
+
|
29
|
+
재고자산회전율_r: float
|
30
|
+
재고자산회전율_dict: dict
|
31
|
+
재고자산회전율_c106: dict
|
32
|
+
|
33
|
+
순부채비율_r: float
|
34
|
+
순부채비율_dict: dict
|
35
|
+
|
36
|
+
score: list
|
37
|
+
date: list
|
38
|
+
|
39
|
+
|
40
|
+
class Blue:
|
41
|
+
def __init__(self, code: str):
|
42
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
43
|
+
mylogger.debug(f"Blue : 종목코드 ({code})")
|
44
|
+
|
45
|
+
self.c101 = myredis.C101(code)
|
46
|
+
self.c103 = myredis.C103(code, 'c103재무상태표q')
|
47
|
+
self.c104 = myredis.C104(code, 'c104q')
|
48
|
+
|
49
|
+
self.name = self.c101.get_name()
|
50
|
+
self._code = code
|
51
|
+
|
52
|
+
def __str__(self):
|
53
|
+
return f"Blue({self.code}/{self.name})"
|
54
|
+
|
55
|
+
@property
|
56
|
+
def code(self) -> str:
|
57
|
+
return self._code
|
58
|
+
|
59
|
+
@code.setter
|
60
|
+
def code(self, code: str):
|
61
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
62
|
+
mylogger.debug(f"Blue : 종목코드 변경({self.code} -> {code})")
|
63
|
+
|
64
|
+
self.c101.code = code
|
65
|
+
self.c103.code = code
|
66
|
+
self.c104.code = code
|
67
|
+
|
68
|
+
self.name = self.c101.get_name()
|
69
|
+
self._code = code
|
70
|
+
|
71
|
+
def _calc유동비율(self, pop_count: int, refresh: bool) -> Tuple[str, float]:
|
72
|
+
"""유동비율계산 - Blue에서 사용
|
73
|
+
|
74
|
+
c104q에서 최근유동비율 찾아보고 유효하지 않거나 \n
|
75
|
+
100이하인 경우에는수동으로 계산해서 다시 한번 평가해 본다.\n
|
76
|
+
"""
|
77
|
+
mylogger.info(f'In the calc유동비율... refresh : {refresh}')
|
78
|
+
self.c104.page = 'c104q'
|
79
|
+
|
80
|
+
유동비율date, 유동비율value = self.c104.latest_value('유동비율', pop_count=pop_count)
|
81
|
+
mylogger.info(f'{self} 유동비율 : {유동비율value}/({유동비율date})')
|
82
|
+
|
83
|
+
if math.isnan(유동비율value) or 유동비율value < 100:
|
84
|
+
유동자산date, 유동자산value = Tools.calc유동자산(self.c103, refresh)
|
85
|
+
유동부채date, 유동부채value = Tools.calc유동부채(self.c103, refresh)
|
86
|
+
|
87
|
+
self.c103.page = 'c103현금흐름표q'
|
88
|
+
추정영업현금흐름date, 추정영업현금흐름value = self.c103.sum_recent_4q('영업활동으로인한현금흐름', refresh)
|
89
|
+
mylogger.debug(f'{self} 계산전 유동비율 : {유동비율value} / ({유동비율date})')
|
90
|
+
|
91
|
+
계산된유동비율 = 0
|
92
|
+
try:
|
93
|
+
계산된유동비율 = round(((유동자산value + 추정영업현금흐름value) / 유동부채value) * 100, 2)
|
94
|
+
except ZeroDivisionError:
|
95
|
+
mylogger.info(f'유동자산: {유동자산value} + 추정영업현금흐름: {추정영업현금흐름value} / 유동부채: {유동부채value}')
|
96
|
+
계산된유동비율 = float('inf')
|
97
|
+
finally:
|
98
|
+
mylogger.debug(f'{self} 계산된 유동비율 : {계산된유동비율}')
|
99
|
+
|
100
|
+
try:
|
101
|
+
date, *_ = Tools.date_set(유동자산date, 유동부채date, 추정영업현금흐름date)
|
102
|
+
except ValueError:
|
103
|
+
# 날짜 데이터가 없는경우
|
104
|
+
date = ''
|
105
|
+
mylogger.warning(f'{self} 유동비율 이상(100 이하 또는 nan) : {유동비율value} -> 재계산 : {계산된유동비율}')
|
106
|
+
return date, 계산된유동비율
|
107
|
+
else:
|
108
|
+
return 유동비율date, 유동비율value
|
109
|
+
|
110
|
+
def _score(self) -> list:
|
111
|
+
return [0 ,]
|
112
|
+
|
113
|
+
def _generate_data(self, refresh: bool) -> BlueData:
|
114
|
+
d1, 유동비율 = self._calc유동비율(pop_count=3, refresh=refresh)
|
115
|
+
mylogger.info(f'유동비율 {유동비율} / [{d1}]')
|
116
|
+
|
117
|
+
재고자산회전율_c106 = myredis.C106.make_like_c106(self.code, 'c104q', '재고자산회전율', refresh)
|
118
|
+
|
119
|
+
self.c104.page = 'c104y'
|
120
|
+
_, 이자보상배율_dict = self.c104.find('이자보상배율', remove_yoy=True, refresh=refresh)
|
121
|
+
_, 순운전자본회전율_dict = self.c104.find('순운전자본회전율', remove_yoy=True, refresh=refresh)
|
122
|
+
_, 재고자산회전율_dict = self.c104.find('재고자산회전율', remove_yoy=True, refresh=refresh)
|
123
|
+
_, 순부채비율_dict = self.c104.find('순부채비율', remove_yoy=True, refresh=refresh)
|
124
|
+
|
125
|
+
self.c104.page = 'c104q'
|
126
|
+
d6, 이자보상배율_r = self.c104.latest_value_pop2('이자보상배율', refresh)
|
127
|
+
d7, 순운전자본회전율_r = self.c104.latest_value_pop2('순운전자본회전율', refresh)
|
128
|
+
d8, 재고자산회전율_r = self.c104.latest_value_pop2('재고자산회전율', refresh)
|
129
|
+
d9, 순부채비율_r = self.c104.latest_value_pop2('순부채비율', refresh)
|
130
|
+
|
131
|
+
if len(이자보상배율_dict) == 0:
|
132
|
+
mylogger.warning(f'empty dict - 이자보상배율 : {이자보상배율_r} / {이자보상배율_dict}')
|
133
|
+
|
134
|
+
if len(순운전자본회전율_dict) == 0:
|
135
|
+
mylogger.warning(f'empty dict - 순운전자본회전율 : {순운전자본회전율_r} / {순운전자본회전율_dict}')
|
136
|
+
|
137
|
+
if len(재고자산회전율_dict) == 0:
|
138
|
+
mylogger.warning(f'empty dict - 재고자산회전율 : {재고자산회전율_r} / {재고자산회전율_dict}')
|
139
|
+
|
140
|
+
if len(순부채비율_dict) == 0:
|
141
|
+
mylogger.warning(f'empty dict - 순부채비율 : {순부채비율_r} / {순부채비율_dict}')
|
142
|
+
|
143
|
+
score = self._score()
|
144
|
+
|
145
|
+
try:
|
146
|
+
date_list = Tools.date_set(d1, d6, d7, d8, d9)
|
147
|
+
except ValueError:
|
148
|
+
# 날짜 데이터가 없는경우
|
149
|
+
date_list = ['' ,]
|
150
|
+
|
151
|
+
return BlueData(
|
152
|
+
code= self.code,
|
153
|
+
name= self.name,
|
154
|
+
유동비율= 유동비율,
|
155
|
+
이자보상배율_r= 이자보상배율_r,
|
156
|
+
이자보상배율_dict= 이자보상배율_dict,
|
157
|
+
|
158
|
+
순운전자본회전율_r= 순운전자본회전율_r,
|
159
|
+
순운전자본회전율_dict= 순운전자본회전율_dict,
|
160
|
+
|
161
|
+
재고자산회전율_r= 재고자산회전율_r,
|
162
|
+
재고자산회전율_dict= 재고자산회전율_dict,
|
163
|
+
재고자산회전율_c106= 재고자산회전율_c106,
|
164
|
+
|
165
|
+
순부채비율_r= 순부채비율_r,
|
166
|
+
순부채비율_dict= 순부채비율_dict,
|
167
|
+
|
168
|
+
score= score,
|
169
|
+
date= date_list,
|
170
|
+
)
|
171
|
+
|
172
|
+
def get(self, refresh = False, verbose = True) -> BlueData:
|
173
|
+
"""
|
174
|
+
BlueData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
175
|
+
:param refresh:
|
176
|
+
:return:
|
177
|
+
"""
|
178
|
+
redis_name = f"{self.code}_blue"
|
179
|
+
mylogger.info(f"{self} BlueData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
180
|
+
if verbose:
|
181
|
+
print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time /3600}h")
|
182
|
+
|
183
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
184
|
+
return asdict(self._generate_data(refresh_in))
|
185
|
+
|
186
|
+
return BlueData \
|
187
|
+
(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time))
|
@@ -0,0 +1,267 @@
|
|
1
|
+
import math
|
2
|
+
from typing import Tuple
|
3
|
+
|
4
|
+
from db_hj3415 import myredis
|
5
|
+
from utils_hj3415.tools import nan_to_zero
|
6
|
+
from utils_hj3415 import setup_logger
|
7
|
+
|
8
|
+
mylogger = setup_logger(__name__,'WARNING')
|
9
|
+
|
10
|
+
|
11
|
+
class Tools:
|
12
|
+
@staticmethod
|
13
|
+
def cal_deviation(v1: float, v2: float) -> float:
|
14
|
+
"""
|
15
|
+
Calculates the percentage deviation between two values.
|
16
|
+
|
17
|
+
This method computes the percentage deviation of the second value
|
18
|
+
from the first value based on the formula:
|
19
|
+
deviation = abs((v1 - v2) / v1) * 100. In the event the first value is
|
20
|
+
zero (division by zero), the function will return NaN to signify
|
21
|
+
an invalid computation.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
v1 (float): The reference value. It represents the base for the relative
|
25
|
+
deviation calculation.
|
26
|
+
v2 (float): The value to compare against the reference.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
float: The computed percentage deviation. Returns NaN if the reference
|
30
|
+
value (v1) is zero.
|
31
|
+
"""
|
32
|
+
try:
|
33
|
+
deviation = abs((v1 - v2) / v1) * 100
|
34
|
+
except ZeroDivisionError:
|
35
|
+
deviation = math.nan
|
36
|
+
return deviation
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def date_set(*args) -> list:
|
40
|
+
"""
|
41
|
+
인자로 받은 값의 비유효한 내용 제거(None,nan)하고 중복된 항목 제거하고 리스트로 반환한다.
|
42
|
+
|
43
|
+
여기서 set의 의미는 집합을 뜻함
|
44
|
+
|
45
|
+
Filters and returns a list of unique non-null, non-empty values from
|
46
|
+
the provided arguments.
|
47
|
+
|
48
|
+
This static method processes the input arguments to retain only unique
|
49
|
+
values that are not empty strings, NaN values, or None. The result is
|
50
|
+
returned as a list.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
*args: Arbitrary positional arguments to be filtered.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
list: A list of unique values after filtering out invalid entries.
|
57
|
+
"""
|
58
|
+
return [i for i in {*args} if i != "" and i is not math.nan and i is not None]
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def calc당기순이익(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
62
|
+
"""
|
63
|
+
지배지분 당기순이익 계산
|
64
|
+
|
65
|
+
일반적인 경우로는 직전 지배주주지분 당기순이익을 찾아서 반환한다.
|
66
|
+
|
67
|
+
금융기관의 경우는 지배당기순이익이 없기 때문에 계산을 통해서 간접적으로 구한다.
|
68
|
+
|
69
|
+
Calculates "지배당기순이익" (Controlling Comprehensive Income) based on the given
|
70
|
+
financial data. The method retrieves or computes the value utilizing methods from
|
71
|
+
the `myredis.C103` class. It handles missing or 'Not-a-Number' conditions by
|
72
|
+
manually calculating from quarterly and annual financial figures. Logs the process
|
73
|
+
at various stages for debugging and auditing.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
c103 (myredis.C103): An instance containing financial data and utilities to
|
77
|
+
access specific data points for the targeted calculation.
|
78
|
+
refresh (bool): A flag to determine whether or not to refresh the data
|
79
|
+
while accessing or computing financial values.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
Tuple[str, float]: A tuple where the first item is the most relevant date for
|
83
|
+
the calculated or retrieved value, and the second item is the calculated
|
84
|
+
or retrieved "지배당기순이익" (Controlling Comprehensive Income).
|
85
|
+
"""
|
86
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
87
|
+
|
88
|
+
mylogger.info(f'{c103.code} / {name} Tools : 당기순이익 계산.. refresh : {refresh}')
|
89
|
+
c103.page = 'c103재무상태표q'
|
90
|
+
|
91
|
+
d1, 지배당기순이익 = c103.latest_value_pop2('*(지배)당기순이익', refresh)
|
92
|
+
mylogger.debug(f"*(지배)당기순이익: {지배당기순이익}")
|
93
|
+
|
94
|
+
if math.isnan(지배당기순이익):
|
95
|
+
mylogger.warning(f"{c103.code} / {name} - (지배)당기순이익이 없는 종목. 수동으로 계산합니다.")
|
96
|
+
c103.page = 'c103손익계산서q'
|
97
|
+
d2, 최근4분기당기순이익 = c103.sum_recent_4q('당기순이익', refresh)
|
98
|
+
mylogger.debug(f"{c103.code} / {name} - 최근4분기당기순이익 : {최근4분기당기순이익}")
|
99
|
+
c103.page = 'c103재무상태표y'
|
100
|
+
d3, 비지배당기순이익 = c103.latest_value_pop2('*(비지배)당기순이익', refresh)
|
101
|
+
mylogger.debug(f"{c103.code} / {name} - 비지배당기순이익y : {비지배당기순이익}")
|
102
|
+
# 가변리스트 언패킹으로 하나의 날짜만 사용하고 나머지는 버린다.
|
103
|
+
# 여기서 *_는 “나머지 값을 다 무시하겠다”는 의미
|
104
|
+
mylogger.debug(f"d2:{d2}, d3: {d3}")
|
105
|
+
try:
|
106
|
+
date, *_ = Tools.date_set(d2, d3)
|
107
|
+
except ValueError:
|
108
|
+
# 날짜 데이터가 없는경우
|
109
|
+
date = ''
|
110
|
+
계산된지배당기순이익 = round(최근4분기당기순이익 - nan_to_zero(비지배당기순이익), 1)
|
111
|
+
mylogger.debug(f"{c103.code} / {name} - 계산된 지배당기순이익 : {계산된지배당기순이익}")
|
112
|
+
return date, 계산된지배당기순이익
|
113
|
+
else:
|
114
|
+
return d1, 지배당기순이익
|
115
|
+
|
116
|
+
@staticmethod
|
117
|
+
def calc유동자산(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
118
|
+
"""
|
119
|
+
유효한 유동자산 계산
|
120
|
+
|
121
|
+
일반적인 경우로 유동자산을 찾아서 반환한다.
|
122
|
+
|
123
|
+
금융기관의 경우는 간접적으로 계산한다.
|
124
|
+
|
125
|
+
Calculates the current assets for a given company code.
|
126
|
+
|
127
|
+
For a specified company, the function calculates the recent 4-quarter
|
128
|
+
sum of current assets if available. If the data is not available or
|
129
|
+
contains invalid values, it attempts to calculate the current assets
|
130
|
+
manually using financial asset data such as cash equivalents, trading
|
131
|
+
securities, available-for-sale securities, and held-to-maturity securities.
|
132
|
+
|
133
|
+
Logs relevant information and warnings during the calculation process,
|
134
|
+
including any cases where data is unavailable or a manual calculation
|
135
|
+
is required.
|
136
|
+
|
137
|
+
Parameters:
|
138
|
+
c103 (myredis.C103): The instance representing financial data of a
|
139
|
+
specific company. This includes methods to extract and calculate
|
140
|
+
various data points.
|
141
|
+
refresh (bool): Indicator flag to determine whether to refresh the
|
142
|
+
underlying data before performing calculations.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Tuple[str, float]: A tuple containing the date associated with the
|
146
|
+
financial data and the calculated or retrieved value of current
|
147
|
+
assets. If dates are not available, the date field may be empty.
|
148
|
+
"""
|
149
|
+
|
150
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
151
|
+
|
152
|
+
mylogger.info(f'{c103.code} / {name} Tools : 유동자산계산... refresh : {refresh}')
|
153
|
+
c103.page = 'c103재무상태표q'
|
154
|
+
|
155
|
+
d, 유동자산 = c103.sum_recent_4q('유동자산', refresh)
|
156
|
+
if math.isnan(유동자산):
|
157
|
+
mylogger.warning(f"{c103.code} / {name} - 유동자산이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
158
|
+
d1, v1 = c103.latest_value_pop2('현금및예치금', refresh)
|
159
|
+
d2, v2 = c103.latest_value_pop2('단기매매금융자산', refresh)
|
160
|
+
d3, v3 = c103.latest_value_pop2('매도가능금융자산', refresh)
|
161
|
+
d4, v4 = c103.latest_value_pop2('만기보유금융자산', refresh)
|
162
|
+
mylogger.debug(f'{c103.code} / {name} 현금및예치금 : {d1}, {v1}')
|
163
|
+
mylogger.debug(f'{c103.code} / {name} 단기매매금융자산 : {d2}, {v2}')
|
164
|
+
mylogger.debug(f'{c103.code} / {name} 매도가능금융자산 : {d3}, {v3}')
|
165
|
+
mylogger.debug(f'{c103.code} / {name} 만기보유금융자산 : {d4}, {v4}')
|
166
|
+
|
167
|
+
try:
|
168
|
+
date, *_ = Tools.date_set(d1, d2, d3, d4)
|
169
|
+
except ValueError:
|
170
|
+
# 날짜 데이터가 없는경우
|
171
|
+
date = ''
|
172
|
+
계산된유동자산value = round(
|
173
|
+
nan_to_zero(v1) + nan_to_zero(v2) + nan_to_zero(v3) + nan_to_zero(v4), 1)
|
174
|
+
|
175
|
+
mylogger.info(f"{c103.code} / {name} - 계산된 유동자산 : {계산된유동자산value}")
|
176
|
+
return date, 계산된유동자산value
|
177
|
+
else:
|
178
|
+
return d, 유동자산
|
179
|
+
|
180
|
+
@staticmethod
|
181
|
+
def calc유동부채(c103: myredis.C103, refresh: bool) -> Tuple[str, float]:
|
182
|
+
"""
|
183
|
+
유효한 유동부채 계산
|
184
|
+
|
185
|
+
일반적인 경우로 유동부채를 찾아서 반환한다.
|
186
|
+
|
187
|
+
금융기관의 경우는 간접적으로 계산한다.
|
188
|
+
|
189
|
+
Calculate '유동부채' (Current Liabilities) based on financial data of a specific entity.
|
190
|
+
|
191
|
+
This static method computes the recent '유동부채' value either from the sum of recent four
|
192
|
+
quarters using predefined keys or calculates manually if no valid data is available.
|
193
|
+
It includes logging for intermediate steps and supports handling missing values by logging
|
194
|
+
warnings and attempting a composed manual computation using alternative financial terms.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
c103 (myredis.C103): The object containing financial data and operations for obtaining the required data.
|
198
|
+
refresh (bool): A flag to indicate whether to fetch the latest data forcibly.
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
Tuple[str, float]: A tuple containing the `date` of financial data and the computed '유동부채' value.
|
202
|
+
"""
|
203
|
+
|
204
|
+
name = myredis.Corps(c103.code, 'c101').get_name(refresh=refresh)
|
205
|
+
|
206
|
+
mylogger.info(f'{c103.code} / {name} Tools : 유동부채계산... refresh : {refresh}')
|
207
|
+
c103.page = 'c103재무상태표q'
|
208
|
+
|
209
|
+
d, 유동부채 = c103.sum_recent_4q('유동부채', refresh)
|
210
|
+
if math.isnan(유동부채):
|
211
|
+
mylogger.warning(f"{c103.code} / {name} - 유동부채가 없는 종목. 수동으로 계산합니다.")
|
212
|
+
d1, v1 = c103.latest_value_pop2('당기손익인식(지정)금융부채', refresh)
|
213
|
+
d2, v2 = c103.latest_value_pop2('당기손익-공정가치측정금융부채', refresh)
|
214
|
+
d3, v3 = c103.latest_value_pop2('매도파생결합증권', refresh)
|
215
|
+
d4, v4 = c103.latest_value_pop2('단기매매금융부채', refresh)
|
216
|
+
mylogger.debug(f'{c103.code} / {name} 당기손익인식(지정)금융부채 : {d1}, {v1}')
|
217
|
+
mylogger.debug(f'{c103.code} / {name} 당기손익-공정가치측정금융부채 : {d2}, {v2}')
|
218
|
+
mylogger.debug(f'{c103.code} / {name} 매도파생결합증권 : {d3}, {v3}')
|
219
|
+
mylogger.debug(f'{c103.code} / {name} 단기매매금융부채 : {d4}, {v4}')
|
220
|
+
|
221
|
+
try:
|
222
|
+
date, *_ = Tools.date_set(d1, d2, d3, d4)
|
223
|
+
except ValueError:
|
224
|
+
# 날짜 데이터가 없는경우
|
225
|
+
date = ''
|
226
|
+
계산된유동부채value = round(
|
227
|
+
nan_to_zero(v1) + nan_to_zero(v2) + nan_to_zero(v3) + nan_to_zero(v4), 1)
|
228
|
+
|
229
|
+
mylogger.info(f"{c103.code} / {name} - 계산된 유동부채 : {계산된유동부채value}")
|
230
|
+
return date, 계산된유동부채value
|
231
|
+
else:
|
232
|
+
return d, 유동부채
|
233
|
+
|
234
|
+
|
235
|
+
"""
|
236
|
+
- 각분기의 합이 연이 아닌 타이틀(즉 sum_4q를 사용하면 안됨)
|
237
|
+
'*(지배)당기순이익'
|
238
|
+
'*(비지배)당기순이익'
|
239
|
+
'장기차입금'
|
240
|
+
'현금및예치금'
|
241
|
+
'매도가능금융자산'
|
242
|
+
'매도파생결합증권'
|
243
|
+
'만기보유금융자산'
|
244
|
+
'당기손익-공정가치측정금융부채'
|
245
|
+
'당기손익인식(지정)금융부채'
|
246
|
+
'단기매매금융자산'
|
247
|
+
'단기매매금융부채'
|
248
|
+
'예수부채'
|
249
|
+
'차입부채'
|
250
|
+
'기타부채'
|
251
|
+
'보험계약부채(책임준비금)'
|
252
|
+
'*CAPEX'
|
253
|
+
'ROE'
|
254
|
+
"""
|
255
|
+
|
256
|
+
"""
|
257
|
+
- sum_4q를 사용해도 되는 타이틀
|
258
|
+
'자산총계'
|
259
|
+
'당기순이익'
|
260
|
+
'유동자산'
|
261
|
+
'유동부채'
|
262
|
+
'비유동부채'
|
263
|
+
|
264
|
+
'영업활동으로인한현금흐름'
|
265
|
+
'재무활동으로인한현금흐름'
|
266
|
+
'ROIC'
|
267
|
+
"""
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import os
|
2
|
+
from dataclasses import dataclass, asdict
|
3
|
+
|
4
|
+
from utils_hj3415 import tools, setup_logger
|
5
|
+
from db_hj3415 import myredis
|
6
|
+
|
7
|
+
from analyser_hj3415.analyser.eval.common import Tools
|
8
|
+
|
9
|
+
|
10
|
+
mylogger = setup_logger(__name__,'WARNING')
|
11
|
+
expire_time = tools.to_int(os.getenv('DEFAULT_EXPIRE_TIME_H', 48)) * 3600
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass()
|
15
|
+
class GrowthData:
|
16
|
+
code: str
|
17
|
+
name: str
|
18
|
+
|
19
|
+
매출액증가율_r: float
|
20
|
+
매출액증가율_dict: dict
|
21
|
+
|
22
|
+
영업이익률_c106: dict
|
23
|
+
|
24
|
+
score: list
|
25
|
+
date: list
|
26
|
+
|
27
|
+
|
28
|
+
class Growth:
|
29
|
+
def __init__(self, code: str):
|
30
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
31
|
+
mylogger.debug(f"Growth : 종목코드 ({code})")
|
32
|
+
|
33
|
+
self.c101 = myredis.C101(code)
|
34
|
+
self.c104 = myredis.C104(code, 'c104q')
|
35
|
+
self.c106 = myredis.C106(code, 'c106q')
|
36
|
+
|
37
|
+
self.name = self.c101.get_name()
|
38
|
+
self._code = code
|
39
|
+
|
40
|
+
def __str__(self):
|
41
|
+
return f"Growth({self.code}/{self.name})"
|
42
|
+
|
43
|
+
@property
|
44
|
+
def code(self) -> str:
|
45
|
+
return self._code
|
46
|
+
|
47
|
+
@code.setter
|
48
|
+
def code(self, code: str):
|
49
|
+
assert tools.is_6digit(code), f'Invalid value : {code}'
|
50
|
+
mylogger.debug(f"Growth : 종목코드 변경({self.code} -> {code})")
|
51
|
+
|
52
|
+
self.c101.code = code
|
53
|
+
self.c104.code = code
|
54
|
+
self.c106.code = code
|
55
|
+
|
56
|
+
self.name = self.c101.get_name()
|
57
|
+
self._code = code
|
58
|
+
|
59
|
+
def _score(self) -> list:
|
60
|
+
return [0,]
|
61
|
+
|
62
|
+
def _generate_data(self, refresh=False) -> GrowthData:
|
63
|
+
self.c104.page = 'c104y'
|
64
|
+
_, 매출액증가율_dict = self.c104.find('매출액증가율', remove_yoy=True, refresh=refresh)
|
65
|
+
|
66
|
+
self.c104.page = 'c104q'
|
67
|
+
d2, 매출액증가율_r = self.c104.latest_value_pop2('매출액증가율')
|
68
|
+
|
69
|
+
mylogger.info(f'매출액증가율 : {매출액증가율_r} {매출액증가율_dict}')
|
70
|
+
|
71
|
+
# c106 에서 타 기업과 영업이익률 비교
|
72
|
+
self.c106.page = 'c106y'
|
73
|
+
영업이익률_c106 = self.c106.find('영업이익률', refresh)
|
74
|
+
|
75
|
+
score = self._score()
|
76
|
+
|
77
|
+
try:
|
78
|
+
date_list = Tools.date_set(d2)
|
79
|
+
except ValueError:
|
80
|
+
# 날짜 데이터가 없는경우
|
81
|
+
date_list = ['', ]
|
82
|
+
|
83
|
+
return GrowthData(
|
84
|
+
code= self.code,
|
85
|
+
name= self.name,
|
86
|
+
|
87
|
+
매출액증가율_r= 매출액증가율_r,
|
88
|
+
매출액증가율_dict= 매출액증가율_dict,
|
89
|
+
|
90
|
+
영업이익률_c106= 영업이익률_c106,
|
91
|
+
|
92
|
+
score= score,
|
93
|
+
date= date_list,
|
94
|
+
)
|
95
|
+
|
96
|
+
def get(self, refresh = False, verbose = True) -> GrowthData:
|
97
|
+
"""
|
98
|
+
GrowthData 형식의 데이터를 계산하여 리턴하고 레디스 캐시에 저장한다.
|
99
|
+
:param refresh:
|
100
|
+
:return:
|
101
|
+
"""
|
102
|
+
redis_name = f"{self.code}_growth"
|
103
|
+
mylogger.info(f"{self} GrowthData를 레디스캐시에서 가져오거나 새로 생성합니다.. refresh : {refresh}")
|
104
|
+
if verbose:
|
105
|
+
print(f"{self} redisname: '{redis_name}' / refresh : {refresh} / expire_time : {expire_time/3600}h")
|
106
|
+
|
107
|
+
def fetch_generate_data(refresh_in: bool) -> dict:
|
108
|
+
return asdict(self._generate_data(refresh_in))
|
109
|
+
|
110
|
+
return GrowthData(**myredis.Base.fetch_and_cache_data(redis_name, refresh, fetch_generate_data, refresh, timer=expire_time))
|