GIL's LAB

Open DART를 이용한 기업공시 수집 (3) 주요 재무지표 수집 및 가공 본문

퀀트 투자/데이터 수집

Open DART를 이용한 기업공시 수집 (3) 주요 재무지표 수집 및 가공

GIL~ 2021. 10. 12. 18:23

개요

이번 포스팅에서는 OpenDartReader를 이용하여 코스피/코스닥 기업의 주요 재무지표를 수집하고 가공하겠습니다. 

구체적으로 수집 및 가공할 재무지표는 다음과 같습니다.

  • 자산총계
  • 부채총계
  • 자본총계
  • 매출액
  • 영업이익
  • 당기순이익
  • 부채비율
  • 영업이익증가율
  • 매출액증가율
  • 당기순이익 증가율
  • 매출액 상태
  • 영업이익 상태
  • 당기순이익 상태
  • ROA
  • ROE

 

관련 패키지 설치와 사용 방법은 이전 포스팅을 참고해주시기 바랍니다.

 

finstate 메서드

finstate 메서드는 기업의 재무 정보를 가져오는데 사용하는 메서드로, 다음과 같이 사용합니다.

finstate(corp, bsns_year, reprt_code)
  • corp: 기업명
  • bsns_year: 사업연도
  • reprt_code: 보고서 유형 ('11011': 사업보고서, '11012': 반기보고서, '11013': 1분기보고서, '11014': 3분기보고서)

이 메서드는 아래 컬럼을 가지는 데이터프레임을 반환합니다.

컬럼 설명 예시
account_nm 계정명 자본총계, 영업이익, 당기순이익 등
fs_nm 개별/연결명 연결재무제표 또는 재무제표
sj_nm 재무제표명 재무상태표 또는 손익계산서
thstrm_dt 당기일자 2019.12.31 현재, 2019.01.01 ~ 2019.12.31
thstrm_amount 당기금액 166,009,000,000

계정명과 당기/전기/전전기 금액 컬럼을 바탕으로 우리가 원하는 값을 가져올 수 있습니다.

간단한 예제로 LG전자의 2020년 재무 정보 가운데, 당기의 당기순이익, 영업이익, 매출액을 가져와보겠습니다.

result = dart.finstate("LG전자", 2020, "11011") # 2020년 LG전자 사업보고서 내 재무 정보 가져오기
result = result.loc[result['fs_nm'] == "연결재무제표"] # 연결재무제표 필터링
# account_nm이 당기순이익, 영업이익, 매출액 중 하나인 경우에만 가져오기
result = result.loc[result['account_nm'].isin(['당기순이익', '영업이익', '매출액'])] 

# 열 필터링
result = result.loc[:, ["account_nm", "fs_nm", "sj_nm", "thstrm_dt", "thstrm_amount"]]
display(result)

 

수집 과정

그럼 이제 본격적으로 재무지표 데이터를 수집해보겠습니다.

먼저, OpenDartReader 모듈을 불러와서 관련 객체 (dart)를 생성합니다.

지난 포스팅에서도 설명했지만, my_api에 들어가는 값은 개인 인증키입니다 (여기서 쓴 값은 실제 인증키가 아닙니다).

import OpenDartReader
my_api = "eeb1b205aa3390eb4f4028xxxa4a75a9ad77d1xx"
dart = OpenDartReader(my_api)

그리고 역시 지난번과 유사하게, 수집할 종목들을 stock_name_list에 저장하겠습니다.

stock_list = stock_list.loc[stock_list['Market'].isin(["KOSPI", "KOSDAQ"])] # Market 기준 필터링
stock_name_list = stock_list.loc[stock_list['Region'].notnull(), 'Name'].tolist() # 선물과 같이 KOSPI에는 있으나 배당이 없을수밖에 없는 종목은 제외

이제 종목명과 연도, 그리고 수집할 지표 목록을 입력받아, 주요 재무지표를 찾아서 반환하는 함수 find_financial_indicators를 작성하겠습니다.

 

이 함수를 찬찬히 뜯어보겠습니다.

이 함수의 출력은 데이터프레임으로, 각 행은 stock_name이라는 종목의 year, year-1, year-2의 재무지표를 나타냅니다. 

먼저, dart.finstate 메서드를 이용하여 stock_name이라는 종목의 year이라는 연도의 사업보고서를 report에 저장합니다.

만약 report가 없다면 (즉, None이라면), 종목명과 연도를 제외한 모든 값을 결측으로 하는 data를 만들어 반환합니다.

그렇지 않다면, 계정(account_nm)이 입력한 재무지표만 갖도록 필터링해줍니다.

이때, 기업에 따라서 연결재무제표가 있기도 하고, 그렇지 않기도 합니다. 

연결재무제표가 있다면 연결재무제표를 사용하고, 그렇지 않다면 재무제표를 사용하도록 필터링해줍니다.

그리고나서 data를 빈 리스트로 초기화한 뒤, y와 c를 변수로 하는 for문을 돕니다.

y는 연도를, c는 대응되는 기수의 변수명을 나타냅니다.

그리고나서 각 지표를 순회하면서 account_nm이 지표인 행이면서 컬럼이 c인 값을 record에 추가합니다.

import numpy as np
import pandas as pd
def find_financial_indicators(stock_name, year, indicators):
    report = dart.finstate(stock_name, year) # 데이터 가져오기
    if report is None: # 리포트가 없다면 (참고: 리포트가 없으면 None을 반환함)
        # 리포트가 없으면 당기, 전기, 전전기 값 모두 제거
        data = [[stock_name, year] + [np.nan] * len(indicators)]
        data = [[stock_name, year-1] + [np.nan] * len(indicators)]
        data = [[stock_name, year-2] + [np.nan] * len(indicators)]
        return pd.DataFrame(data, columns = ["기업", "연도"] + indicators)
    
    else:
        report = report[report['account_nm'].isin(indicators)] # 관련 지표로 필터링
        if sum(report['fs_nm'] == "연결재무제표") > 0:
            # 연결재무제표 데이터가 있으면 연결재무제표를 사용
            report = report.loc[report['fs_nm'] == "연결재무제표"]
            
        else:
            # 연결재무제표 데이터가 없으면 일반재무제표를 사용
            report = report.loc[report['fs_nm'] == "재무제표"]
        
        data = []
        for y, c in zip([year, year-1, year-2], ['thstrm_amount', 'frmtrm_amount', 'bfefrmtrm_amount']):
            record = [stock_name, y]
            for indic in indicators:
                # account_nm이 indic인 행의 c 컬럼 값을 가져옴
                if sum(report['account_nm'] == indic) > 0: # 해당 지표가 있다면 추가 (지표가 없는 경우도 있음)
                    value = report.loc[report['account_nm'] == indic, c].iloc[0]
                else:
                    value = np.nan
                
                record.append(value)
            
            data.append(record)
        return pd.DataFrame(data, columns = ["기업", "연도"] + indicators)

 

위 함수를 적용했을 때의 예제는 아래와 같습니다.

indicators = ['자산총계', '부채총계', '자본총계', '매출액', '영업이익', '당기순이익']
display(find_financial_indicators("삼성전자", 2020, indicators))

 

이제 이 함수를 모든 종목과 연도에 대해 적용하는 방식으로 수집을 완료해줍니다.

import time 
indicators = ['자산총계', '부채총계', '자본총계', '매출액', '영업이익', '당기순이익']
data = pd.DataFrame() # 이 데이터프레임에 각각의 데이터를 추가할 예정

for idx, stock_name in enumerate(stock_name_list):
    print(idx+1, "/", len(stock_name_list)) # 현재까지 진행된 상황 출력
    for year in [2015, 2018, 2020]:
        try: # 재무제표 데이터가 잘 불러와지지 않는 경우가 있어, try - except으로 넘김
            result = find_financial_indicators(stock_name, year, indicators) # 재무지표 데이터 
        except:
            pass
        data = pd.concat([data, result], axis = 0, ignore_index = True) # data에 부착
        time.sleep(0.5)

 

데이터 정제

위 코드에서 알 수 있듯이, year로 2018년과 2020년을 사용했기에, year = 2018일때의 당기와 year = 2020일때의 전전기가 2018년으로 겹칩니다. 즉, 중복된 행이 발생합니다.

따라서 중복된 행을 먼저 제거해줍니다.

# 중복된 행 제거: 2018년 값이 중복되므로
data.drop_duplicates(inplace = True)

그리고 지표들이 숫자가 아니라 문자로 되어 있습니다.

역시 숫자로 바꿔주기 위한 작업을 해줍니다.

# 숫자로 모두 변환
def str_to_float(value):
    if type(value) == float: # nan의 경우에는 문자가 아니라, 이미 float 취급됨
        return value
    elif value == '-': # -로 되어 있으면 0으로 변환
        return 0
    else:
        return float(value.replace(',', ''))

for indc in indicators:
    data[indc] = data[indc].apply(str_to_float)

 

주요 지표 계산

이제 주요 지표를 data의 새로운 열로 추가해주겠습니다.

 

부채 비율

부채비율은 아래와 같이 계산하며, 파이썬에서는 두 열에 대한 유니버설 연산으로 쉽게 구할 수 있습니다.

부채비율 = 자본 총계 / 자산 총계
data['부채비율'] = data['자본총계'] / data['자산총계'] * 100
display(data['부채비율'].head())

 

영업이익/매출액/당기순이익 증가율

영업이익 증가율은 다음과 같이 계산합니다.

영업이익 증가율 = (당기 영업이익 - 전기 영업이익) / 전기 영업이익 * 100%

코드로는 기업과 연도를 기준으로 정렬한 뒤, diff 함수를 사용하여 (당기 영업이익 - 전기 영업이익)을 계산하는 방식으로 계산합니다. 단, 연도가 2013년도인 경우에는 위의 행이 없거나, 있더라도 다른 종목의 2020년도 영업이익이기 때문에, 결측으로 바꿔줍니다.

data.sort_values(by = ['기업', '연도'], inplace = True) # 기업과 연도를 기준으로 정렬
data['영업이익증가율'] = data['영업이익'].diff() / data['영업이익']
data.loc[data['연도'] == 2013, '영업이익증가율'] = np.nan
# 2013년도에는 전기 정보가 없으므로 계산 불가 (계산된 것들은 다른 종목이랑 섞인 것)

 

매출액과 당기 순이익도 같은 방식으로 구현합니다.

data.sort_values(by = ['기업', '연도'], inplace = True) # 기업과 연도를 기준으로 정렬
data['매출액증가율'] = data['매출액'].diff() / data['매출액']
data.loc[data['연도'] == 2013, '매출액증가율'] = np.nan
# 2013년도에는 전기 정보가 없으므로 계산 불가 (계산된 것들은 다른 종목이랑 섞인 것)
data.sort_values(by = ['기업', '연도'], inplace = True) # 기업과 연도를 기준으로 정렬
data['당기순이익증가율'] = data['당기순이익'].diff() / data['당기순이익']
data.loc[data['연도'] == 2013, '당기순이익증가율'] = np.nan
# 2013년도에는 전기 정보가 없으므로 계산 불가 (계산된 것들은 다른 종목이랑 섞인 것)

 

영업이익/매출액/당기순이익 상태

이제 매출액, 영업이익, 당기순이익의 상태를 구하겠습니다.

상태는 흑자지속, 적자지속, 흑자전환, 적자전환으로 다음과 같이 구분합니다.

  • 전기 흑자 → 당기 흑자: 흑자 지속
  • 전기 흑자 → 당기 적자: 적자 전환
  • 전기 적자 → 당기 흑자: 흑자 전환
  • 전기 적자 → 당기 적자: 적자 지속 

파이썬 구현은 아래와 같이 합니다.

여기서 iloc를 사용하여 앞쪽 조건에는 당기에 대한 조건을, 뒤쪽 조건에는 전기에 대한 조건을 계산했다고 볼 수 있습니다.

data['매출액_상태'] = np.nan # 상태를 모두 결측으로 초기화

# iloc[1:]: 현재, iloc[:-1]: 과거
data.loc[(data['매출액'].iloc[1:] > 0) & (data['매출액'].iloc[:-1] > 0), '매출액_상태'] = "흑자지속"
data.loc[(data['매출액'].iloc[1:] <= 0) & (data['매출액'].iloc[:-1] <= 0), '매출액_상태'] = "적자지속"
data.loc[(data['매출액'].iloc[1:] > 0) & (data['매출액'].iloc[:-1] <= 0), '매출액_상태'] = "흑자전환"
data.loc[(data['매출액'].iloc[1:] <= 0) & (data['매출액'].iloc[:-1] > 0), '매출액_상태'] = "적자전환"

data.loc[data['연도'] == 2013, '매출액_상태'] = np.nan

상태를 계산하는 이유는 이 상태 자체로도 투자하는데 참고할 수 있지만, 증가율에 대한 보조 지표로도 사용할 수 있기 때문입니다.

가령, 영업이익이 -100원이었다가 100원으로 되는 경우에는, 영업이익 계산식에 의해 증가율이 -200%라는 납득하기 어려운 수치가 나옵니다. 

 

ROA (Return on assets)

ROA는 다음과 같이 계산할 수 있습니다.

(당기순이익 / 자산총계) * 100%
data['ROA'] = data['당기순이익'] / data['자산총계']

 

ROE (Return on equity)

ROE는 다음과 같이 계산할 수 있습니다. 

여기서 평균 자기 자본은 (전기자본총계 + 당기자본총계) / 2로 계산하므로, rolling 메서드를 이용하여 구현했습니다. 

ROE = (당기순이익 / 평균 자기 자본)
average_equity = data['자본총계'].rolling(2).mean() # 평균 자기 자본
data['ROE'] = data['당기순이익'] / average_equity

 

이제 해당 데이터를 저장하면 원하는 재무정보를 모두 수집하게 됩니다. 

data.to_csv("../../데이터/주요재무지표.csv", index = False)

 


데이터 분석 서비스가 필요한 분은 아래 링크로! 

https://kmong.com/gig/374194 

 

데이터사이언스 박사의 데이터 분석 서비스 드립니다. | 150000원부터 시작 가능한 총 평점 5점의 I

78개 총 작업 개수 완료한 총 평점 5점인 데이터사이언스박사의 IT·프로그래밍, 데이터 분석·시각화 서비스를 68개의 리뷰와 함께 확인해 보세요. IT·프로그래밍, 데이터 분석·시각화 제공 등 150

kmong.com

 

Comments