analyser_hj3415 2.10.6__py3-none-any.whl → 3.0.1__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 +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))
|