analyser_hj3415 2.0.1__py2.py3-none-any.whl → 2.1.0__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/.DS_Store +0 -0
- analyser_hj3415/analysers/eval.py +289 -0
- analyser_hj3415/{report.py → analysers/report.py} +23 -59
- analyser_hj3415/{score.py → analysers/score.py} +56 -61
- analyser_hj3415/{db/evaltools.py → tools.py} +102 -79
- analyser_hj3415/trash.py +210 -0
- {analyser_hj3415-2.0.1.dist-info → analyser_hj3415-2.1.0.dist-info}/METADATA +5 -9
- analyser_hj3415-2.1.0.dist-info/RECORD +14 -0
- analyser_hj3415/db/.DS_Store +0 -0
- analyser_hj3415/db/chk_db.py +0 -240
- analyser_hj3415/db/mongo.py +0 -934
- analyser_hj3415/eval.py +0 -382
- analyser_hj3415-2.0.1.dist-info/RECORD +0 -16
- /analyser_hj3415/{db/__init__.py → run.py} +0 -0
- {analyser_hj3415-2.0.1.dist-info → analyser_hj3415-2.1.0.dist-info}/LICENSE +0 -0
- {analyser_hj3415-2.0.1.dist-info → analyser_hj3415-2.1.0.dist-info}/WHEEL +0 -0
- {analyser_hj3415-2.0.1.dist-info → analyser_hj3415-2.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,9 @@
|
|
1
1
|
import math
|
2
2
|
from typing import Tuple
|
3
|
-
from
|
4
|
-
|
5
|
-
from .mongo import C101, C103, C104, Corps
|
3
|
+
from db_hj3415.myredis import C101, C103, C104
|
6
4
|
|
7
5
|
import logging
|
6
|
+
|
8
7
|
logger = logging.getLogger(__name__)
|
9
8
|
formatter = logging.Formatter('%(levelname)s: [%(name)s] %(message)s')
|
10
9
|
ch = logging.StreamHandler()
|
@@ -13,24 +12,16 @@ logger.addHandler(ch)
|
|
13
12
|
logger.setLevel(logging.WARNING)
|
14
13
|
|
15
14
|
|
16
|
-
def
|
15
|
+
def set_data(*args) -> list:
|
17
16
|
"""
|
18
|
-
|
19
|
-
|
17
|
+
비유효한 내용 제거(None,nan)하고 중복된 항목 제거하고 리스트로 반환한다.
|
18
|
+
:param args:
|
19
|
+
:return:
|
20
20
|
"""
|
21
|
-
|
22
|
-
# 입력받은 데이터를 중복되는 것을 제하기 위해 집합으로 변환한다.
|
23
|
-
d_set = {i for i in args}
|
24
|
-
for i in d_set:
|
25
|
-
logger.debug(i)
|
26
|
-
# 하나씩 꺼내서 빈문자가 아니면 반환한다.
|
27
|
-
if i != "" and i is not math.nan and i is not None:
|
28
|
-
return i
|
29
|
-
else:
|
30
|
-
return None
|
21
|
+
return [i for i in {*args} if i != "" and i is not math.nan and i is not None]
|
31
22
|
|
32
23
|
|
33
|
-
def calc당기순이익(
|
24
|
+
def calc당기순이익(code: str) -> Tuple[str, float]:
|
34
25
|
"""지배지분 당기순이익 계산
|
35
26
|
|
36
27
|
일반적인 경우로는 직전 지배주주지분 당기순이익을 찾아서 반환한다.\n
|
@@ -38,25 +29,24 @@ def calc당기순이익(client, code: str) -> Tuple[str, float]:
|
|
38
29
|
계산을 통해서 간접적으로 구한다.\n
|
39
30
|
"""
|
40
31
|
logger.debug(f'In the calc당기순이익... code:{code}')
|
41
|
-
c103q = C103(
|
32
|
+
c103q = C103(code, 'c103재무상태표q')
|
42
33
|
try:
|
43
|
-
|
44
|
-
logger.info(f'*(지배)당기순이익 : {profit_dict}')
|
45
|
-
return c103q.latest_value('*(지배)당기순이익', nan_to_zero=True)
|
34
|
+
return c103q.latest_value_pop2('*(지배)당기순이익')
|
46
35
|
except:
|
47
|
-
|
36
|
+
logger.warning(f"{code} - (지배)당기순이익이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
48
37
|
c103q.page = 'c103손익계산서q'
|
49
|
-
최근당기순이익date, 최근당기순이익value = c103q.sum_recent_4q('당기순이익'
|
38
|
+
최근당기순이익date, 최근당기순이익value = c103q.sum_recent_4q('당기순이익')
|
50
39
|
c103q.page = 'c103재무상태표q'
|
51
|
-
비지배당기순이익date, 비지배당기순이익value= c103q.
|
40
|
+
비지배당기순이익date, 비지배당기순이익value = c103q.latest_value_pop2('*(비지배)당기순이익')
|
52
41
|
|
53
|
-
|
42
|
+
# 가변리스트 언패킹으로 하나의 날짜만 사용하고 나머지는 버린다.
|
43
|
+
date, *_ = set_data(최근당기순이익date, 비지배당기순이익date)
|
54
44
|
계산된지배당기순이익value = 최근당기순이익value - 비지배당기순이익value
|
55
45
|
|
56
46
|
return date, 계산된지배당기순이익value
|
57
47
|
|
58
48
|
|
59
|
-
def calc유동자산(
|
49
|
+
def calc유동자산(code: str) -> Tuple[str, float]:
|
60
50
|
"""유효한 유동자산 계산
|
61
51
|
|
62
52
|
일반적인 경우로 유동자산을 찾아서 반환한다.\n
|
@@ -64,29 +54,27 @@ def calc유동자산(client, code: str) -> Tuple[str, float]:
|
|
64
54
|
Red와 Blue에서 사용한다.\n
|
65
55
|
"""
|
66
56
|
logger.debug(f'In the calc유동자산... code:{code}')
|
67
|
-
c103q = C103(
|
57
|
+
c103q = C103(code, 'c103재무상태표q')
|
68
58
|
try:
|
69
|
-
|
70
|
-
logger.info(f'유동자산 : {asset_dict}')
|
71
|
-
return c103q.sum_recent_4q('유동자산', nan_to_zero=True)
|
59
|
+
return c103q.sum_recent_4q('유동자산')
|
72
60
|
except:
|
73
|
-
|
74
|
-
d1, v1 = c103q.
|
75
|
-
d2, v2 = c103q.
|
76
|
-
d3, v3 = c103q.
|
77
|
-
d4, v4 = c103q.
|
61
|
+
logger.warning(f"{code} - 유동자산이 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
62
|
+
d1, v1 = c103q.latest_value_pop2('현금및예치금')
|
63
|
+
d2, v2 = c103q.latest_value_pop2('단기매매금융자산')
|
64
|
+
d3, v3 = c103q.latest_value_pop2('매도가능금융자산')
|
65
|
+
d4, v4 = c103q.latest_value_pop2('만기보유금융자산')
|
78
66
|
logger.debug(f'현금및예치금 : {d1}, {v1}')
|
79
67
|
logger.debug(f'단기매매금융자산 : {d2}, {v2}')
|
80
68
|
logger.debug(f'매도가능금융자산 : {d3}, {v3}')
|
81
69
|
logger.debug(f'만기보유금융자산 : {d4}, {v4}')
|
82
70
|
|
83
|
-
date =
|
71
|
+
date, *_ = set_data(d1, d2, d3, d4)
|
84
72
|
계산된유동자산value = v1 + v2 + v3 + v4
|
85
73
|
|
86
74
|
return date, 계산된유동자산value
|
87
75
|
|
88
76
|
|
89
|
-
def calc유동부채(
|
77
|
+
def calc유동부채(code: str) -> Tuple[str, float]:
|
90
78
|
"""유효한 유동부채 계산
|
91
79
|
|
92
80
|
일반적인 경우로 유동부채를 찾아서 반환한다.\n
|
@@ -94,29 +82,27 @@ def calc유동부채(client, code: str) -> Tuple[str, float]:
|
|
94
82
|
Red와 Blue에서 사용한다.\n
|
95
83
|
"""
|
96
84
|
logger.debug(f'In the calc유동부채... code:{code}')
|
97
|
-
c103q = C103(
|
85
|
+
c103q = C103(code, 'c103재무상태표q')
|
98
86
|
try:
|
99
|
-
|
100
|
-
logger.debug(f'유동부채 : {debt_dict}')
|
101
|
-
return c103q.sum_recent_4q('유동부채', nan_to_zero=True)
|
87
|
+
return c103q.sum_recent_4q('유동부채')
|
102
88
|
except:
|
103
|
-
|
104
|
-
d1, v1 = c103q.
|
105
|
-
d2, v2 = c103q.
|
106
|
-
d3, v3 = c103q.
|
107
|
-
d4, v4 = c103q.
|
89
|
+
logger.warning(f"{code} - 유동부채가 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
90
|
+
d1, v1 = c103q.latest_value_pop2('당기손익인식(지정)금융부채')
|
91
|
+
d2, v2 = c103q.latest_value_pop2('당기손익-공정가치측정금융부채')
|
92
|
+
d3, v3 = c103q.latest_value_pop2('매도파생결합증권')
|
93
|
+
d4, v4 = c103q.latest_value_pop2('단기매매금융부채')
|
108
94
|
logger.debug(f'당기손익인식(지정)금융부채 : {d1}, {v1}')
|
109
95
|
logger.debug(f'당기손익-공정가치측정금융부채 : {d2}, {v2}')
|
110
96
|
logger.debug(f'매도파생결합증권 : {d3}, {v3}')
|
111
97
|
logger.debug(f'단기매매금융부채 : {d4}, {v4}')
|
112
98
|
|
113
|
-
date =
|
99
|
+
date, *_ = set_data(d1, d2, d3, d4)
|
114
100
|
계산된유동부채value = v1 + v2 + v3 + v4
|
115
101
|
|
116
102
|
return date, 계산된유동부채value
|
117
103
|
|
118
104
|
|
119
|
-
def calc비유동부채(
|
105
|
+
def calc비유동부채(code: str) -> Tuple[str, float]:
|
120
106
|
"""유효한 비유동부채 계산
|
121
107
|
|
122
108
|
일반적인 경우로 비유동부채를 찾아서 반환한다.\n
|
@@ -124,46 +110,44 @@ def calc비유동부채(client, code: str) -> Tuple[str, float]:
|
|
124
110
|
Red와 Blue에서 사용한다.\n
|
125
111
|
"""
|
126
112
|
logger.debug(f'In the calc비유동부채... code:{code}')
|
127
|
-
c103q = C103(
|
113
|
+
c103q = C103(code, 'c103재무상태표q')
|
128
114
|
try:
|
129
|
-
|
130
|
-
logger.debug(f'비유동부채 : {debt_dict}')
|
131
|
-
return c103q.sum_recent_4q('비유동부채', nan_to_zero=True)
|
115
|
+
return c103q.sum_recent_4q('비유동부채')
|
132
116
|
except:
|
133
|
-
|
117
|
+
logger.warning(f"{code} - 비유동부채가 없는 종목. 수동으로 계산합니다(금융관련업종일 가능성있음).")
|
134
118
|
# 보험관련업종은 예수부채가 없는대신 보험계약부채가 있다...
|
135
|
-
d1, v1 = c103q.
|
136
|
-
d2, v2 = c103q.
|
137
|
-
d3, v3 = c103q.
|
138
|
-
d4, v4 = c103q.
|
119
|
+
d1, v1 = c103q.latest_value_pop2('예수부채')
|
120
|
+
d2, v2 = c103q.latest_value_pop2('보험계약부채(책임준비금)')
|
121
|
+
d3, v3 = c103q.latest_value_pop2('차입부채')
|
122
|
+
d4, v4 = c103q.latest_value_pop2('기타부채')
|
139
123
|
logger.debug(f'예수부채 : {d1}, {v1}')
|
140
124
|
logger.debug(f'보험계약부채(책임준비금) : {d2}, {v2}')
|
141
125
|
logger.debug(f'차입부채 : {d3}, {v3}')
|
142
126
|
logger.debug(f'기타부채 : {d4}, {v4}')
|
143
127
|
|
144
|
-
date =
|
128
|
+
date, *_ = set_data(d1, d2, d3, d4)
|
145
129
|
계산된비유동부채value = v1 + v2 + v3 + v4
|
146
130
|
|
147
131
|
return date, 계산된비유동부채value
|
148
132
|
|
149
133
|
|
150
|
-
def calc유동비율(
|
134
|
+
def calc유동비율(code: str, pop_count: int) -> Tuple[str, float]:
|
151
135
|
"""유동비율계산 - Blue에서 사용
|
152
136
|
|
153
137
|
c104q에서 최근유동비율 찾아보고 유효하지 않거나 \n
|
154
138
|
100이하인 경우에는수동으로 계산해서 다시 한번 평가해 본다.\n
|
155
139
|
"""
|
156
140
|
logger.debug(f'In the calc유동비율... code:{code}')
|
157
|
-
c104q = C104(
|
158
|
-
유동비율date, 유동비율value = c104q.latest_value('유동비율', pop_count=pop_count
|
141
|
+
c104q = C104(code, 'c104q')
|
142
|
+
유동비율date, 유동비율value = c104q.mymongo_c1034.latest_value('유동비율', pop_count=pop_count)
|
159
143
|
logger.debug(f'{code} 유동비율 : {유동비율value}({유동비율date})')
|
160
144
|
|
161
145
|
if math.isnan(유동비율value) or 유동비율value < 100:
|
162
146
|
logger.warning('유동비율 is under 100 or nan..so we will recalculate..')
|
163
|
-
유동자산date, 유동자산value = calc유동자산(
|
164
|
-
유동부채date, 유동부채value = calc유동부채(
|
147
|
+
유동자산date, 유동자산value = calc유동자산(code)
|
148
|
+
유동부채date, 유동부채value = calc유동부채(code)
|
165
149
|
|
166
|
-
c103q = C103(
|
150
|
+
c103q = C103(code, 'c103현금흐름표q')
|
167
151
|
추정영업현금흐름date, 추정영업현금흐름value = c103q.sum_recent_4q('영업활동으로인한현금흐름')
|
168
152
|
logger.debug(f'{code} 계산전 유동비율 : {유동비율value}({유동비율date})')
|
169
153
|
|
@@ -175,17 +159,36 @@ def calc유동비율(client, code: str, pop_count: int) -> Tuple[str, float]:
|
|
175
159
|
계산된유동비율 = float('inf')
|
176
160
|
finally:
|
177
161
|
logger.debug(f'{code} 계산된 유동비율 : {계산된유동비율}')
|
178
|
-
|
162
|
+
date, *_ = set_data(유동자산date, 유동부채date, 추정영업현금흐름date)
|
163
|
+
return date, 계산된유동비율
|
179
164
|
else:
|
180
165
|
return 유동비율date, 유동비율value
|
181
166
|
|
182
167
|
|
183
|
-
|
184
|
-
|
168
|
+
"""
|
169
|
+
FCF는 “Free Cash Flow”의 약자로, 한국어로는 “자유 현금 흐름”이라고 합니다. FCF는 기업이 운영 활동을 통해 창출한 현금 중에서 영업 및 자본적 지출을
|
170
|
+
제외하고 남은 현금을 의미합니다. 이는 기업의 재무 건전성을 평가하는 중요한 지표로 사용됩니다. 자유 현금 흐름은 기업이 부채를 상환하고, 배당금을 지급하며,
|
171
|
+
추가적인 투자를 할 수 있는 자금을 나타냅니다.
|
172
|
+
|
173
|
+
FCF의 중요성
|
185
174
|
|
186
|
-
|
187
|
-
|
175
|
+
1. 재무 건전성 평가: FCF는 기업이 실제로 얼마나 많은 현금을 창출하고 있는지를 보여줍니다. 이는 기업의 재무 건전성을 평가하는 데 중요한 지표입니다.
|
176
|
+
2. 투자 결정: 투자자들은 FCF를 통해 기업의 성장 가능성을 평가하고, 투자 결정을 내리는 데 참고합니다.
|
177
|
+
3. 배당 지급 능력: FCF는 기업이 주주들에게 배당금을 지급할 수 있는 능력을 나타냅니다.
|
178
|
+
4. 부채 상환: 기업은 FCF를 이용해 부채를 상환하고, 재무 구조를 개선할 수 있습니다.
|
188
179
|
|
180
|
+
CAPEX는 “Capital Expenditures”의 약자로, 한국어로는 “자본적 지출”이라고 합니다. CAPEX는 기업이 장기 자산을 구입하거나 유지하는 데 사용하는
|
181
|
+
비용을 의미합니다. 이는 기업이 장기적인 성장을 위해 자산을 확장, 업그레이드 또는 유지하는 데 필요한 비용입니다. 이러한 자산에는 부동산, 건물, 기계,
|
182
|
+
장비 등이 포함됩니다.
|
183
|
+
|
184
|
+
CAPEX가 거의 없거나 아예 없는 업종에서도 자유 현금 흐름(Free Cash Flow, FCF)을 계산할 수 있습니다. CAPEX가 없는 경우,
|
185
|
+
계산식에서 CAPEX 부분을 0으로 처리하면 됩니다.
|
186
|
+
"""
|
187
|
+
|
188
|
+
|
189
|
+
def findFCF(code: str) -> dict:
|
190
|
+
"""
|
191
|
+
FCF 계산
|
189
192
|
Returns:
|
190
193
|
dict: 계산된 fcf 딕셔너리 또는 영업현금흐름 없는 경우 - {}
|
191
194
|
|
@@ -193,10 +196,10 @@ def findFCF(client, code: str) -> dict:
|
|
193
196
|
CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.\n
|
194
197
|
|
195
198
|
"""
|
196
|
-
c103y = C103(
|
197
|
-
영업활동현금흐름_dict = c103y.
|
199
|
+
c103y = C103(code, 'c103현금흐름표y')
|
200
|
+
_, 영업활동현금흐름_dict = c103y.find_without_yoy('영업활동으로인한현금흐름')
|
198
201
|
c103y.page = 'c103재무상태표y'
|
199
|
-
capex = c103y.
|
202
|
+
_, capex = c103y.find_without_yoy('*CAPEX')
|
200
203
|
|
201
204
|
logger.debug(f'영업활동현금흐름 {영업활동현금흐름_dict}')
|
202
205
|
logger.debug(f'CAPEX {capex}')
|
@@ -206,6 +209,7 @@ def findFCF(client, code: str) -> dict:
|
|
206
209
|
|
207
210
|
if len(capex) == 0:
|
208
211
|
# CAPEX 가 없는 업종은 영업활동현금흐름을 그대로 사용한다.
|
212
|
+
logger.warning(f"{code} - CAPEX가 없는 업종으로 영업현금흐름을 그대로 사용합니다..")
|
209
213
|
return 영업활동현금흐름_dict
|
210
214
|
|
211
215
|
# 영업 활동으로 인한 현금 흐름에서 CAPEX 를 각 연도별로 빼주어 fcf 를 구하고 리턴값으로 fcf 딕셔너리를 반환한다.
|
@@ -223,8 +227,22 @@ def findFCF(client, code: str) -> dict:
|
|
223
227
|
return dict(sorted(r_dict.items(), reverse=False))
|
224
228
|
|
225
229
|
|
226
|
-
|
227
|
-
|
230
|
+
"""
|
231
|
+
PFCF의 중요성
|
232
|
+
1. 기업 가치 평가: PFCF는 기업이 창출하는 현금 흐름에 비해 주가가 적정한지 평가하는 데 사용됩니다. 낮은 PFCF는 주가가 상대적으로 저평가되었음을 나타낼
|
233
|
+
수 있고, 높은 PFCF는 주가가 상대적으로 고평가되었음을 나타낼 수 있습니다.
|
234
|
+
2. 투자 결정: 투자자들은 PFCF를 사용하여 현금 흐름 창출 능력 대비 주가가 매력적인지를 판단하고, 투자 결정을 내리는 데 참고합니다.
|
235
|
+
3. 비교 분석: 같은 산업 내 다른 기업들과 비교하여, 어느 기업이 더 효율적으로 현금 흐름을 창출하는지를 평가할 수 있습니다.
|
236
|
+
|
237
|
+
PFCF의 한계
|
238
|
+
|
239
|
+
•산업 특성: PFCF는 산업마다 적정한 수준이 다를 수 있습니다. 예를 들어, 기술 산업과 제조 산업의 적정 PFCF는 다를 수 있습니다.
|
240
|
+
•일회성 항목: 특정 연도의 일회성 비용이나 수익이 FCF에 영향을 미칠 수 있으며, 이는 PFCF 계산에 왜곡을 가져올 수 있습니다.
|
241
|
+
"""
|
242
|
+
|
243
|
+
|
244
|
+
def findPFCF(code: str) -> dict:
|
245
|
+
"""Price to Free Cash Flow Ratio(주가 대비 자유 현금 흐름 비율)계산
|
228
246
|
|
229
247
|
PFCF = 시가총액 / FCF
|
230
248
|
|
@@ -232,12 +250,12 @@ def findPFCF(client, code: str) -> dict:
|
|
232
250
|
https://www.investopedia.com/terms/p/pricetofreecashflow.asp
|
233
251
|
"""
|
234
252
|
# marketcap 계산 (fcf가 억 단위라 시가총액을 억으로 나눠서 단위를 맞춰 준다)
|
235
|
-
marketcap억 = get_marketcap(
|
253
|
+
marketcap억 = get_marketcap(code) / 100000000
|
236
254
|
if math.isnan(marketcap억):
|
237
255
|
return {}
|
238
256
|
|
239
257
|
# pfcf 계산
|
240
|
-
fcf_dict = findFCF(
|
258
|
+
fcf_dict = findFCF(code)
|
241
259
|
logger.debug(f'fcf_dict : {fcf_dict}')
|
242
260
|
pfcf_dict = {}
|
243
261
|
for FCFdate, FCFvalue in fcf_dict.items():
|
@@ -249,9 +267,14 @@ def findPFCF(client, code: str) -> dict:
|
|
249
267
|
return pfcf_dict
|
250
268
|
|
251
269
|
|
252
|
-
def get_marketcap(
|
253
|
-
|
270
|
+
def get_marketcap(code: str) -> float:
|
271
|
+
"""
|
272
|
+
시가총액(원) 반환
|
273
|
+
:param code:
|
274
|
+
:return:
|
275
|
+
"""
|
276
|
+
c101 = C101(code)
|
254
277
|
try:
|
255
278
|
return int(c101.get_recent()['시가총액'])
|
256
279
|
except KeyError:
|
257
|
-
return
|
280
|
+
return math.nan
|
analyser_hj3415/trash.py
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
def _make_df_part(db_addr, codes: list, q):
|
4
|
+
def make_record(my_client, my_code: str) -> dict:
|
5
|
+
# 장고에서 사용할 eval 테이블을 만들기 위해 각각의 레코드를 구성하는 함수
|
6
|
+
c101 = mongo.C101(my_client, my_code).get_recent()
|
7
|
+
|
8
|
+
red_dict = red(my_client, my_code)
|
9
|
+
mil_dict = mil(my_client, my_code)
|
10
|
+
growth_dict = growth(my_client, my_code)
|
11
|
+
|
12
|
+
mil_date = mil_dict['date']
|
13
|
+
red_date = red_dict['date']
|
14
|
+
growth_date = growth_dict['date']
|
15
|
+
|
16
|
+
return {
|
17
|
+
'code': c101['코드'],
|
18
|
+
'종목명': c101['종목명'],
|
19
|
+
'주가': utils.to_int(c101['주가']),
|
20
|
+
'PER': utils.to_float(c101['PER']),
|
21
|
+
'PBR': utils.to_float(c101['PBR']),
|
22
|
+
'시가총액': utils.to_float(c101['시가총액']),
|
23
|
+
'RED': utils.to_int(red_dict['red_price']),
|
24
|
+
'주주수익률': utils.to_float(mil_dict['주주수익률']),
|
25
|
+
'이익지표': utils.to_float(mil_dict['이익지표']),
|
26
|
+
'ROIC': utils.to_float(mil_dict['투자수익률']['ROIC']),
|
27
|
+
'ROE': utils.to_float(mil_dict['투자수익률']['ROE']),
|
28
|
+
'PFCF': utils.to_float(mongo.Corps.latest_value(mil_dict['가치지표']['PFCF'])[1]),
|
29
|
+
'PCR': utils.to_float(mongo.Corps.latest_value(mil_dict['가치지표']['PCR'])[1]),
|
30
|
+
'매출액증가율': utils.to_float(growth_dict['매출액증가율'][0]),
|
31
|
+
'date': list(set(mil_date + red_date + growth_date))
|
32
|
+
}
|
33
|
+
# 각 코어별로 디비 클라이언트를 만들어야만 한다. 안그러면 에러발생
|
34
|
+
client = mongo.connect_mongo(db_addr)
|
35
|
+
t = len(codes)
|
36
|
+
d = []
|
37
|
+
for i, code in enumerate(codes):
|
38
|
+
print(f'{i+1}/{t} {code}')
|
39
|
+
try:
|
40
|
+
d.append(make_record(client, code))
|
41
|
+
except:
|
42
|
+
logger.error(f'error on {code}')
|
43
|
+
continue
|
44
|
+
df = pd.DataFrame(d)
|
45
|
+
logger.info(df)
|
46
|
+
q.put(df)
|
47
|
+
|
48
|
+
|
49
|
+
def make_today_eval_df(client, refresh: bool = False) -> pd.DataFrame:
|
50
|
+
""" 멀티프로세싱을 사용하여 전체 종목의 eval 을 데이터프레임으로 만들어 반환
|
51
|
+
|
52
|
+
기본값으로 refresh 는 False 로 설정되어 당일자의 저장된 데이터프레임이 있으면 새로 생성하지 않고 mongo DB를 이용한다.
|
53
|
+
"""
|
54
|
+
today_str = datetime.datetime.today().strftime('%Y%m%d')
|
55
|
+
df = mongo.EvalByDate(client, today_str).load_df()
|
56
|
+
if refresh or len(df) == 0:
|
57
|
+
codes_in_db = mongo.Corps.get_all_codes(client)
|
58
|
+
|
59
|
+
print('*' * 25, f"Eval all using multiprocess(refresh={refresh})", '*' * 25)
|
60
|
+
print(f'Total {len(codes_in_db)} items..')
|
61
|
+
logger.debug(codes_in_db)
|
62
|
+
n, divided_list = utils.code_divider_by_cpu_core(codes_in_db)
|
63
|
+
|
64
|
+
addr = mongo.extract_addr_from_client(client)
|
65
|
+
|
66
|
+
start_time = time.time()
|
67
|
+
q = Queue()
|
68
|
+
ths = []
|
69
|
+
for i in range(n):
|
70
|
+
ths.append(Process(target=_make_df_part, args=(addr, divided_list[i], q)))
|
71
|
+
for i in range(n):
|
72
|
+
ths[i].start()
|
73
|
+
|
74
|
+
df_list = []
|
75
|
+
for i in range(n):
|
76
|
+
df_list.append(q.get())
|
77
|
+
# 부분데이터프레임들을 하나로 합침
|
78
|
+
final_df = pd.concat(df_list, ignore_index=True)
|
79
|
+
|
80
|
+
for i in range(n):
|
81
|
+
ths[i].join()
|
82
|
+
|
83
|
+
print(f'Total spent time : {round(time.time() - start_time, 2)} sec.')
|
84
|
+
logger.debug(final_df)
|
85
|
+
print(f"Save to mongo db(db: eval col: {today_str})")
|
86
|
+
mongo.EvalByDate(client, today_str).save_df(final_df)
|
87
|
+
else:
|
88
|
+
print(f"Use saved dataframe from mongo db..")
|
89
|
+
final_df = df
|
90
|
+
return final_df
|
91
|
+
|
92
|
+
|
93
|
+
def yield_valid_spac(client) -> tuple:
|
94
|
+
"""
|
95
|
+
전체 스팩주의 현재가를 평가하여 2000원 이하인 경우 yield한다.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
tuple: (code, name, price)
|
99
|
+
"""
|
100
|
+
codes = mongo.Corps.get_all_codes(client)
|
101
|
+
logger.debug(f'len(codes) : {len(codes)}')
|
102
|
+
print('<<< Finding valuable SPAC >>>')
|
103
|
+
for i, code in enumerate(codes):
|
104
|
+
name = mongo.Corps.get_name(client, code)
|
105
|
+
logger.debug(f'code : {code} name : {name}')
|
106
|
+
if '스팩' in str(name):
|
107
|
+
logger.debug(f'>>> spac - code : {code} name : {name}')
|
108
|
+
price, _, _ = utils.get_price_now(code=code)
|
109
|
+
if price <= 2000:
|
110
|
+
logger.warning(f'현재가:{price}')
|
111
|
+
print(f"code: {code} name: {name}, price: {price}")
|
112
|
+
yield code, name, price
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
class GetDFTest(unittest.TestCase):
|
117
|
+
def test_make_df_part(self):
|
118
|
+
codes = ['025320', '000040', '060280', '003240']
|
119
|
+
from multiprocessing import Queue
|
120
|
+
q = Queue()
|
121
|
+
eval._make_df_part(addr, codes, q)
|
122
|
+
|
123
|
+
def test_get_df(self):
|
124
|
+
print(eval.make_today_eval_df(client, refresh=True))
|
125
|
+
print(eval.make_today_eval_df(client, refresh=False))
|
126
|
+
|
127
|
+
|
128
|
+
class SpacTest(unittest.TestCase):
|
129
|
+
def test_valid_spac(self):
|
130
|
+
for code, name, price in eval.yield_valid_spac(client):
|
131
|
+
print(code, name, price)
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
def mil(code: str) -> Tuple[int, int, int, int]:
|
137
|
+
"""
|
138
|
+
- 재무활동현금흐름이 마이너스라는 것은 배당급 지급했거나, 자사주 매입했거나, 부채를 상환한 상태임.
|
139
|
+
- 반대는 채권자로 자금을 조달했거나 신주를 발행했다는 의미
|
140
|
+
<주주수익률> - 재무활동현금흐름/시가총액 => 5%이상인가?
|
141
|
+
|
142
|
+
투하자본수익률(ROIC)가 30%이상인가
|
143
|
+
ROE(자기자본이익률) 20%이상이면 아주 우수 다른 투자이익률과 비교해볼것 10%미만이면 별로...단, 부채비율을 확인해야함.
|
144
|
+
|
145
|
+
이익지표 ...영업현금흐름이 순이익보다 많은가 - 결과값이 음수인가..
|
146
|
+
|
147
|
+
FCF는 영업현금흐름에서 자본적 지출(유·무형투자 비용)을 차감한 순수한 현금력이라 할 수 있다.
|
148
|
+
말 그대로 자유롭게(Free) 사용할 수 있는 여윳돈을 뜻한다.
|
149
|
+
잉여현금흐름이 플러스라면 미래의 투자나 채무상환에 쓸 재원이 늘어난 것이다.
|
150
|
+
CAPEX(Capital expenditures)는 미래의 이윤을 창출하기 위해 지출된 비용을 말한다.
|
151
|
+
이는 기업이 고정자산을 구매하거나, 유효수명이 당회계년도를 초과하는 기존의 고정자산에 대한 투자에 돈이 사용될 때 발생한다.
|
152
|
+
|
153
|
+
잉여현금흐름이 마이너스일때는 설비투자가 많은 시기라 주가가 약세이며 이후 설비투자 마무리되면서 주가가 상승할수 있다.
|
154
|
+
주가는 잉여현금흐름이 증가할때 상승하는 경향이 있다.
|
155
|
+
fcf = 영업현금흐름 - capex
|
156
|
+
|
157
|
+
가치지표평가
|
158
|
+
price to fcf 계산
|
159
|
+
https://www.investopedia.com/terms/p/pricetofreecashflow.asp
|
160
|
+
pcr보다 정확하게 주식의 가치를 평가할수 있음. 10배이하 추천
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
tuple: 주주수익률, 이익지표, 투자수익률, PFCF포인트
|
164
|
+
"""
|
165
|
+
mil_dict = eval.mil(code)
|
166
|
+
|
167
|
+
print(pprint.pformat(mil_dict, width=200))
|
168
|
+
|
169
|
+
# 주주수익률 평가
|
170
|
+
if math.isnan(mil_dict['주주수익률']):
|
171
|
+
score1 = 0
|
172
|
+
else:
|
173
|
+
주주수익률평가 = math.ceil(mil_dict['주주수익률'] - (eval.EXPECT_EARN * 100))
|
174
|
+
score1 = 0 if 0 > 주주수익률평가 else 주주수익률평가
|
175
|
+
|
176
|
+
# 이익지표 평가
|
177
|
+
score2 = 10 if mil_dict['이익지표'] < 0 else 0
|
178
|
+
|
179
|
+
# 투자수익률 평가
|
180
|
+
MAX3 = 20
|
181
|
+
score3 = 0
|
182
|
+
roic = mil_dict['투자수익률']['ROIC']
|
183
|
+
roe = mil_dict['투자수익률']['ROE']
|
184
|
+
if math.isnan(roic) or roic <= 0:
|
185
|
+
# roic 가 비정상이라 평가할 수 없는 경우
|
186
|
+
if 10 < roe <= 20:
|
187
|
+
score3 += round(MAX3 * 0.333)
|
188
|
+
elif 20 < roe:
|
189
|
+
score3 += round(MAX3 * 0.666)
|
190
|
+
elif 0 < roic:
|
191
|
+
# roic 로 평가할 수 있는 경우
|
192
|
+
if 0 < roic <= 15:
|
193
|
+
score3 += round(MAX3 * 0.333)
|
194
|
+
elif 15 < roic <= 30:
|
195
|
+
score3 += round(MAX3 * 0.666)
|
196
|
+
elif 30 < roic:
|
197
|
+
score3 += MAX3
|
198
|
+
|
199
|
+
# PFCF 평가
|
200
|
+
pfcf_dict = mil_dict['가치지표']['PFCF']
|
201
|
+
_, pfcf = mongo.Corps.latest_value(pfcf_dict)
|
202
|
+
|
203
|
+
logger.debug(f'recent pfcf {_}, {pfcf}')
|
204
|
+
try:
|
205
|
+
p = round(-40 * math.log10(pfcf) + 40)
|
206
|
+
except ValueError:
|
207
|
+
p = 0
|
208
|
+
score4 = 0 if 0 > p else p
|
209
|
+
|
210
|
+
return score1, score2, score3, score4
|
@@ -1,15 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: analyser_hj3415
|
3
|
-
Version: 2.0
|
3
|
+
Version: 2.1.0
|
4
4
|
Summary: Stock analyser and database processing programs
|
5
5
|
Author-email: Hyungjin Kim <hj3415@gmail.com>
|
6
6
|
Description-Content-Type: text/markdown
|
7
7
|
Classifier: License :: OSI Approved :: MIT License
|
8
|
-
Requires-Dist:
|
9
|
-
Requires-Dist:
|
10
|
-
Requires-Dist: sqlalchemy>=2.0.31
|
11
|
-
Requires-Dist: utils-hj3415>=2.0.1
|
12
|
-
Requires-Dist: scraper2-hj3415>=0.4.0
|
8
|
+
Requires-Dist: utils-hj3415>=2.6.0
|
9
|
+
Requires-Dist: db-hj3415>=3.3.2
|
13
10
|
Project-URL: Home, https://www.hyungjin.kr
|
14
11
|
|
15
12
|
### analyser-hj3415
|
@@ -20,12 +17,11 @@ analyser_hj3415 manage the database.
|
|
20
17
|
---
|
21
18
|
#### Requirements
|
22
19
|
|
23
|
-
scrapy>=2.11.2
|
24
20
|
pandas>=2.2.2
|
21
|
+
pymongo>=4.8.0
|
25
22
|
sqlalchemy>=2.0.31
|
26
|
-
selenium>=4.22.0
|
27
23
|
utils-hj3415>=2.0.1
|
28
|
-
|
24
|
+
scraper-hj3415>=2.0.0
|
29
25
|
|
30
26
|
---
|
31
27
|
#### API
|
@@ -0,0 +1,14 @@
|
|
1
|
+
analyser_hj3415/.DS_Store,sha256=OQfTSOHL-zjUtnNyBpNRVUJUstR4j6I7jihKDFQQmME,6148
|
2
|
+
analyser_hj3415/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
analyser_hj3415/cli.py,sha256=qzRnpDRJvQnQevSKHBpKbTsBjmSWllZjzTV4z_alg2A,4891
|
4
|
+
analyser_hj3415/run.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
analyser_hj3415/tools.py,sha256=SNsrnL5CKmKAdFkmlwgREMIkWDRi6N9LngCdhhhop3Y,13606
|
6
|
+
analyser_hj3415/trash.py,sha256=vHrv8Q61QOkcwhmWfrj_yVdsdd5MoAxs9gXMOJEjMHM,8360
|
7
|
+
analyser_hj3415/analysers/eval.py,sha256=mlHi6EPc8l8O6vKnWyX4Cz1BaeGhUpWM8gVZRNhm-JU,13299
|
8
|
+
analyser_hj3415/analysers/report.py,sha256=whggmLXl7yF-BjQ6JKgxmhILT2T4uFP-rit_BSes9xM,9189
|
9
|
+
analyser_hj3415/analysers/score.py,sha256=DoLac-PXQrA-GfkEHRD52R-8FFJD-eYLCPTg3oEkNGA,16213
|
10
|
+
analyser_hj3415-2.1.0.dist-info/entry_points.txt,sha256=dHaCM3eOAGONmxTWuRVqo9Zyq2C7J5TZmpH0PD6FW5k,103
|
11
|
+
analyser_hj3415-2.1.0.dist-info/LICENSE,sha256=QVKTp0dTnB5xG8RLgG17LwSWCKNEzYoVVM6KjoCPKc0,1079
|
12
|
+
analyser_hj3415-2.1.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
13
|
+
analyser_hj3415-2.1.0.dist-info/METADATA,sha256=h7E4uyJhGWiRAOVfZ-ApChWxDfm8pOi8Iu-iz1Byqpg,6417
|
14
|
+
analyser_hj3415-2.1.0.dist-info/RECORD,,
|
analyser_hj3415/db/.DS_Store
DELETED
Binary file
|