MultiFactor 0.3.3__tar.gz → 0.3.4__tar.gz

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.
Files changed (45) hide show
  1. {multifactor-0.3.3 → multifactor-0.3.4}/PKG-INFO +1 -18
  2. {multifactor-0.3.3 → multifactor-0.3.4}/README.md +0 -11
  3. {multifactor-0.3.3 → multifactor-0.3.4}/pyproject.toml +1 -7
  4. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/Visual.py +0 -4
  5. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/__init__.py +2 -3
  6. multifactor-0.3.4/src/MultiFactor/daum_consensus.py +131 -0
  7. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/quality.py +13 -3
  8. multifactor-0.3.4/src/MultiFactor/quality_one.py +31 -0
  9. multifactor-0.3.4/src/MultiFactor/value.py +46 -0
  10. multifactor-0.3.4/src/MultiFactor/value_one.py +34 -0
  11. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vfinance_kr.py +11 -3
  12. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor.egg-info/PKG-INFO +1 -18
  13. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor.egg-info/SOURCES.txt +1 -1
  14. multifactor-0.3.4/src/MultiFactor.egg-info/requires.txt +5 -0
  15. multifactor-0.3.3/src/MultiFactor/quality_one.py +0 -21
  16. multifactor-0.3.3/src/MultiFactor/value.py +0 -34
  17. multifactor-0.3.3/src/MultiFactor/value_one.py +0 -22
  18. multifactor-0.3.3/src/MultiFactor/vtreemap_kr.py +0 -130
  19. multifactor-0.3.3/src/MultiFactor.egg-info/requires.txt +0 -11
  20. {multifactor-0.3.3 → multifactor-0.3.4}/setup.cfg +0 -0
  21. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/core.py +0 -0
  22. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/momentum.py +0 -0
  23. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/momentum_one.py +0 -0
  24. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/score.py +0 -0
  25. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/score_adj_weight.py +0 -0
  26. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/stockinfo.py +0 -0
  27. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/us_core.py +0 -0
  28. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/us_momentum.py +0 -0
  29. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/us_quality.py +0 -0
  30. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/us_stockinfo.py +0 -0
  31. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/us_value.py +0 -0
  32. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vcompare_idx.py +0 -0
  33. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vheatmap_idx.py +0 -0
  34. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vheatmap_kr.py +0 -0
  35. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vheatmap_us.py +0 -0
  36. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vmulti_idx.py +0 -0
  37. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vmulti_kr.py +0 -0
  38. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vmulti_us.py +0 -0
  39. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vret_idx.py +0 -0
  40. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vret_kr.py +0 -0
  41. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vret_us.py +0 -0
  42. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vtargetprice_us.py +0 -0
  43. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor/vtreemap_us.py +0 -0
  44. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor.egg-info/dependency_links.txt +0 -0
  45. {multifactor-0.3.3 → multifactor-0.3.4}/src/MultiFactor.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MultiFactor
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: 멀티팩터 기반 주식 데이터 수집 패키지
5
5
  Author-email: HANHO <hanhoman@gmail.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -14,12 +14,6 @@ Requires-Dist: pandas
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: yfinance
17
- Requires-Dist: requests
18
- Requires-Dist: seaborn
19
- Requires-Dist: plotly
20
- Requires-Dist: ipywidgets
21
- Requires-Dist: ipython
22
- Requires-Dist: anywidget
23
17
 
24
18
  # MultiFactor 📈
25
19
 
@@ -1237,7 +1231,6 @@ mf_us.get_Ngroup(df, Ngroup=10)
1237
1231
  | 누적 수익률 | 미국주식 누적수익률 비교 차트 | vis.ret_us |
1238
1232
  | 실적재무 종합 | 국내주식 실적 및 밸류 추이 | vis.finance_kr |
1239
1233
  | 업종종목 트리맵 | 미국주식 주요종목 주가등락율 트리맵 | vis.treemap_us |
1240
- | 업종종목 트리맵 | 국내주식 주요종목 주가등락율 트리맵 | vis.treemap_kr |
1241
1234
  | 수익률 히트맵 | 지수 연월 수익률 히트맵 | vis.heatmap_idx |
1242
1235
  | 수익률 히트맵 | 국내주식 연월 수익률 히트맵 | vis.heatmap_kr |
1243
1236
  | 수익률 히트맵 | 미국주식 연월 수익률 히트맵 | vis.heatmap_us |
@@ -1343,16 +1336,6 @@ vis.treemap_us()
1343
1336
 
1344
1337
  <br>
1345
1338
 
1346
- ⑩ (업종종목 트리맵) 국내주식 주요종목 주가등락율 트리맵
1347
-
1348
- 구글 Colab에서 다음 코드로 국내주식 주가등락률 트리맵을 출력합니다.
1349
-
1350
- ```python
1351
- vis.treemap_kr()
1352
- ```
1353
-
1354
- <br>
1355
-
1356
1339
  ⑩ (수익률 히트맵) 지수 연월 수익률 히트맵
1357
1340
 
1358
1341
  ```python
@@ -1214,7 +1214,6 @@ mf_us.get_Ngroup(df, Ngroup=10)
1214
1214
  | 누적 수익률 | 미국주식 누적수익률 비교 차트 | vis.ret_us |
1215
1215
  | 실적재무 종합 | 국내주식 실적 및 밸류 추이 | vis.finance_kr |
1216
1216
  | 업종종목 트리맵 | 미국주식 주요종목 주가등락율 트리맵 | vis.treemap_us |
1217
- | 업종종목 트리맵 | 국내주식 주요종목 주가등락율 트리맵 | vis.treemap_kr |
1218
1217
  | 수익률 히트맵 | 지수 연월 수익률 히트맵 | vis.heatmap_idx |
1219
1218
  | 수익률 히트맵 | 국내주식 연월 수익률 히트맵 | vis.heatmap_kr |
1220
1219
  | 수익률 히트맵 | 미국주식 연월 수익률 히트맵 | vis.heatmap_us |
@@ -1320,16 +1319,6 @@ vis.treemap_us()
1320
1319
 
1321
1320
  <br>
1322
1321
 
1323
- ⑩ (업종종목 트리맵) 국내주식 주요종목 주가등락율 트리맵
1324
-
1325
- 구글 Colab에서 다음 코드로 국내주식 주가등락률 트리맵을 출력합니다.
1326
-
1327
- ```python
1328
- vis.treemap_kr()
1329
- ```
1330
-
1331
- <br>
1332
-
1333
1322
  ⑩ (수익률 히트맵) 지수 연월 수익률 히트맵
1334
1323
 
1335
1324
  ```python
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  # 1. PyPI에 등록될 배포 이름 (pip install MultiFactor)
7
7
  name = "MultiFactor"
8
- version = "0.3.3"
8
+ version = "0.3.4"
9
9
  authors = [
10
10
  { name="HANHO", email="hanhoman@gmail.com" },
11
11
  ]
@@ -27,12 +27,6 @@ dependencies = [
27
27
  "numpy",
28
28
  "matplotlib",
29
29
  "yfinance",
30
- "requests",
31
- "seaborn",
32
- "plotly",
33
- "ipywidgets",
34
- "ipython",
35
- "anywidget",
36
30
  ]
37
31
 
38
32
  # 3. src 폴더 안의 패키지를 자동으로 찾도록 설정
@@ -11,7 +11,6 @@ from . import vret_us
11
11
  from . import vfinance_kr
12
12
  from . import vheatmap_idx
13
13
  from . import vtreemap_us
14
- from . import vtreemap_kr
15
14
  from . import vheatmap_kr
16
15
  from . import vheatmap_us
17
16
  from . import vtargetprice_us
@@ -81,9 +80,6 @@ class Visual:
81
80
  def treemap_us(self):
82
81
  vtreemap_us.treemap_us()
83
82
 
84
- def treemap_kr(self):
85
- vtreemap_kr.treemap_kr()
86
-
87
83
  def heatmap_kr(self):
88
84
  vheatmap_kr.heatmap_kr()
89
85
 
@@ -14,8 +14,7 @@ os.environ['PYTHONWARNINGS'] = 'ignore'
14
14
  from .core import MultiFactorKR
15
15
  from .us_core import MultiFactorUS
16
16
  from .Visual import Visual
17
-
18
- __version__ = "0.3.3"
17
+ from .daum_consensus import get_daum_finstate, get_daum_consensus
19
18
 
20
19
  # 패키지를 import할 때 노출할 항목을 명시합니다.
21
- __all__ = ['MultiFactorKR', 'MultiFactorUS', 'Visual', '__version__']
20
+ __all__ = ['MultiFactorKR', 'MultiFactorUS', 'Visual', 'get_daum_finstate', 'get_daum_consensus']
@@ -0,0 +1,131 @@
1
+ """다음증권 추정실적 컨센서스를 연간/분기 DataFrame으로 가져옵니다."""
2
+
3
+ import json
4
+ import re
5
+ import time
6
+ import ssl
7
+ from urllib.parse import urlencode
8
+ from urllib.request import Request, urlopen
9
+ from urllib.error import URLError
10
+
11
+ import pandas as pd
12
+ import numpy as np
13
+
14
+ CONSENSUS_URL = "https://wisefn.finance.daum.net/v1/company/cF1002.aspx"
15
+ CONSENSUS_COLUMNS = [
16
+ "FY-end",
17
+ "주재무제표",
18
+ "매출액 (억원)",
19
+ "YoY (%)",
20
+ "영업이익 (억원)",
21
+ "당기순이익 (억원)",
22
+ "EPS (원)",
23
+ "PER (배)",
24
+ "PBR (배)",
25
+ "ROE (%)",
26
+ "EV/EBITDA (배)",
27
+ "순부채 비율 (%)",
28
+ ]
29
+ DATA_BLOCK_PATTERN = re.compile(
30
+ r'\{\s*tid\s*:\s*"(?P<tab>TB_[01])"\s*,\s*'
31
+ r"dest\s*:\s*(?P<rows>\[\[.*?\]\])\s*\}",
32
+ re.DOTALL,
33
+ )
34
+ STOCK_CODE_PATTERN = re.compile(r"(?:A)?\d{6}")
35
+
36
+ COLUMN_MAP = {
37
+ "매출액 (억원)": "매출액",
38
+ "영업이익 (억원)": "영업이익",
39
+ "당기순이익 (억원)": "당기순이익",
40
+ "PER (배)": "PER(배)",
41
+ "PBR (배)": "PBR(배)",
42
+ "ROE (%)": "ROE(%)",
43
+ }
44
+
45
+
46
+ def get_daum_consensus(
47
+ stock_code: str = "A005930",
48
+ timeout: float = 15,
49
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
50
+ """다음증권에서 종목의 연간·분기 컨센서스를 가져옵니다."""
51
+ clean_code = str(stock_code).strip()
52
+ if not STOCK_CODE_PATTERN.fullmatch(clean_code):
53
+ clean_code = clean_code.zfill(6)
54
+ if not STOCK_CODE_PATTERN.fullmatch(clean_code):
55
+ raise ValueError(f"올바르지 않은 종목코드 형식입니다: {stock_code}")
56
+
57
+ company_code = clean_code.removeprefix("A")
58
+ query = urlencode({"cmp_cd": company_code, "finGubun": "MAIN"})
59
+ request = Request(
60
+ f"{CONSENSUS_URL}?{query}",
61
+ headers={
62
+ "User-Agent": (
63
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
64
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
65
+ "Chrome/130.0.0.0 Safari/537.36"
66
+ ),
67
+ "Referer": f"https://finance.daum.net/quotes/A{company_code}",
68
+ "Accept": "text/html,application/xhtml+xml",
69
+ },
70
+ )
71
+ html = None
72
+ max_retries = 3
73
+ last_err = None
74
+
75
+ for attempt in range(max_retries):
76
+ try:
77
+ with urlopen(request, timeout=timeout) as response:
78
+ html = response.read().decode(
79
+ response.headers.get_content_charset() or "utf-8"
80
+ )
81
+ break
82
+ except (URLError, ssl.SSLError, ConnectionError, OSError) as e:
83
+ last_err = e
84
+ if attempt < max_retries - 1:
85
+ time.sleep(1.0)
86
+ else:
87
+ raise last_err
88
+
89
+ rows_by_tab = {
90
+ match.group("tab"): json.loads(match.group("rows"))
91
+ for match in DATA_BLOCK_PATTERN.finditer(html)
92
+ }
93
+ if "TB_0" not in rows_by_tab or "TB_1" not in rows_by_tab:
94
+ raise ValueError(
95
+ "응답에서 연간/분기 컨센서스를 찾지 못했습니다. "
96
+ "다음증권 페이지의 데이터 형식이 변경되었을 수 있습니다."
97
+ )
98
+
99
+ def to_dataframe(rows: list[list[str]]) -> pd.DataFrame:
100
+ if any(len(row) != len(CONSENSUS_COLUMNS) for row in rows):
101
+ raise ValueError("컨센서스 데이터의 열 구성이 예상과 다릅니다.")
102
+ frame = pd.DataFrame(rows, columns=CONSENSUS_COLUMNS)
103
+ for column in CONSENSUS_COLUMNS[2:]:
104
+ values = frame[column].str.replace(",", "", regex=False)
105
+ frame[column] = pd.to_numeric(values, errors="coerce")
106
+ return frame
107
+
108
+ annual = to_dataframe(rows_by_tab["TB_0"])
109
+ quarterly = to_dataframe(rows_by_tab["TB_1"])
110
+ return annual, quarterly
111
+
112
+
113
+ def get_daum_finstate(stock_code: str, freq: str = "Y", timeout: float = 15) -> pd.DataFrame:
114
+ """
115
+ 다음증권 컨센서스를 수집하여 기존 SnapDataReader와 호환되는 컬럼 및 인덱스 구조로 반환합니다.
116
+ - freq: 'Y' (연간) 또는 'Q' (분기)
117
+ """
118
+ annual_df, quarterly_df = get_daum_consensus(stock_code=stock_code, timeout=timeout)
119
+ is_annual = freq.upper().endswith("Y") or freq.upper() == "Y"
120
+ df = annual_df.copy() if is_annual else quarterly_df.copy()
121
+
122
+ if "FY-end" in df.columns:
123
+ df.set_index("FY-end", inplace=True)
124
+ df.index.name = "날짜"
125
+
126
+ # 기존 SnapDataReader 컬럼명('매출액', '영업이익', 'PER(배)' 등) 매핑 추가
127
+ for orig_col, target_col in COLUMN_MAP.items():
128
+ if orig_col in df.columns:
129
+ df[target_col] = df[orig_col]
130
+
131
+ return df
@@ -5,6 +5,11 @@ import numpy as np
5
5
 
6
6
  import time
7
7
 
8
+ try:
9
+ from .daum_consensus import get_daum_finstate
10
+ except ImportError:
11
+ from daum_consensus import get_daum_finstate
12
+
8
13
 
9
14
  def get_quality(stock_list):
10
15
  result_fin = []
@@ -18,10 +23,15 @@ def get_quality(stock_list):
18
23
  print(f"{i}/{len(stock_list)} 진행 중... ({sname})")
19
24
 
20
25
  try:
21
- # 재무 데이터 추출 : 분기 K-IFRS 연결 기준
22
- data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Q/{scode}').dropna(subset='영업이익') # 분기
26
+ # 재무 데이터 추출 : 1. SnapDataReader 먼저 시도 -> 에러 시 다음증권 컨센서스로 대체
27
+ try:
28
+ data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Q/{scode}').dropna(subset='영업이익') # 분기
29
+ except Exception:
30
+ data = get_daum_finstate(scode, freq='Q').dropna(subset='영업이익')
31
+
23
32
  fin = (data[['매출액','영업이익','당기순이익']].pct_change()*100).iloc[-1].to_list()
24
- roe = data['ROE(%)'].iloc[-1]
33
+ roe_series = data['ROE(%)'].dropna()
34
+ roe = roe_series.iloc[-1] if not roe_series.empty else np.nan
25
35
  result_fin.append([scode] + fin + [roe])
26
36
 
27
37
  except:
@@ -0,0 +1,31 @@
1
+ import FinanceDataReader as fdr
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ try:
7
+ from .daum_consensus import get_daum_finstate
8
+ except ImportError:
9
+ from daum_consensus import get_daum_finstate
10
+
11
+
12
+ def get_quality_one(scode):
13
+ result_fin = []
14
+ try:
15
+ # 1. SnapDataReader 먼저 시도 -> 에러 시 다음증권 컨센서스로 대체
16
+ try:
17
+ data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Q/{scode}').dropna(subset='영업이익') # 분기
18
+ except Exception:
19
+ data = get_daum_finstate(scode, freq='Q').dropna(subset='영업이익')
20
+
21
+ fin = (data[['매출액','영업이익','당기순이익']].pct_change()*100).iloc[-1].to_list()
22
+ roe_series = data['ROE(%)'].dropna()
23
+ roe = roe_series.iloc[-1] if not roe_series.empty else np.nan
24
+ result_fin.append([scode] + fin + [roe])
25
+ data_fin = pd.DataFrame(result_fin, columns=['scode', 'revenue_rate', 'oper_income_rate', 'net_income_rate', 'ROE'])
26
+ return data_fin
27
+
28
+ except Exception as e:
29
+ print(f"에러가 발생했습니다. 상세 내용: {e}")
30
+ print("추출오류 : ", scode) # 에러 종목코드 출력
31
+ return None
@@ -0,0 +1,46 @@
1
+ import FinanceDataReader as fdr
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ import time
7
+
8
+ try:
9
+ from .daum_consensus import get_daum_finstate
10
+ except ImportError:
11
+ from daum_consensus import get_daum_finstate
12
+
13
+ def get_value(stock_list):
14
+
15
+ result = []
16
+
17
+ for i, (scode, sname) in enumerate(stock_list.items()):
18
+
19
+ # [중요] 데이터 수집 시 서버 차단 방지 (1초 대기)
20
+ time.sleep(1)
21
+
22
+ # 진행 상황 모니터링 (50번째 순서마다 종목명 출력)
23
+ if i % 50 == 0:
24
+ print(f"{i}/{len(stock_list)} 진행 중... ({sname})")
25
+
26
+ try:
27
+ # 재무제표 데이터 추출 : 1. SnapDataReader 먼저 시도 -> 에러 시 다음증권 컨센서스로 대체
28
+ try:
29
+ data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Y/{scode}').dropna(subset='영업이익') # 연간
30
+ except Exception:
31
+ data = get_daum_finstate(scode, freq='Y').dropna(subset='영업이익')
32
+
33
+ per_series = data['PER(배)'].dropna()
34
+ pbr_series = data['PBR(배)'].dropna()
35
+ per = per_series.iloc[-1] if not per_series.empty else np.nan
36
+ pbr = pbr_series.iloc[-1] if not pbr_series.empty else np.nan
37
+ result.append([scode, per, pbr])
38
+
39
+ except Exception as e:
40
+ print("추출오류 : ", sname, f"({type(e).__name__}: {e})") # 에러 종목코드 및 원인 출력
41
+ result.append([scode, None, None])
42
+
43
+
44
+ data_val = pd.DataFrame(result, columns=['scode', 'PER', 'PBR'])
45
+
46
+ return data_val
@@ -0,0 +1,34 @@
1
+ import FinanceDataReader as fdr
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ try:
7
+ from .daum_consensus import get_daum_finstate
8
+ except ImportError:
9
+ from daum_consensus import get_daum_finstate
10
+
11
+
12
+ def get_value_one(scode):
13
+
14
+ result = []
15
+
16
+ try:
17
+ # 1. SnapDataReader 먼저 시도 -> 에러 시 다음증권 컨센서스로 대체
18
+ try:
19
+ data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Y/{scode}').dropna(subset='영업이익') # 연간
20
+ except Exception:
21
+ data = get_daum_finstate(scode, freq='Y').dropna(subset='영업이익')
22
+
23
+ per_series = data['PER(배)'].dropna()
24
+ pbr_series = data['PBR(배)'].dropna()
25
+ per = per_series.iloc[-1] if not per_series.empty else np.nan
26
+ pbr = pbr_series.iloc[-1] if not pbr_series.empty else np.nan
27
+ result.append([scode, per, pbr])
28
+ data_val = pd.DataFrame(result, columns=['scode', 'PER', 'PBR'])
29
+ return data_val
30
+
31
+ except Exception as e:
32
+ print(f"에러가 발생했습니다. 상세 내용: {e}")
33
+ print("추출오류 : ", scode) # 에러 종목코드 출력
34
+ return None
@@ -10,6 +10,10 @@ import concurrent.futures
10
10
  # 데이터 수집 라이브러리
11
11
  import FinanceDataReader as fdr
12
12
  import yfinance as yf
13
+ try:
14
+ from .daum_consensus import get_daum_finstate
15
+ except ImportError:
16
+ from daum_consensus import get_daum_finstate
13
17
 
14
18
  # 시각화 라이브러리
15
19
  import matplotlib.pyplot as plt
@@ -57,13 +61,17 @@ def finance_kr():
57
61
 
58
62
  # --- [2] 밸류 & 퀄리티 지표: 재무 데이터 막대그래프 ---
59
63
  try:
60
- # 연간(Y) 또는 분기(Q) 재무제표 동적 수집
61
- fs = fdr.SnapDataReader(f'NAVER/FINSTATE-{YQ}/{stock_code}')
64
+ # 연간(Y) 또는 분기(Q) 재무제표 동적 수집 (SnapDataReader 먼저 시도 -> 에러 시 다음증권 대체)
65
+ try:
66
+ fs = fdr.SnapDataReader(f'NAVER/FINSTATE-{YQ}/{stock_code}')
67
+ except Exception:
68
+ fs = get_daum_finstate(stock_code, freq=YQ)
69
+
62
70
  period_text = "연간" if YQ == 'Y' else "분기"
63
71
 
64
72
  cols = ['PER(배)', 'PBR(배)', 'ROE(%)', '매출액', '영업이익', '당기순이익']
65
73
  res = fs[cols].T.dropna(axis=1, how='all')
66
- res.columns = [x.strftime('%y-%m') for x in res.columns]
74
+ res.columns = [x.strftime('%y-%m') if hasattr(x, 'strftime') else str(x) for x in res.columns]
67
75
 
68
76
  fig_vq, axes = plt.subplots(2, 3, figsize=(14, 8), tight_layout=True)
69
77
  fig_vq.suptitle(f"[{stock_name}] 밸류 & 퀄리티 지표 ({period_text} 재무제표)", fontsize=15, fontweight='bold', y=1.02)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MultiFactor
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: 멀티팩터 기반 주식 데이터 수집 패키지
5
5
  Author-email: HANHO <hanhoman@gmail.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -14,12 +14,6 @@ Requires-Dist: pandas
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: yfinance
17
- Requires-Dist: requests
18
- Requires-Dist: seaborn
19
- Requires-Dist: plotly
20
- Requires-Dist: ipywidgets
21
- Requires-Dist: ipython
22
- Requires-Dist: anywidget
23
17
 
24
18
  # MultiFactor 📈
25
19
 
@@ -1237,7 +1231,6 @@ mf_us.get_Ngroup(df, Ngroup=10)
1237
1231
  | 누적 수익률 | 미국주식 누적수익률 비교 차트 | vis.ret_us |
1238
1232
  | 실적재무 종합 | 국내주식 실적 및 밸류 추이 | vis.finance_kr |
1239
1233
  | 업종종목 트리맵 | 미국주식 주요종목 주가등락율 트리맵 | vis.treemap_us |
1240
- | 업종종목 트리맵 | 국내주식 주요종목 주가등락율 트리맵 | vis.treemap_kr |
1241
1234
  | 수익률 히트맵 | 지수 연월 수익률 히트맵 | vis.heatmap_idx |
1242
1235
  | 수익률 히트맵 | 국내주식 연월 수익률 히트맵 | vis.heatmap_kr |
1243
1236
  | 수익률 히트맵 | 미국주식 연월 수익률 히트맵 | vis.heatmap_us |
@@ -1343,16 +1336,6 @@ vis.treemap_us()
1343
1336
 
1344
1337
  <br>
1345
1338
 
1346
- ⑩ (업종종목 트리맵) 국내주식 주요종목 주가등락율 트리맵
1347
-
1348
- 구글 Colab에서 다음 코드로 국내주식 주가등락률 트리맵을 출력합니다.
1349
-
1350
- ```python
1351
- vis.treemap_kr()
1352
- ```
1353
-
1354
- <br>
1355
-
1356
1339
  ⑩ (수익률 히트맵) 지수 연월 수익률 히트맵
1357
1340
 
1358
1341
  ```python
@@ -3,6 +3,7 @@ pyproject.toml
3
3
  src/MultiFactor/Visual.py
4
4
  src/MultiFactor/__init__.py
5
5
  src/MultiFactor/core.py
6
+ src/MultiFactor/daum_consensus.py
6
7
  src/MultiFactor/momentum.py
7
8
  src/MultiFactor/momentum_one.py
8
9
  src/MultiFactor/quality.py
@@ -29,7 +30,6 @@ src/MultiFactor/vret_idx.py
29
30
  src/MultiFactor/vret_kr.py
30
31
  src/MultiFactor/vret_us.py
31
32
  src/MultiFactor/vtargetprice_us.py
32
- src/MultiFactor/vtreemap_kr.py
33
33
  src/MultiFactor/vtreemap_us.py
34
34
  src/MultiFactor.egg-info/PKG-INFO
35
35
  src/MultiFactor.egg-info/SOURCES.txt
@@ -0,0 +1,5 @@
1
+ finance-datareader
2
+ pandas
3
+ numpy
4
+ matplotlib
5
+ yfinance
@@ -1,21 +0,0 @@
1
- import FinanceDataReader as fdr
2
-
3
- import pandas as pd
4
- import numpy as np
5
-
6
-
7
- def get_quality_one(scode):
8
- result_fin = []
9
- try:
10
- # 재무 데이터 추출 : 분기 K-IFRS 연결 기준
11
- data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Q/{scode}').dropna(subset='영업이익') # 분기
12
- fin = (data[['매출액','영업이익','당기순이익']].pct_change()*100).iloc[-1].to_list()
13
- roe = data['ROE(%)'].iloc[-1]
14
- result_fin.append([scode] + fin + [roe])
15
- data_fin = pd.DataFrame(result_fin, columns=['scode', 'revenue_rate', 'oper_income_rate', 'net_income_rate', 'ROE'])
16
- return data_fin
17
-
18
- except Exception as e:
19
- print(f"에러가 발생했습니다. 상세 내용: {e}")
20
- print("추출오류 : ", sname) # 에러 종목코드 출력
21
- return None
@@ -1,34 +0,0 @@
1
- import FinanceDataReader as fdr
2
-
3
- import pandas as pd
4
- import numpy as np
5
-
6
- import time
7
-
8
- def get_value(stock_list):
9
-
10
- result = []
11
-
12
- for i, (scode, sname) in enumerate(stock_list.items()):
13
-
14
- # [중요] 데이터 수집 시 서버 차단 방지 (1초 대기)
15
- time.sleep(1)
16
-
17
- # 진행 상황 모니터링 (50번째 순서마다 종목명 출력)
18
- if i % 50 == 0:
19
- print(f"{i}/{len(stock_list)} 진행 중... ({sname})")
20
-
21
- try:
22
- # 재무제표 데이터 추출 (연간 K-IFRS 연결 기준)
23
- data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Y/{scode}').dropna(subset='영업이익') # 연간
24
- per, pbr = data['PER(배)'].iloc[-1], data['PBR(배)'].iloc[-1]
25
- result.append([scode, per, pbr])
26
-
27
- except:
28
- print("추출오류 : ", sname) # 에러 종목코드 출력
29
- result.append([scode, None, None])
30
-
31
-
32
- data_val = pd.DataFrame(result, columns=['scode', 'PER', 'PBR'])
33
-
34
- return data_val
@@ -1,22 +0,0 @@
1
- import FinanceDataReader as fdr
2
-
3
- import pandas as pd
4
- import numpy as np
5
-
6
-
7
- def get_value_one(scode):
8
-
9
- result = []
10
-
11
- try:
12
- # 재무제표 데이터 추출 (연간 K-IFRS 연결 기준)
13
- data = fdr.SnapDataReader(f'NAVER/FINSTATE-2Y/{scode}').dropna(subset='영업이익') # 연간
14
- per, pbr = data['PER(배)'].iloc[-1], data['PBR(배)'].iloc[-1]
15
- result.append([scode, per, pbr])
16
- data_val = pd.DataFrame(result, columns=['scode', 'PER', 'PBR'])
17
- return data_val
18
-
19
- except Exception as e:
20
- print(f"에러가 발생했습니다. 상세 내용: {e}")
21
- print("추출오류 : ", sname) # 에러 종목코드 출력
22
- return None
@@ -1,130 +0,0 @@
1
- from io import StringIO
2
-
3
- import numpy as np
4
- import pandas as pd
5
- import FinanceDataReader as fdr
6
- import plotly.express as px
7
- import plotly.graph_objects as go
8
- import ipywidgets as widgets
9
- from IPython.display import clear_output, display
10
-
11
-
12
- def _enable_colab_widgets():
13
- try:
14
- from google.colab import output
15
- except ImportError:
16
- return False
17
-
18
- output.enable_custom_widget_manager()
19
- return True
20
-
21
-
22
- def _generate_sector_data(prompt):
23
- try:
24
- from google.colab import ai
25
- except ImportError as exc:
26
- raise RuntimeError("google.colab.ai를 사용할 수 있는 구글 Colab 환경에서 실행해주세요.") from exc
27
-
28
- response = ai.generate_text(prompt)
29
- sector = pd.read_csv(StringIO(response), names=["ticker_name", "sector"], on_bad_lines="skip")
30
- sector["ticker_name"] = sector["ticker_name"].astype(str).str.strip()
31
- sector["sector"] = sector["sector"].astype(str).str.strip()
32
- sector = sector[(sector["ticker_name"] != "") & (sector["sector"] != "")]
33
- return sector.drop_duplicates(subset=["ticker_name"], keep="first")
34
-
35
-
36
- def treemap_kr():
37
- _enable_colab_widgets()
38
-
39
- print("코스피 주식 데이터를 가져오는 중입니다...")
40
- required_cols = ["Code", "Name", "Marcap", "ChagesRatio", "Close"]
41
- df_base = fdr.StockListing("KOSPI")
42
-
43
- missing_cols = [col for col in required_cols if col not in df_base.columns]
44
- if missing_cols:
45
- print(f"필수 컬럼을 찾을 수 없습니다: {', '.join(missing_cols)}")
46
- return
47
-
48
- df_base = df_base[required_cols].copy()
49
- df_base.columns = ["ticker", "ticker_name", "mktcap", "ret", "close"]
50
- df_base["ticker_name"] = df_base["ticker_name"].astype(str).str.strip()
51
- df_base = df_base.dropna(subset=["ticker_name", "mktcap", "ret", "close"])
52
- df_base = df_base[df_base["mktcap"] > 0]
53
-
54
- if df_base.empty:
55
- print("트리맵을 생성할 코스피 데이터를 찾을 수 없습니다.")
56
- return
57
-
58
- print("데이터 로드 완료. 아래 드롭다운에서 분석할 종목 수를 선택하세요.")
59
-
60
- @widgets.interact(top_n=widgets.Dropdown(options=[50, 100, 200], value=100, description="종목 수:"))
61
- def generate_stock_treemap(top_n):
62
- df = df_base.sort_values(by="mktcap", ascending=False).head(top_n).copy()
63
- stock_names = "\n".join(df["ticker_name"])
64
-
65
- prompt = f"""
66
- #지시문
67
- - 다음 종목명의 종목섹터를 알고 싶습니다.
68
- - 종목섹터는 한글로 작성해주세요.
69
- - 종목별로 섹터가 여러개로 분류되면, 대표섹터 하나만 생성합니다. (예: 반도체/IT이면, 반도체만 생성)
70
- - 출력형식을 정확히 지켜서 작성해주세요. 형식이 틀리면 안 됩니다.
71
-
72
- #출력형식
73
- <종목명>,<섹터명>
74
- <종목명>,<섹터명>
75
-
76
- #종목명
77
- {stock_names}
78
- """
79
-
80
- max_retries = 3
81
- sector = pd.DataFrame(columns=["ticker_name", "sector"])
82
-
83
- for retry_count in range(max_retries):
84
- try:
85
- print(f"\r[Gemini API] 업종 정보 추론 중... (시도 {retry_count + 1}/{max_retries})", end="")
86
- sector_temp = _generate_sector_data(prompt)
87
- test_merge = df.merge(sector_temp, on="ticker_name", how="left")
88
- nan_count = test_merge["sector"].isna().sum() + (test_merge["sector"] == "nan").sum()
89
-
90
- if nan_count < len(df) * 0.1:
91
- sector = sector_temp
92
- print(" -> 성공!")
93
- break
94
- except Exception as exc:
95
- if retry_count == max_retries - 1:
96
- print(f"\n[오류] 업종 정보 추론에 실패했습니다. 기타 업종으로 진행합니다. ({exc})")
97
-
98
- df_final = df.merge(sector, on="ticker_name", how="left")
99
- df_final["sector"] = df_final["sector"].fillna("기타 업종").replace("nan", "기타 업종")
100
- df_final["mktcap_log"] = np.log10(df_final["mktcap"])
101
-
102
- custom_colorscale = [
103
- [0.0, "rgb(44, 95, 233)"],
104
- [0.4, "rgb(151, 185, 255)"],
105
- [0.5, "rgb(255, 255, 255)"],
106
- [0.6, "rgb(255, 165, 165)"],
107
- [1.0, "rgb(235, 41, 41)"],
108
- ]
109
-
110
- fig = px.treemap(
111
- df_final,
112
- path=["sector", "ticker_name"],
113
- values="mktcap_log",
114
- color="ret",
115
- color_continuous_scale=custom_colorscale,
116
- color_continuous_midpoint=0,
117
- title=f"코스피 시가총액 상위 {top_n} 종목 업종/종목별 등락률 트리맵",
118
- hover_data={"ret": ":.2f", "mktcap": ":,", "close": ":,", "ticker": True, "mktcap_log": False},
119
- )
120
-
121
- fig.update_layout(
122
- width=1000,
123
- height=700,
124
- title_font_size=16,
125
- title_font_family="Malgun Gothic",
126
- margin=dict(t=50, l=10, r=10, b=10),
127
- )
128
-
129
- clear_output(wait=True)
130
- display(go.FigureWidget(fig))
@@ -1,11 +0,0 @@
1
- finance-datareader
2
- pandas
3
- numpy
4
- matplotlib
5
- yfinance
6
- requests
7
- seaborn
8
- plotly
9
- ipywidgets
10
- ipython
11
- anywidget
File without changes