analyser_hj3415 2.9.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # 필요한 라이브러리 불러오기
2
+ import numpy as np
3
+ from sklearn.linear_model import LinearRegression
4
+ from sklearn.model_selection import train_test_split
5
+ import matplotlib.pyplot as plt
6
+
7
+ # 1. 데이터 준비 (주택 면적, 가격)
8
+ # 예를 들어 면적에 따른 주택 가격 데이터 (면적: X, 가격: y)
9
+ X = np.array([[1500], [2000], [2500], [3000], [3500], [4000]]) # 면적 (단위: square feet)
10
+ y = np.array([300000, 400000, 500000, 600000, 700000, 800000]) # 가격 (단위: dollars)
11
+
12
+ # 2. 학습 데이터와 테스트 데이터를 나누기 (80% 학습, 20% 테스트)
13
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
14
+
15
+ # 3. 선형 회귀 모델 생성
16
+ model = LinearRegression()
17
+
18
+ # 4. 모델을 학습시키기 (train 데이터를 사용)
19
+ model.fit(X_train, y_train)
20
+
21
+ # 5. 테스트 데이터로 예측 수행
22
+ y_pred = model.predict(X_test)
23
+
24
+ # 6. 예측 결과 출력
25
+ print("실제 값:", y_test)
26
+ print("예측 값:", y_pred)
27
+
28
+ # 7. 시각화를 통해 학습 결과 확인
29
+ plt.scatter(X_train, y_train, color='blue', label='Training data') # 학습 데이터
30
+ plt.scatter(X_test, y_test, color='green', label='Test data') # 실제 값
31
+ plt.plot(X_test, y_pred, color='red', label='Prediction') # 예측된 값
32
+ plt.xlabel('House Size (square feet)')
33
+ plt.ylabel('Price (dollars)')
34
+ plt.legend()
35
+ plt.show()
36
+
37
+ # 9. 모델 평가 (R^2 스코어)
38
+ r2_score = model.score(X_test, y_test)
39
+ print(f"모델의 R^2 스코어: {r2_score:.2f}")
@@ -0,0 +1,342 @@
1
+ import math
2
+ import numpy
3
+ import pprint
4
+ import copy
5
+ from typing import Tuple
6
+
7
+ from db_hj3415 import myredis, mymongo
8
+ from utils_hj3415 import utils
9
+
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+ formatter = logging.Formatter('%(levelname)s: [%(name)s] %(message)s')
14
+ ch = logging.StreamHandler()
15
+ ch.setFormatter(formatter)
16
+ logger.addHandler(ch)
17
+ logger.setLevel(logging.WARNING)
18
+
19
+
20
+
21
+ def mil(code: str, expect_earn: float) -> Tuple[int, int, int, int]:
22
+ """
23
+ 이익 지표 일반적인 기준 설정
24
+
25
+ 1. 비율이 10% 이상일 경우 주의:
26
+ • 이 비율이 10%를 넘을 경우, 이는 기업의 순이익이 현금 흐름과 비교하여 너무 높아졌음을 나타냅니다.
27
+ • 순이익이 과도하게 부풀려졌을 가능성이 있으며, 회계적인 항목(예: 미수금, 재고자산의 평가차익 등)이나 비현금성 이익이 이익에 크게 기여하고 있을 수 있습니다. 이런 경우, 현금 흐름이 제대로 발생하지 않고 있음에도 순이익만 높게 기록되는 상황이 발생할 수 있어 이익의 질이 낮다고 평가됩니다.
28
+ • 특히, 비율이 지속적으로 높다면 재무 건전성에 문제가 있을 수 있습니다.
29
+ 2. 비율이 5% 이상 ~ 10% 미만일 때:
30
+ • 비율이 5% ~ 10% 사이라면, 기업의 이익이 여전히 현금 흐름과 괴리가 있지만, 업종에 따라 일정 부분은 허용될 수 있습니다.
31
+ • 예를 들어, 기술 기업이나 고성장 산업에서는 초기 투자나 R&D 비용이 많기 때문에 순이익과 현금 흐름의 차이가 다소 발생할 수 있습니다. 이런 경우 비율이 5%를 넘더라도 성장성을 고려한 투자 관점에서 용인될 수 있습니다.
32
+ • 다만, 일반적인 제조업 등 현금 창출이 중요한 업종에서는 5% 이상의 괴리도 주의 깊게 볼 필요가 있습니다.
33
+ 3. 비율이 5% 미만일 때:
34
+ • 비율이 5% 미만이라면, 기업의 순이익과 현금 흐름 간의 차이가 크지 않으므로, 이익의 질이 높고 재무 건전성이 양호하다고 평가할 수 있습니다.
35
+ • 특히 음수일 경우, 영업활동에서 벌어들인 현금이 순이익을 초과한다는 것을 의미하므로, 이는 매우 긍정적인 신호입니다. 기업이 순이익보다도 더 많은 현금을 창출하고 있는 상태로, 이익이 실제 현금 흐름으로 뒷받침되고 있음을 나타냅니다.
36
+
37
+ 참고할 수 있는 기준
38
+ 1. 경고 신호: 10% 이상
39
+ • 비율이 10% 이상이라면, 기업의 이익이 실제 현금 흐름과 크게 괴리되어 있음을 의미하며, 이익의 질이 낮고 재무 건전성이 떨어질 가능성이 큽니다. 특히, 이 비율이 지속적으로 높다면, 주의가 필요합니다.
40
+ 2. 주의: 5% ~ 10%
41
+ • 이 비율은 어느 정도 괴리가 있는 상태이며, 업종 특성에 따라 감안할 수 있지만, 일반적인 기업의 경우 주의를 요합니다.
42
+ 3. 안정적: 5% 미만
43
+ • 5% 미만은 비교적 안정적인 상태로 평가될 수 있습니다. 이익과 현금 흐름 간의 괴리가 크지 않기 때문에, 기업의 재무 상태와 이익의 질이 건전하다고 볼 수 있습니다.
44
+ 4. 매우 긍정적: 음수
45
+ • 비율이 음수일 경우, 현금 창출 능력이 매우 우수한 상태입니다. 현금 흐름이 순이익보다 더 크므로, 이는 매우 긍정적인 신호로 평가됩니다.
46
+
47
+ 사례별 해석
48
+
49
+ 1. 비율이 15%:
50
+ • 회사는 순이익이 영업활동현금흐름에 비해 과도하게 높습니다. 이는 비현금성 이익이 많이 포함되어 있거나, 회계적인 처리로 인해 순이익이 부풀려졌을 가능성이 큽니다. 기업의 이익 질에 의문을 가져야 하며, 장기적으로 재무 구조에 문제가 생길 수 있습니다.
51
+ 2. 비율이 7%:
52
+ • 괴리가 존재하지만, 일부 산업에서는 용인될 수 있는 범위입니다. 다만, 현금 흐름이 안정적인지 추가적인 분석이 필요합니다.
53
+ 3. 비율이 -3%:
54
+ • 매우 긍정적인 상황입니다. 회사는 순이익보다 더 많은 현금을 벌어들이고 있어, 이익의 질이 매우 높으며, 주주들에게도 더 많은 이익을 돌려줄 가능성이 큽니다.
55
+
56
+ 기타 고려 사항
57
+
58
+ • 업종별 차이: 기술 기업, 스타트업 등은 고정자산 투자나 R&D 비용이 많아 현금 흐름과 이익이 괴리될 수 있습니다. 이 경우 이익의 질이 낮다고 단정할 수는 없으므로 산업 특성을 고려해야 합니다.
59
+ • 기업의 성장 단계: 빠르게 성장하는 기업일수록 현금 흐름보다 순이익이 클 수 있지만, 시간이 지나면서 현금 창출 능력이 증가할 수 있습니다.
60
+ • 비현금성 항목 분석: 감가상각, 미수금 등의 비현금성 항목이 이익에 얼마나 영향을 미치는지 분석하여 이익의 질을 파악하는 것이 중요합니다.
61
+ ROIC 평가 기준
62
+
63
+ 1. 10% 이상:
64
+ • 일반적으로 ROIC가 10% 이상이면 매우 우수한 기업으로 평가됩니다.
65
+ • 이는 기업이 투자된 자본으로 높은 수익을 창출하고 있음을 의미하며, 자본의 사용 효율성이 매우 높다는 것을 나타냅니다.
66
+ 2. 7% ~ 10%:
67
+ • ROIC가 7%에서 10% 사이라면, 기업은 자본을 비교적 효율적으로 사용하고 있는 상태입니다.
68
+ • 자본비용(WACC)이 이보다 낮다면, 투자자는 기업이 가치 창출을 하고 있다고 판단할 수 있습니다.
69
+ 3. 5% ~ 7%:
70
+ • 이 범위에서는 보통 수준의 효율성을 보여줍니다. ROIC가 자본비용보다 낮지 않다면 투자자에게 긍정적일 수 있지만, 이 수익률이 경쟁사 대비 낮다면 경쟁력이 떨어질 수 있음을 시사할 수 있습니다.
71
+ 4. 5% 미만:
72
+ • ROIC가 5% 미만이라면, 기업은 자본을 비효율적으로 사용하고 있음을 의미할 수 있습니다.
73
+ • 자본을 투입해 수익을 내지 못하거나, 투자된 자본으로부터 적절한 수익을 창출하지 못하고 있는 상태일 수 있습니다.
74
+ :param code:
75
+ :param expect_earn:
76
+ :return:
77
+ """
78
+
79
+ mil_dict = eval.mil(code)
80
+
81
+ # print(pprint.pformat(mil_dict, width=200))
82
+
83
+ # 주주수익률 평가
84
+ if math.isnan(mil_dict['주주수익률']):
85
+ score1 = 0
86
+ else:
87
+ 주주수익률평가 = math.ceil(mil_dict['주주수익률'] - (expect_earn * 100))
88
+ score1 = 0 if 0 > 주주수익률평가 else 주주수익률평가
89
+
90
+ # 이익지표 평가
91
+ score2 = 10 if mil_dict['이익지표'] < 0 else 0
92
+
93
+ # 투자수익률 평가
94
+ MAX3 = 20
95
+ score3 = 0
96
+ roic = mil_dict['투자수익률']['ROIC']
97
+ roe = mil_dict['투자수익률']['ROE']
98
+ if math.isnan(roic) or roic <= 0:
99
+ # roic 가 비정상이라 평가할 수 없는 경우
100
+ if 10 < roe <= 20:
101
+ score3 += round(MAX3 * 0.333)
102
+ elif 20 < roe:
103
+ score3 += round(MAX3 * 0.666)
104
+ elif 0 < roic:
105
+ # roic 로 평가할 수 있는 경우
106
+ if 0 < roic <= 15:
107
+ score3 += round(MAX3 * 0.333)
108
+ elif 15 < roic <= 30:
109
+ score3 += round(MAX3 * 0.666)
110
+ elif 30 < roic:
111
+ score3 += MAX3
112
+
113
+ # PFCF 평가
114
+ pfcf_dict = mil_dict['가치지표']['PFCF']
115
+ _, pfcf = mymongo.C1034.latest_dict_value(pfcf_dict)
116
+
117
+ logger.debug(f'recent pfcf {_}, {pfcf}')
118
+ try:
119
+ p = round(-40 * math.log10(pfcf) + 40)
120
+ except ValueError:
121
+ p = 0
122
+ score4 = 0 if 0 > p else p
123
+
124
+ return score1, score2, score3, score4
125
+
126
+
127
+ def blue(code: str) -> Tuple[int, int, int, int, int]:
128
+ """회사의 안정성을 보는 지표들
129
+
130
+ 0을 기준으로 상태가 좋치 않을 수록 마이너스 값을 가진다.
131
+
132
+ Returns:
133
+ tuple : 유동비율, 이자보상배율, 순부채비율, 순운전자본회전율, 재고자산회전율 평가 포인트
134
+
135
+ Notes:
136
+ """
137
+ def _calc_point_with_std(data: dict) -> int:
138
+ """표준편차를 통해 점수를 계산하는 내부 함수
139
+
140
+ Args:
141
+ data(dict): 재무재표상의 연/분기 딕셔너리 데이터
142
+ """
143
+ NEG_MAX = -5
144
+ d_values = [i for i in data.values() if not math.isnan(i)]
145
+ logger.debug(f'd_values : {d_values}')
146
+ if len(d_values) == 0:
147
+ p = NEG_MAX
148
+ else:
149
+ std = numpy.std(d_values)
150
+ # 표준편차가 작을수록 데이터의 변환가 적다는 의미임.
151
+ logger.debug(f'표준편차 : {std}')
152
+ p = NEG_MAX if float(std) > -NEG_MAX else -math.floor(float(std))
153
+
154
+ return int(p)
155
+
156
+ c104y = myredis.C104(code, 'c104y')
157
+
158
+ blue_dict = eval.blue(code)
159
+
160
+ # print(pprint.pformat(blue_dict, width=200))
161
+
162
+ def 유동비율평가(유동비율: float) -> int:
163
+ # 채점은 0을 기준으로 마이너스 해간다. 즉 0이 제일 좋은 상태임.
164
+ # 유동비율 평가 - 100 이하는 문제 있음
165
+ NEG_MAX = -10
166
+ if math.isnan(유동비율) or 유동비율 <= 0:
167
+ p = NEG_MAX
168
+ elif math.isinf(유동비율):
169
+ p = 0
170
+ else:
171
+ p = 0 if 100 < round(유동비율) else NEG_MAX + round(유동비율/10)
172
+ logger.debug(f'유동비율평가 point : {p}')
173
+ return int(p)
174
+
175
+ p1 = 유동비율평가(blue_dict['유동비율'])
176
+
177
+ def 이자보상배율평가(이자보상배율: tuple) -> int:
178
+ # 이자보상배율평가 : 1이면 자금사정 빡빡 5이상이면 양호
179
+ NEG_MAX = -5
180
+ 최근이자보상배율q, dict_y = 이자보상배율
181
+
182
+ if math.isnan(최근이자보상배율q) or 최근이자보상배율q <= 1:
183
+ # 최근 분기의 값이 비정상이면 최근 년도를 한번 더 비교해 보지만 좀더 엄격하게 전년대비도 비교한다.
184
+
185
+ _, 최근이자보상배율y = mymongo.C1034.latest_dict_value(dict_y)
186
+ c104y.page = 'c104y'
187
+ 전년대비 = c104y.find_yoy(title='이자보상배율')
188
+
189
+ if math.isnan(최근이자보상배율y) or 최근이자보상배율y <= 1 or math.isnan(전년대비) or 전년대비 < 0:
190
+ p = NEG_MAX
191
+ else:
192
+ p = 0 if 5 < 최근이자보상배율y else NEG_MAX + round(최근이자보상배율y)
193
+ else:
194
+ p = 0 if 5 < 최근이자보상배율q else NEG_MAX + round(최근이자보상배율q)
195
+ logger.debug(f'이자보상배율평가 point : {p}')
196
+ return int(p)
197
+
198
+ p2 = 이자보상배율평가(blue_dict['이자보상배율'])
199
+
200
+ def 순부채비율평가(순부채비율: tuple) -> int:
201
+ # 부채비율은 업종마다 달라 일괄비교 어려우나 순부채 비율이 20%이하인것이 좋고 꾸준히 늘어나지 않는것이 좋다.
202
+ # 순부채 비율이 30%이상이면 좋치 않다.
203
+ NEG_MAX = -5
204
+ 최근순부채비율q, dict_y = 순부채비율
205
+
206
+ if math.isnan(최근순부채비율q) or 최근순부채비율q >= 80:
207
+ # 최근 분기의 값이 비정상이면 최근 년도를 한번 더 비교해 보지만 좀더 엄격하게 전년대비도 비교한다.
208
+ _, 최근순부채비율y = mymongo.C1034.latest_dict_value(dict_y)
209
+ c104y.page = 'c104y'
210
+ 전년대비 = c104y.find_yoy(title='순부채비율')
211
+ if math.isnan(최근순부채비율y) or 최근순부채비율y >= 80 or math.isnan(전년대비) or 전년대비 > 0:
212
+ p = NEG_MAX
213
+ else:
214
+ p = 0 if 최근순부채비율y < 30 else round((30 - 최근순부채비율y) / 10)
215
+ else:
216
+ p = 0 if 최근순부채비율q < 30 else round((30 - 최근순부채비율q) / 10)
217
+ logger.debug(f'순부채비율평가 point : {p}')
218
+ return int(p)
219
+
220
+ p3 = 순부채비율평가(blue_dict['순부채비율'])
221
+
222
+ def 순운전자본회전율평가(순운전자본회전율: tuple) -> int:
223
+ # 순운전자본회전율은 매출액/순운전자본으로 일정비율이 유지되는것이 좋으며 너무 작아지면 순운전자본이 많아졌다는 의미로 재고나 외상이 쌓인다는 뜻
224
+ _, dict_y = 순운전자본회전율
225
+ p = _calc_point_with_std(data=dict_y)
226
+ logger.debug(f'순운전자본회전율평가 point : {p}')
227
+ return p
228
+
229
+ p4 = 순운전자본회전율평가(blue_dict['순운전자본회전율'])
230
+
231
+ def 재고자산회전율평가(재고자산회전율: tuple) -> int:
232
+ # 재고자산회전율은 매출액/재고자산으로 회전율이 낮을수록 재고가 많다는 이야기이므로 불리 전년도등과 비교해서 큰차이 발생하면 알람.
233
+ # 재고자산회전율이 작아지면 재고가 쌓인다는뜻
234
+ _, dict_y = 재고자산회전율
235
+ p = _calc_point_with_std(data=dict_y)
236
+ # 라이벌기업과 비교점수 추가
237
+ logger.debug(f'재고자산회전율평가 point : {p}')
238
+ return p
239
+
240
+ p5 = 재고자산회전율평가(blue_dict['재고자산회전율'])
241
+
242
+ return p1, p2, p3, p4, p5
243
+
244
+
245
+ def growth(code: str) -> Tuple[int, int]:
246
+ """회사의 성장성을 보는 지표들
247
+
248
+ <매출액>
249
+ 매출액은 어떤경우에도 성장하는 기업이 좋다.매출이 20%씩 늘어나는 종목은 유망한 종목
250
+ <영업이익률>
251
+ 영업이익률은 기업의 경쟁력척도로 경쟁사에 비해 높으면 경제적해자를 갖춘셈
252
+
253
+ Returns:
254
+ tuple : 매출액증가율, 영업이익률 평가 포인트
255
+ """
256
+ growth_dict = eval.growth(code)
257
+
258
+ logger.debug(pprint.pformat(growth_dict, width=200))
259
+
260
+ def 매출액증가율평가(매출액증가율: tuple) -> int:
261
+ # 매출액은 어떤경우에도 성장하는 기업이 좋다.매출이 20%씩 늘어나는 종목은 유망한 종목
262
+ MAX = 20
263
+ 최근매출액증가율q, dict_y = 매출액증가율
264
+ _, 최근매출액증가율y = mymongo.C1034.latest_dict_value(dict_y)
265
+
266
+ # 최근 자료가 성장하는 중인지 판단
267
+ if math.isnan(최근매출액증가율q):
268
+ 최근매출액증가율q = 최근매출액증가율y
269
+
270
+ sp1 = 0
271
+ if math.isnan(최근매출액증가율y):
272
+ pass
273
+ elif 0 < 최근매출액증가율y and 0 < 최근매출액증가율q:
274
+ # 최근에 마이너스 성장이 아닌경우 MAX/10점 보너스
275
+ sp1 += MAX / 10
276
+ if 최근매출액증가율y < 최근매출액증가율q:
277
+ # 최근에 이전보다 더 성장중이면 MAX/10점 보너스
278
+ sp1 += MAX / 10
279
+ # 나머지는 성장률 기반 점수 배정
280
+ sp1 += MAX / 2 if 최근매출액증가율q > MAX else 최근매출액증가율q / 2
281
+ elif 최근매출액증가율y <= 0 < 최근매출액증가율q:
282
+ # 직전에 마이너스였다가 최근에 회복된 경우 MAX/10점 보너스
283
+ sp1 += MAX / 10
284
+ # 나머지는 성장률 기반 점수 배정
285
+ sp1 += MAX / 2 if 최근매출액증가율q > MAX else 최근매출액증가율q / 2
286
+ else:
287
+ # 최근 자료가 마이너스인 경우 마이너스만큼 점수를 차감한다.
288
+ sp1 += -(MAX / 2) if 최근매출액증가율q < -MAX else 최근매출액증가율q / 2
289
+
290
+ # 평균매출액증가율 구하기
291
+ d_values = [i for i in dict_y.values() if not math.isnan(i)]
292
+ logger.debug(f'평균매출액증가율 d_values : {d_values}')
293
+
294
+ if len(d_values) == 0:
295
+ 평균매출액증가율 = float('nan')
296
+ else:
297
+ 평균매출액증가율 = float(numpy.mean(d_values))
298
+ logger.debug(f'평균 : {평균매출액증가율}')
299
+
300
+ sp2 = 0
301
+ if math.isnan(평균매출액증가율):
302
+ sp2 += -(MAX/2)
303
+ elif 평균매출액증가율 <= 0:
304
+ # 평균매출액증가율이 마이너스인 경우 마이너스만큼 점수를 차감한다.
305
+ sp2 += -(MAX / 2) if 평균매출액증가율 < -MAX else 평균매출액증가율 / 2
306
+ else:
307
+ sp2 = MAX / 2 if 평균매출액증가율 > MAX else 평균매출액증가율 / 2
308
+
309
+ logger.debug(f'매출액증가율평가 point : {sp1 + sp2}')
310
+
311
+ return int(sp1 + sp2)
312
+
313
+ p1 = 매출액증가율평가(growth_dict['매출액증가율'])
314
+
315
+ def 영업이익률평가(영업이익률: dict) -> int:
316
+ # 영업이익률은 기업의 경쟁력척도로 경쟁사에 비해 높으면 경제적해자를 갖춘셈
317
+ 영업이익률 = copy.deepcopy(영업이익률)
318
+ name = myredis.Corps.get_name(code)
319
+
320
+ p = 0
321
+ try:
322
+ myprofit = utils.to_float(영업이익률.pop(name))
323
+ except KeyError:
324
+ logger.warning(f'{name} 영업이익률 does not exist.')
325
+ return 0
326
+ logger.debug(f'종목영업이익률 : {myprofit}')
327
+
328
+ for profit in 영업이익률.values():
329
+ profit = utils.to_float(profit)
330
+ if math.isnan(profit):
331
+ continue
332
+ elif myprofit > profit:
333
+ p += 1
334
+ else:
335
+ continue
336
+
337
+ logger.debug(f'영업이익률평가 point : {p}')
338
+ return p
339
+
340
+ p2 = 영업이익률평가(growth_dict['영업이익률'])
341
+
342
+ return p1, p2
@@ -0,0 +1,289 @@
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
211
+
212
+
213
+
214
+
215
+ def dbmanager():
216
+ cmd = ['repair', 'sync', 'eval', 'update']
217
+ parser = argparse.ArgumentParser()
218
+ parser.add_argument('cmd', help=f"Command - {cmd}")
219
+ parser.add_argument('target', help="Target for scraping (type 6digit code or 'all' or 'parts')")
220
+ parser.add_argument('-d', '--db_path', help="Set mongo database path")
221
+
222
+ args = parser.parse_args()
223
+
224
+ db_path = args.db_path if args.db_path else "mongodb://192.168.0.173:27017"
225
+ client = mongo.connect_mongo(db_path)
226
+
227
+ if args.cmd in cmd:
228
+ if args.cmd == 'repair':
229
+ if args.target == 'all' or utils.is_6digit(args.target):
230
+ need_for_repair_codes = chk_db.chk_integrity_corps(client, args.target)
231
+ # repair dict 예시 - {'343510': ['c106', 'c104', 'c103'], '298000': ['c104'], '091810': ['c104']}
232
+ print(f"Need for repairing codes :{need_for_repair_codes}")
233
+ if need_for_repair_codes:
234
+ # x = input("Do you want to try to repair db by scraping? (y/N)")
235
+ # if x == 'y' or x == 'Y':
236
+ for code, failed_page_list in need_for_repair_codes.items():
237
+ for page in failed_page_list:
238
+ if page == 'c101':
239
+ nfsrun.c101([code, ], db_path)
240
+ elif page == 'c103':
241
+ nfsrun.c103([code, ], db_path)
242
+ elif page == 'c104':
243
+ nfsrun.c104([code, ], db_path)
244
+ elif page == 'c106':
245
+ nfsrun.c106([code, ], db_path)
246
+ recheck_result = chk_db.chk_integrity_corps(client, code)
247
+ if recheck_result:
248
+ # 다시 스크랩해도 오류가 지속되는 경우
249
+ print(f"The db integrity failure persists..{recheck_result}")
250
+ # x = input(f"Do you want to delete {code} on DB? (y/N)")
251
+ # if x == 'y' or x == 'Y':
252
+ # mongo.Corps.del_db(client, code)
253
+ # else:
254
+ # print("Canceled.")
255
+ mongo.Corps.del_db(client, code)
256
+ # else:
257
+ # print("Done.")
258
+ else:
259
+ print("Done.")
260
+ else:
261
+ print(f"Invalid target option : {args.target}")
262
+ elif args.cmd == 'update':
263
+ if args.target == 'all' or utils.is_6digit(args.target):
264
+ need_for_update_codes = list(chk_db.chk_modifying_corps(client, args.target).keys())
265
+ # need_for_update_codes 예시 - [codes....]
266
+ print(f"Need for updating codes :{need_for_update_codes}")
267
+ if need_for_update_codes:
268
+ nfsrun.c103(need_for_update_codes, db_path)
269
+ nfsrun.c104(need_for_update_codes, db_path)
270
+ nfsrun.c106(need_for_update_codes, db_path)
271
+ elif args.target == 'parts':
272
+ pass
273
+ else:
274
+ print(f"Invalid target option : {args.target}")
275
+ elif args.cmd == 'sync':
276
+ if args.target == 'all':
277
+ chk_db.sync_mongo_with_krx(client)
278
+ else:
279
+ print(f"The target should be 'all' in sync command.")
280
+ elif args.cmd == 'eval':
281
+ if args.target == 'all':
282
+ # eval을 평가해서 데이터베이스에 저장한다.
283
+ eval.make_today_eval_df(client, refresh=True)
284
+ else:
285
+ print(f"The target should be 'all' in sync command.")
286
+ else:
287
+ print(f"The command should be in {cmd}")
288
+
289
+ client.close()