Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
Tags
- swift
- sqlite
- Linux
- MySQL
- mssql
- Excel
- PyQt5
- 라즈베리파이
- MS-SQL
- ASP
- node.js
- 다이어트
- GIT
- 리눅스
- ubuntu
- javascript
- PyQt
- 맛집
- PER
- pandas
- 함수
- 유니티
- tensorflow
- flutter
- IOS
- 날짜
- python
- urllib
- port
- Unity
Archives
아미(아름다운미소)
재무제표 최종 본문
import pandas as pd
import requests
from typing import Optional, Dict, Any, Tuple
import warnings
warnings.filterwarnings('ignore')
class EnhancedFinancialAnalyzer:
"""네이버 금융 재무제표 분석기 - 확장된 재무비율 포함"""
def __init__(self):
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
def get_financial_statement(self, ticker: str) -> Optional[pd.DataFrame]:
"""네이버 금융에서 손익계산서 데이터를 가져오는 함수"""
url = f'https://finance.naver.com/item/main.naver?code={ticker}'
try:
response = requests.get(url, headers=self.headers, timeout=10)
if response.status_code != 200:
print(f"⚠️ 페이지 접근 실패: HTTP {response.status_code}")
return None
tables = pd.read_html(url, encoding='euc-kr', header=0)
except requests.exceptions.RequestException as e:
print(f"⚠️ 네트워크 오류: {e}")
return None
except Exception as e:
print(f"⚠️ 데이터 파싱 오류: {e}")
return None
# 손익계산서 테이블 찾기
for i, table in enumerate(tables):
if table.shape[1] >= 3 and len(table) > 5:
first_col = table.iloc[:, 0].astype(str).str.strip()
if any('매출' in cell for cell in first_col):
print(f"✅ 재무제표 발견 (테이블 #{i+1})")
return table
print("⚠️ 손익계산서 테이블을 찾을 수 없습니다")
return None
def get_balance_sheet(self, ticker: str) -> Optional[pd.DataFrame]:
"""네이버 금융에서 재무상태표 데이터를 가져오는 함수"""
url = f'https://finance.naver.com/item/main.naver?code={ticker}'
try:
response = requests.get(url, headers=self.headers, timeout=10)
if response.status_code != 200:
return None
tables = pd.read_html(url, encoding='euc-kr', header=0)
# 재무상태표 테이블 찾기 (자산, 부채 등이 포함된 테이블)
for i, table in enumerate(tables):
if table.shape[1] >= 3 and len(table) > 5:
first_col = table.iloc[:, 0].astype(str).str.strip()
if any(keyword in cell for keyword in ['자산', '부채', '자본'] for cell in first_col):
print(f"✅ 재무상태표 발견 (테이블 #{i+1})")
return table
except Exception as e:
print(f"⚠️ 재무상태표 데이터 오류: {e}")
return None
def get_company_info(self, ticker: str) -> Dict[str, Any]:
"""기업 기본정보 및 주가 정보 수집"""
try:
# 주식 정보 페이지에서 시가총액, 주가 등 정보 수집
url = f'https://finance.naver.com/item/main.naver?code={ticker}'
response = requests.get(url, headers=self.headers, timeout=10)
if response.status_code != 200:
return {}
tables = pd.read_html(url, encoding='euc-kr')
# 시가총액, 주가 등 정보가 있는 테이블 찾기
company_info = {}
for table in tables:
if len(table.columns) >= 2:
# 테이블을 문자열로 변환하여 검색
table_str = table.astype(str)
if table_str.apply(lambda x: x.str.contains('시가총액|주가|거래량', na=False)).any().any():
# 시가총액 정보 추출 시도
try:
for idx, row in table.iterrows():
if '시가총액' in str(row.iloc[0]):
company_info['시가총액'] = str(row.iloc[1])
elif '현재가' in str(row.iloc[0]) or '주가' in str(row.iloc[0]):
company_info['현재가'] = str(row.iloc[1])
except:
continue
return company_info
except Exception as e:
print(f"⚠️ 기업정보 수집 오류: {e}")
return {}
def clean_financial_df(self, df: pd.DataFrame) -> pd.DataFrame:
"""데이터프레임 전처리"""
df_copy = df.copy()
df_copy.set_index(df_copy.columns[0], inplace=True)
df_copy = df_copy.replace(['-', '/', 'N/A', '', ' '], '0')
def convert_to_number(x):
if pd.isna(x) or x == '':
return 0
try:
if isinstance(x, str):
cleaned = x.replace(',', '').replace('(', '-').replace(')', '').strip()
return float(cleaned) if cleaned else 0
return float(x)
except (ValueError, TypeError):
return 0
for col in df_copy.columns:
df_copy[col] = df_copy[col].apply(convert_to_number)
return df_copy
def find_row_name(self, df: pd.DataFrame, candidates: list) -> str:
"""유연한 행 이름 매칭"""
index_str = df.index.astype(str).str.strip()
for candidate in candidates:
if candidate in index_str.values:
return candidate
matches = index_str[index_str.str.contains(candidate, na=False)]
if len(matches) > 0:
return matches.iloc[0]
raise KeyError(f"다음 항목을 찾을 수 없습니다: {candidates}")
def calculate_growth_rate(self, current: float, previous: float) -> Tuple[str, float]:
"""성장률 계산 (문자열과 숫자값 모두 반환)"""
try:
if previous == 0:
return "N/A (이전값 0)", 0
growth_rate = ((current - previous) / abs(previous)) * 100
return f"{growth_rate:.2f}%", growth_rate
except (ZeroDivisionError, TypeError):
return "N/A", 0
def calculate_extended_ratios(self, income_df: pd.DataFrame, balance_df: Optional[pd.DataFrame] = None) -> Dict[str, Any]:
"""확장된 재무비율 계산"""
ratios = {}
try:
income_cleaned = self.clean_financial_df(income_df)
# 최신 두 기간 데이터
if len(income_cleaned.columns) < 2:
return {'오류': '비교할 데이터가 충분하지 않습니다'}
latest = income_cleaned.columns[-1]
prev = income_cleaned.columns[-2]
# 손익계산서 주요 항목
revenue_current = income_cleaned.loc[self.find_row_name(income_cleaned, ['매출액', '수익(매출액)', '총매출액']), latest]
revenue_previous = income_cleaned.loc[self.find_row_name(income_cleaned, ['매출액', '수익(매출액)', '총매출액']), prev]
operating_current = income_cleaned.loc[self.find_row_name(income_cleaned, ['영업이익', '영업이익(손실)', '영업손익']), latest]
operating_previous = income_cleaned.loc[self.find_row_name(income_cleaned, ['영업이익', '영업이익(손실)', '영업손익']), prev]
net_current = income_cleaned.loc[self.find_row_name(income_cleaned, ['당기순이익', '당기순이익(손실)', '순이익', '당기순손익']), latest]
net_previous = income_cleaned.loc[self.find_row_name(income_cleaned, ['당기순이익', '당기순이익(손실)', '순이익', '당기순손익']), prev]
# 1. 수익성 비율 (Profitability Ratios)
if revenue_current != 0:
ratios['매출총이익률'] = f"{((revenue_current - 0) / revenue_current * 100):.2f}%" # 매출원가 데이터 필요시 수정
ratios['영업이익률'] = f"{(operating_current / revenue_current * 100):.2f}%"
ratios['순이익률'] = f"{(net_current / revenue_current * 100):.2f}%"
# EBITDA 추정 (감가상각비 데이터가 있다면 더 정확)
try:
# 감가상각비 찾기 시도
depreciation = 0
try:
depreciation_row = self.find_row_name(income_cleaned, ['감가상각비', '상각비'])
depreciation = income_cleaned.loc[depreciation_row, latest]
except KeyError:
# 감가상각비를 찾을 수 없으면 영업이익의 10%로 추정
depreciation = operating_current * 0.1
ebitda = operating_current + depreciation
ratios['EBITDA'] = f"{ebitda:,.0f}백만원"
ratios['EBITDA마진'] = f"{(ebitda / revenue_current * 100):.2f}%" if revenue_current != 0 else "N/A"
except:
ratios['EBITDA'] = "계산불가"
ratios['EBITDA마진'] = "계산불가"
# 2. 성장성 비율 (Growth Ratios)
revenue_growth_str, revenue_growth_val = self.calculate_growth_rate(revenue_current, revenue_previous)
operating_growth_str, operating_growth_val = self.calculate_growth_rate(operating_current, operating_previous)
net_growth_str, net_growth_val = self.calculate_growth_rate(net_current, net_previous)
ratios['매출액증가율'] = revenue_growth_str
ratios['영업이익증가율'] = operating_growth_str
ratios['순이익증가율'] = net_growth_str
# 재무상태표 기반 비율 (데이터가 있는 경우)
if balance_df is not None:
try:
balance_cleaned = self.clean_financial_df(balance_df)
# 자산 관련
total_assets = balance_cleaned.loc[self.find_row_name(balance_cleaned, ['자산총계', '총자산', '자산합계']), latest]
# 부채 관련
total_liabilities = balance_cleaned.loc[self.find_row_name(balance_cleaned, ['부채총계', '총부채', '부채합계']), latest]
# 자본 관련
total_equity = balance_cleaned.loc[self.find_row_name(balance_cleaned, ['자본총계', '총자본', '자본합계', '자기자본']), latest]
# 3. 안전성 비율 (Stability Ratios)
if total_assets != 0:
ratios['부채비율'] = f"{(total_liabilities / total_equity * 100):.2f}%" if total_equity != 0 else "N/A"
ratios['자기자본비율'] = f"{(total_equity / total_assets * 100):.2f}%"
ratios['부채자산비율'] = f"{(total_liabilities / total_assets * 100):.2f}%"
# 4. 활동성 비율 (Activity Ratios)
if total_assets != 0:
ratios['총자산회전율'] = f"{(revenue_current / total_assets):.2f}회"
# 5. 수익성 심화 분석
if total_assets != 0:
ratios['ROA(총자산수익률)'] = f"{(net_current / total_assets * 100):.2f}%"
if total_equity != 0:
ratios['ROE(자기자본수익률)'] = f"{(net_current / total_equity * 100):.2f}%"
except KeyError as e:
ratios['재무상태표_오류'] = f"재무상태표 항목 부족: {str(e)}"
except Exception as e:
ratios['재무상태표_계산오류'] = str(e)
# 6. 종합 평가 점수 시스템
score = 0
max_score = 0
# 수익성 점수 (40점)
max_score += 40
if revenue_current > 0:
operating_margin = operating_current / revenue_current * 100
if operating_margin >= 20: score += 15
elif operating_margin >= 15: score += 12
elif operating_margin >= 10: score += 8
elif operating_margin >= 5: score += 4
elif operating_margin >= 0: score += 1
net_margin = net_current / revenue_current * 100
if net_margin >= 15: score += 15
elif net_margin >= 10: score += 12
elif net_margin >= 5: score += 8
elif net_margin >= 2: score += 4
elif net_margin >= 0: score += 1
# EBITDA 마진 평가
try:
ebitda_margin = float(ratios.get('EBITDA마진', '0%').replace('%', ''))
if ebitda_margin >= 25: score += 10
elif ebitda_margin >= 20: score += 8
elif ebitda_margin >= 15: score += 6
elif ebitda_margin >= 10: score += 3
elif ebitda_margin >= 5: score += 1
except:
pass
# 성장성 점수 (30점)
max_score += 30
if revenue_growth_val >= 20: score += 10
elif revenue_growth_val >= 10: score += 8
elif revenue_growth_val >= 5: score += 6
elif revenue_growth_val >= 0: score += 3
if operating_growth_val >= 30: score += 10
elif operating_growth_val >= 15: score += 8
elif operating_growth_val >= 5: score += 6
elif operating_growth_val >= 0: score += 3
if net_growth_val >= 30: score += 10
elif net_growth_val >= 15: score += 8
elif net_growth_val >= 5: score += 6
elif net_growth_val >= 0: score += 3
# 안정성 점수 (30점) - 재무상태표 데이터가 있는 경우만
if balance_df is not None and '부채비율' in ratios:
max_score += 30
try:
debt_ratio = float(ratios['부채비율'].replace('%', ''))
if debt_ratio <= 30: score += 15
elif debt_ratio <= 50: score += 12
elif debt_ratio <= 100: score += 8
elif debt_ratio <= 200: score += 4
elif debt_ratio <= 300: score += 1
equity_ratio = float(ratios['자기자본비율'].replace('%', ''))
if equity_ratio >= 70: score += 15
elif equity_ratio >= 50: score += 12
elif equity_ratio >= 30: score += 8
elif equity_ratio >= 20: score += 4
elif equity_ratio >= 10: score += 1
except:
max_score -= 30
# 최종 점수 계산
if max_score > 0:
final_score = (score / max_score) * 100
ratios['종합점수'] = f"{final_score:.1f}점 ({score}/{max_score})"
if final_score >= 80:
ratios['투자등급'] = "🟢 우수 (A급)"
elif final_score >= 65:
ratios['투자등급'] = "🟡 양호 (B급)"
elif final_score >= 50:
ratios['투자등급'] = "🟠 보통 (C급)"
elif final_score >= 35:
ratios['투자등급'] = "🔴 주의 (D급)"
else:
ratios['투자등급'] = "🔴 위험 (E급)"
else:
ratios['종합점수'] = "계산불가"
ratios['투자등급'] = "평가불가"
except Exception as e:
ratios['계산오류'] = str(e)
return ratios
def analyze_financials_extended(self, ticker: str) -> Dict[str, Any]:
"""확장된 재무 분석"""
print(f"\n{'='*60}")
print(f"📊 [{ticker}] 심화 재무제표 분석 보고서")
print(f"{'='*60}")
# 손익계산서 데이터 수집
income_df = self.get_financial_statement(ticker)
if income_df is None:
return {'오류': '손익계산서 데이터를 가져올 수 없습니다'}
# 재무상태표 데이터 수집 (선택적)
balance_df = self.get_balance_sheet(ticker)
if balance_df is not None:
print("✅ 재무상태표 데이터도 확보됨 - 더 정확한 분석 가능")
else:
print("⚠️ 재무상태표 데이터 없음 - 손익계산서 중심 분석")
# 기업 정보 수집
company_info = self.get_company_info(ticker)
# 확장된 재무비율 계산
ratios = self.calculate_extended_ratios(income_df, balance_df)
# 기본 정보 출력
print(f"\n📋 기업 기본정보:")
print("-" * 30)
for key, value in company_info.items():
print(f"{key}: {value}")
# 재무비율 출력
print(f"\n📊 재무비율 분석 결과:")
print("-" * 30)
# 카테고리별로 구분하여 출력
categories = {
'💰 수익성 지표': ['매출총이익률', '영업이익률', '순이익률', 'EBITDA', 'EBITDA마진', 'ROA(총자산수익률)', 'ROE(자기자본수익률)'],
'📈 성장성 지표': ['매출액증가율', '영업이익증가율', '순이익증가율'],
'🛡️ 안정성 지표': ['부채비율', '자기자본비율', '부채자산비율'],
'🔄 활동성 지표': ['총자산회전율'],
'⭐ 종합평가': ['종합점수', '투자등급']
}
for category, metrics in categories.items():
category_ratios = {k: v for k, v in ratios.items() if k in metrics}
if category_ratios:
print(f"\n{category}:")
for metric, value in category_ratios.items():
print(f" • {metric}: {value}")
# 기타 정보 출력
other_ratios = {k: v for k, v in ratios.items()
if k not in sum(categories.values(), [])
and not k.endswith('오류')}
if other_ratios:
print(f"\n📝 추가 정보:")
for key, value in other_ratios.items():
print(f" • {key}: {value}")
# 오류 정보 출력
error_ratios = {k: v for k, v in ratios.items() if k.endswith('오류')}
if error_ratios:
print(f"\n⚠️ 분석 제한사항:")
for key, value in error_ratios.items():
print(f" • {key}: {value}")
print(f"\n{'='*60}")
return ratios
def compare_companies(self, tickers: list) -> pd.DataFrame:
"""여러 기업 재무비율 비교"""
print(f"\n{'='*70}")
print(f"🔍 기업 비교 분석 ({len(tickers)}개 기업)")
print(f"{'='*70}")
comparison_data = []
for ticker in tickers:
print(f"\n📊 {ticker} 분석 중...")
income_df = self.get_financial_statement(ticker)
if income_df is None:
continue
balance_df = self.get_balance_sheet(ticker)
ratios = self.calculate_extended_ratios(income_df, balance_df)
# 비교용 데이터 추출
company_data = {'종목코드': ticker}
# 주요 지표만 선택
key_metrics = ['영업이익률', '순이익률', 'ROE(자기자본수익률)', 'ROA(총자산수익률)',
'매출액증가율', '영업이익증가율', '부채비율', '자기자본비율', '종합점수', '투자등급']
for metric in key_metrics:
company_data[metric] = ratios.get(metric, 'N/A')
comparison_data.append(company_data)
# 데이터프레임 생성
if comparison_data:
comparison_df = pd.DataFrame(comparison_data)
print(f"\n📋 비교 결과:")
print("-" * 70)
print(comparison_df.to_string(index=False))
return comparison_df
else:
print("❌ 비교할 데이터가 없습니다")
return pd.DataFrame()
def main():
analyzer = EnhancedFinancialAnalyzer()
# 1. 단일 기업 심화 분석
print("🎯 심화 분석 예시")
analyzer.analyze_financials_extended("005930") # 삼성전자
# 2. 여러 기업 비교 분석
print("\n" + "="*80)
print("🎯 기업 비교 분석 예시")
tech_companies = ["005930", "000660", "035420"] # 삼성전자, SK하이닉스, 네이버
comparison_result = analyzer.compare_companies(tech_companies)
if not comparison_result.empty:
# 특정 지표 기준 랭킹
try:
print(f"\n🏆 ROE 기준 순위:")
roe_ranking = comparison_result[comparison_result['ROE(자기자본수익률)'] != 'N/A'].copy()
if not roe_ranking.empty:
roe_ranking['ROE_숫자'] = roe_ranking['ROE(자기자본수익률)'].str.replace('%', '').astype(float)
roe_ranking = roe_ranking.sort_values('ROE_숫자', ascending=False)
for idx, row in roe_ranking.iterrows():
print(f" {idx+1}위: {row['종목코드']} - {row['ROE(자기자본수익률)']}")
except Exception as e:
print(f"랭킹 계산 오류: {e}")
if __name__ == "__main__":
main()
Comments