일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 머신러닝
- 이력서 첨삭
- sklearn
- 주요 파라미터
- 데이터사이언스학과
- 커리어전환
- 퀀트
- 데이터사이언티스트
- 코딩테스트
- 데이터 사이언스
- 하이퍼 파라미터
- 자기소개서
- 데이터사이언스
- 데이터 사이언티스트
- pandas
- 베이지안 최적화
- 경력기술서 첨삭
- AutoML
- 파이썬
- 하이퍼 파라미터 튜닝
- 데이터분석
- 대학원
- 주식데이터
- 경력 기술서
- 판다스
- 파라미터 튜닝
- 퀀트 투자 책
- 사이킷런
- 주가데이터
- 랜덤포레스트
- Today
- Total
GIL's LAB
Open DART를 이용한 기업공시 수집 (3) 주요 재무지표 수집 및 가공 본문
개요
이번 포스팅에서는 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)
데이터 분석 서비스가 필요한 분은 아래 링크로!
'퀀트 투자 > 데이터 수집' 카테고리의 다른 글
FinanceDataReader를 이용한 전체 주식 데이터 수집 (0) | 2021.12.16 |
---|---|
Open DART를 이용한 기업공시 수집 (4) 전체 재무제표 수집 (5) | 2021.12.15 |
Open DART를 이용한 기업공시 수집 (2) 주당 배당금 수집 (14) | 2021.10.11 |
Open DART를 이용한 기업공시 수집 (1) 환경 설정 (0) | 2021.10.11 |
오픈 API를 이용한 주식 데이터 수집하기 (3) 분틱 코스피/코스닥 데이터 수집 방법 (1) | 2021.09.17 |