OpenDartReader와 marcap으로 PER/PBR/ROE등 투자지표 구하기¶
1. 용어정리¶
1.1 PER(주가수익률; Price Earnings Ratio)¶
- 주가를 1주당 순이익(EPS)로 나눈 값
PER이 1이면 시가총액과 기업의 순이익이 동일한 것이고, PER이 10이면 시가총액이 순이익의 10배라는 뜻
보통 PER이 낮으면 주가가 기업의 이익에 비해 저평가된 것이라고 봄
바이오기업이나 테크 기업 같은 경우 시장에서 기대하는 미래가치에 비해 현재의 순이익은 매우 적기 때문에 PER이 매우 크게 나타남. 그렇다고 이들 기업이 투자 가치가 떨어지는 것은 아님
1.2. PBR(주가 순자산 비율; Price Book-value Ratio)¶
- 주가를 1주당 순자산가치로 나눈 값
PBR이 1이면 시가총액과 기업의 순자산가치가 같다는 의미
보통 PBR이 낮으면 주가가 기업의 장부가치에 비해 저평가된 것이라고 봄
1.3. ROE(자기자본이익률; Return On Equity)¶
- 당기순이익을 순자산(자기자본)으로 나눈 값
PBR과 PER을 알고 있으면 ROE를 계산할 수 있음
ROE/PER > 3 일 경우 투자가치가 있다고 판단하는 경우도 있음(브라운스톤공식)
PBR이 같아도 ROE와 PER에 따라 다른 의미를 가짐
- ROE와 PER이 각각 (20%, 5배), (5%, 20배)인 두 기업이 있으면 PBR은 1로 같지만 ROE가 높고 PER이 낮은 기업이 현재 재무구조상 더 좋다고 볼 수 있음
1.4. ROA(총자산수익률; Return On Assets)¶
- 당기순이익을 자산총액으로 나눈 값
특정 기업이 자산을 얼마나 효율적으로 운영했는지 보여주는 지표
일반적으로 ROE의 경우 업계 평균 ROE, 동종기업 ROE와 비교하여 기업 실적을 판단하는데 사용됨
하지만 ROE로는 부채로 발생한 손익여부를 확인할 수 없기 때문에 부채 비중이 큰 업종에는 적합하지 않을 수 있음. 이 경우에는 ROE보다 ROA를 사용하는 것이 적합
2. OpenDartReader로 투자지표 계산에 필요한 정보 조회하기¶
PER을 계산하기 위해 필요한 정보: 주가, 당기순이익, 총발행주식수(보통주+우선주)
PBR을 계산하기 위해 필요한 정보: 주가, 순자산(자본), 총발행주식수(보통주+우선주)
ROE를 계산하기 위해 필요한 정보: 당기순이익, 순자산(자본)
ROA를 계산하기 위해 필요한 정보: 당기순이익, 자산(자본+부채)
4개 지표를 계산하기 위해 필요한 정보는 주가, 당기순이익, 총발행주식수, 자본, 부채
2.1. 자본, 자산, 당기순이익 조회¶
- corp: 종목코드, bsns_year: 사업연도, reprt_code: 보고서코드(1분기보고서 : 11013, 반기보고서 : 11012, 3분기보고서 : 11014, 사업보고서 : 11011)
import OpenDartReader
import pandas as pd
api_key = '발급받은 api key'
dart = OpenDartReader(api_key)
- API KEY 발급방법은 [python] OpenDartReader로 재무정보 조회하기 - (1)재무제표 조회 참고
fs_2019 = dart.finstate_all(corp='005930', bsns_year='2019', fs_div='CFS', reprt_code=11011)
fs_2020_3Q = dart.finstate_all(corp='005930', bsns_year='2020', fs_div='CFS', reprt_code=11014)
- 자본과 자산(자본+부채)은 재무상태표(sj_vis='BIS'), 당기순이익은 포괄손익계산서(sj_div='CIS')에서 확인할 수 있다.
fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['BS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_Equity']), ]
fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['BS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_Liabilities']), ]
fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['IS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_ProfitLossAttributableToOwnersOfParent']), ]
# 자본과 부채는 재무상태표에서 당기금액('thstrm_amount') 값을 가져오면 됨
equity = int(fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['BS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_Equity']), 'thstrm_amount'].replace(",", "")) # 당기자본(자본총계)
liability = int(fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['BS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_Liabilities']), 'thstrm_amount'].replace(",", "")) # 당기부채(부채총계)
assets = equity + liability # 자산총계
- 당기순이익은 '지배기업귀속'에 해당하는 값을 조회
# 2019 4분기 ~ 2020 3분기까지의 당기순이익의 합을 구하려면 2019년 4분기 당기순이익과 2020년 1분기 ~ 3분기 당기순이익의 합을 알아여함
# 2020년 1분기 ~ 3분기 당기순이익의 합은 2020년 3분기 손익계산서에서 'thstrm_add_amount' 값을 가져오면 되고
# 2019년 4분기 당기순이익은 2019년 전체 당기순이익에서 2019년 1분기 ~ 3분기 당기순이익의 합을 빼서 구할 수 있음
profit_2019_3Q = int(fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['IS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_ProfitLossAttributableToOwnersOfParent']), 'frmtrm_add_amount'].replace(",", "")) # 당기순이익
profit_2019 = int(fs_2019.loc[fs_2019['sj_div'].isin(['IS']) & fs_2019['account_id'].isin(['ifrs-full_ProfitLossAttributableToOwnersOfParent']), 'thstrm_amount'].replace(",", "")) # 당기순이익
profit_2020_3Q = int(fs_2020_3Q.loc[fs_2020_3Q['sj_div'].isin(['IS']) & fs_2020_3Q['account_id'].isin(['ifrs-full_ProfitLossAttributableToOwnersOfParent']), 'thstrm_add_amount'].replace(",", "")) # 당기순이익
profit = (profit_2019-profit_2019_3Q) + profit_2020_3Q
profit
2.2. 총발행주식수 조회¶
- 사업보고서 - '소액주주'에서 보통주 총발행 주식수 확인가능
변수명 | 변수설명 |
---|---|
shrholdr_co | 주주수 |
shrholdr_tot_co | 전체 주주수 |
shrholdr_rate | 주주 비율 |
hold_stock_co | 보유 주식수 |
stock_tot_co | 총발행 주식수 |
hold_stock_rate | 보유 주식 비율 |
small = dart.report('005930', '소액주주', 2020, reprt_code=11014)
small
stock_tot_co = int(small['stock_tot_co'].str.replace(',', ''))
stock_tot_co
3. marcap으로 종가와 총발행주식수 구하기¶
문제점: EPS는 보통주와 우선주의 합인 수정평균발행주식수를 이용하여 계산하는데 Open dart API를 통해서는 우선주의 총발행 주식수를 정확하게 확인할 수 있는 방법이 없음
https://financedata.github.io/marcap/ 에서 제공하는 상장기업 시가총액 데이터셋 사용
변수명 | 변수설명 |
---|---|
Date | 날짜 (DatetimeIndex) |
Code | 종목코드 |
Name | 종명이름 |
Open | 시가 |
High | 고가 |
Low | 저가 |
Close | 종가 |
Volume | 거래량 |
Amount | 거래대금 |
Changes | 전일대비 |
ChagesRatio | 전일비 |
Marcap | 시가총액(백만원) |
Stocks | 상장주식수 |
MarcapRatio | 시가총액비중(%) |
ForeignShares | 외국인 보유주식수 |
ForeignRatio | 외국인 지분율(%) |
Rank | 시가총액 순위 (당일) |
# git 명령으로 깃허브의 저장소를 복제(clone)
# 데이터와 데이터를 읽는데 도움이 되는 간단한 파이썬 유틸리티 함수가 포함되어 있음
# 윈도우 + 아나콘다 사용자는 conda install git 으로 git 설치 후 실행
!git clone "https://github.com/FinanceData/marcap.git" marcap
# from marcap import marcap_data
# marcap_data 함수 안에 데이터타입 정의하는 부분에서 에러가 발생하여 직접 함수 정의 후 사용
from datetime import datetime
import numpy as np
import pandas as pd
def marcap_data(start, end=None, code=None):
'''
지정한 기간 데이터 가져오기
:param datetime start: 시작일
:param datetime end: 종료일 (지정하지 않으면 시작일과 동일)
:param str code: 종목코드 (지정하지 않으면 모든 종목)
:return: DataFrame
'''
start = pd.to_datetime(start)
end = start if end==None else pd.to_datetime(end)
df_list = []
# dtypes={'Code':str, 'Name':str,
# 'Open':int, 'High':int, 'Low':int, 'Close':int, 'Volume':int, 'Amount':int,
# 'Changes':int, 'ChangeCode':str, 'ChagesRatio':float, 'Marcap':int, 'Stocks':int,
# 'MarketId':str, 'Market':str, 'Dept':str,
# 'Rank':int}
# Amount, Marcap을 int 타입으로 변환할 수 없다는 에러메시지 발생
dtypes={'Code':str, 'Name':str,
'Open':int, 'High':int, 'Low':int, 'Close':int, 'Volume':int, 'Amount':float,
'Changes':int, 'ChangeCode':str, 'ChagesRatio':float, 'Marcap':float, 'Stocks':int,
'MarketId':str, 'Market':str, 'Dept':str,
'Rank':int}
for year in range(start.year, end.year + 1):
try:
csv_file = 'marcap/data/marcap-%s.csv.gz' % (year)
df = pd.read_csv(csv_file, dtype=dtypes, parse_dates=['Date'])
df_list.append(df)
except Exception as e:
print(e)
pass
df_merged = pd.concat(df_list)
df_merged = df_merged[(start <= df_merged['Date']) & (df_merged['Date'] <= end)]
df_merged = df_merged.sort_values(['Date','Rank'])
if code:
df_merged = df_merged[code == df_merged['Code']]
df_merged.set_index('Date', inplace=True)
return df_merged[df_merged['Volume'] > 0]
# 삼성전자
df_005930 = marcap_data('2021-01-21', code='005930')
df_005930 = df_005930.assign(Amount=df_005930['Amount'].astype('int64'),
Marcap=df_005930['Marcap'].astype('int64'))
df_005930
- 상장주식수('Stocks') 데이터를 가져오는 코드에 문제가 있는 것으로 보임
df_005930['Stocks'].iat[0]
# 시가총액/종가 로 계산하면 맞게 나옴
stocks_005930 = int(df_005930['Marcap']/df_005930['Close'])
stocks_005930
# 삼성전자우
df_005935 = marcap_data('2021-01-21', code='005935')
df_005935 = df_005935.assign(Amount=df_005935['Amount'].astype('int64'),
Marcap=df_005935['Marcap'].astype('int64'))
df_005935
df_005935['Stocks'].iat[0]
stocks_005935 = int(df_005935['Marcap']/df_005935['Close'])
stocks_005935
- 우선주 상장주식수는 맞게 들어가 있음
total_stocks = stocks_005930 + stocks_005935
total_stocks
4. 지표계산¶
EPS = profit/total_stocks
PER = df_005930['Close'].iat[0]/EPS
print('EPS: {}, PER: {}'.format(int(round(EPS)), round(PER, 2)))
BPS = equity/total_stocks
PBR = df_005930['Close'].iat[0]/BPS
print('BPS: {}, PBR: {}'.format(int(round(BPS)), round(PBR, 2)))
ROE = PBR/PER
ROA = profit/assets
print('ROE: {}%, ROA: {}%'.format(round(ROE*100, 2), round(ROA*100, 2)))