아미(아름다운미소)

재무제표 최종 본문

카테고리 없음

재무제표 최종

유키공 2025. 8. 7. 08:44
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