analyser_hj3415 2.0.1__py2.py3-none-any.whl → 2.1.0__py2.py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|