예제 소스
한국투자증권 API와 LLM 주식 응용 프로그램 · 파일 19개
build_stock_list.py
# -*- coding: utf-8 -*-
"""
FinanceDataReader 를 이용해 KOSPI/KOSDAQ 전종목 정보를 조회하여
'투자 유의 종목'(관리종목 / 투자유의 / 투자환기 / 정리매매 / 단기과열 등),
우선주(종목명 …우 / 2우B / 4우(전환) 등), ETF/ETN/ELW/리츠 등 비주식을 제외한
종목 리스트를 CSV 파일로 저장합니다.
사용법
$ python build_stock_list.py
출력
stock_list.csv
- 종목코드, 종목명, 시장, 소속부
의존성
pip install finance-datareader pandas
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
import pandas as pd
try:
import FinanceDataReader as fdr
except ImportError as exc: # pragma: no cover
print(
"FinanceDataReader 가 설치되어 있지 않습니다. "
"`pip install finance-datareader` 로 먼저 설치해 주세요.",
file=sys.stderr,
)
raise SystemExit(1) from exc
# 소속부(Dept) 중 '투자 유의' 로 간주하여 제외할 키워드.
# FDR 의 Dept 컬럼은 여러 사유가 콤마로 같이 들어오는 경우가 있어
# 부분 일치(키워드 포함) 로 판정합니다.
# 종목명 끝 패턴: 삼성전자우, 현대차2우B, CJ4우(전환), SK우 등
PREFERRED_NAME_RE = re.compile(
r"(?:우선주|[1-5]?우[BC]?|\d+우)(?:\([^)]*\))?$"
)
EXCLUDE_DEPT_KEYWORDS: tuple[str, ...] = (
"관리종목",
"투자유의",
"투자환기",
"투자주의",
"투자경고",
"투자위험",
"정리매매",
"단기과열",
"거래정지",
"불성실공시",
)
def fetch_korean_stocks() -> pd.DataFrame:
"""KOSPI/KOSDAQ 전종목 DataFrame 을 반환합니다.
반환 컬럼(주요): Code, Name, Market, Dept
"""
frames: list[pd.DataFrame] = []
for market in ("KOSPI", "KOSDAQ"):
df = fdr.StockListing(market)
if df is None or df.empty:
continue
# Market 컬럼이 없으면 직접 채워줌
if "Market" not in df.columns:
df = df.assign(Market=market)
frames.append(df)
if not frames:
return pd.DataFrame(columns=["Code", "Name", "Market", "Dept"])
merged = pd.concat(frames, ignore_index=True)
# 컬럼 정규화: 일부 버전에서는 Dept 가 없을 수 있음
if "Dept" not in merged.columns:
merged["Dept"] = ""
keep_cols = [c for c in ("Code", "Name", "Market", "Dept") if c in merged.columns]
return merged[keep_cols].copy()
def is_preferred_stock(name: object, code: object = None) -> bool:
"""우선주 여부 (종목명 기준). code 는 예비용으로만 받으며 현재는 name 으로 판정."""
_ = code
if not isinstance(name, str):
return False
n = name.strip()
if not n:
return False
if "우선주" in n:
return True
return bool(PREFERRED_NAME_RE.search(n))
def _is_excluded_dept(dept: object) -> bool:
if not isinstance(dept, str) or not dept.strip():
return False
return any(kw in dept for kw in EXCLUDE_DEPT_KEYWORDS)
def _is_stock_code(code: object) -> bool:
"""6자리 숫자 종목코드만 일반 주권으로 간주 (ETF/ETN/ELW 등 일부 제외)."""
if not isinstance(code, str):
return False
return len(code) == 6 and code.isdigit()
def filter_korean_stocks(df: pd.DataFrame) -> tuple[pd.DataFrame, dict[str, int]]:
stats = {
"total": int(len(df)),
"non_stock_skipped": 0,
"section_excluded": 0,
"preferred_excluded": 0,
}
if df.empty:
return df.copy(), stats
code_mask = df["Code"].map(_is_stock_code)
stats["non_stock_skipped"] = int((~code_mask).sum())
dept_mask = df["Dept"].map(_is_excluded_dept) if "Dept" in df.columns else False
stats["section_excluded"] = int(pd.Series(dept_mask).sum()) if "Dept" in df.columns else 0
if "Name" in df.columns:
pref_mask = df.apply(
lambda r: is_preferred_stock(r.get("Name"), r.get("Code")),
axis=1,
)
else:
pref_mask = False
stats["preferred_excluded"] = int(pd.Series(pref_mask).sum()) if "Name" in df.columns else 0
keep_mask = (
code_mask
& (~pd.Series(dept_mask, index=df.index) if "Dept" in df.columns else True)
& (~pd.Series(pref_mask, index=df.index) if "Name" in df.columns else True)
)
out = df.loc[keep_mask].copy()
out = out.rename(
columns={
"Code": "종목코드",
"Name": "종목명",
"Market": "시장",
"Dept": "소속부",
}
)
out["소속부"] = out.get("소속부", "").fillna("")
out = out.drop_duplicates(subset=["종목코드"]).sort_values("종목코드").reset_index(drop=True)
return out, stats
def main() -> int:
out_path = Path(__file__).resolve().parent / "stock_list.csv"
print("[1/3] 국내 주식 목록 조회 중 (KOSPI/KOSDAQ)...")
try:
all_stocks_df = fetch_korean_stocks()
except Exception as exc:
print(f" ✗ 전종목 데이터 조회 실패: {exc}", file=sys.stderr)
return 1
if all_stocks_df.empty:
print(" ✗ 데이터 조회 결과가 비어 있습니다.", file=sys.stderr)
return 1
print(f" · 조회된 총 후보 종목 수: {len(all_stocks_df)}")
print("[2/3] 필터링 중...")
filtered_df, stats = filter_korean_stocks(all_stocks_df)
print(f" · 총 종목 수 : {stats['total']}")
print(f" · 비주식 제외 : {stats['non_stock_skipped']}")
print(f" · 유의/관리 제외 : {stats['section_excluded']}")
print(f" · 우선주 제외 : {stats['preferred_excluded']}")
print(f" · 최종 유효 종목수: {len(filtered_df)}")
print(f"[3/3] CSV 저장: {out_path}")
filtered_df.to_csv(out_path, encoding="utf-8-sig", index=False)
print(f" ✓ 저장 완료 ({len(filtered_df)} 종목)")
return 0
if __name__ == "__main__":
sys.exit(main())