OpenDartReader 로 종목을 분류해보자 (2)
프로젝트/[ing]_백테스팅_툴

OpenDartReader 로 종목을 분류해보자 (2)

728x90
반응형

 

 

 

앞선 포스팅에도 말씀 드렸듯이, 이번엔 Pykrx 를 사용해서 시가총액을 불러오고 그 데이터를 앞 포스팅의 dataframe 과 합쳐보도록 하겠습니다. 되도록 앞의 내용을 보고 오시는걸 추천드립니다 ~ 

 

2021.12.02 - [프로젝트/[ing]_백테스팅_툴] - OpenDartReader 로 종목을 분류해보자 (1)

 

OpenDartReader 로 종목을 분류해보자 (1)

기업을 분석하는데는 기본적으로 여러 데이터가 필요합니다. 그 중에서도 기업의 기본 (펀더멘털) 이라 할 수 있는 재무데이터 분석이 필수적이라 할 수 있죠. 하지만 분석할 기업은 많습니다. 2

dragon1-honey1-wayfarer.tistory.com

 

 

1. Pykrx ?

pykrx 는 Naver 나 KRX 에서 주가정보를 스크래핑 하는 모듈 입니다. 

이 모듈을 통해 데이터를 간단하게 취득할 수 있습니다.

 

 

설치법이나 간단한 사용법은 아래 사이트 참조하세요

 

https://github.com/sharebook-kr/pykrx

 

GitHub - sharebook-kr/pykrx: KRX 주식 정보 스크래핑

KRX 주식 정보 스크래핑. Contribute to sharebook-kr/pykrx development by creating an account on GitHub.

github.com

 

여기서 사용할 함수는 get_market_cap_by_date 입니다. 일자별로 시가총액을 조회하는 함수 입니다. 홈페이지에선 get_market_cap 으로 오타가 나있네요;;

 

2. 활용

 

간단한 사용법은 아래 코드와 같습니다. 

 

from pykrx import stock
df = stock.get_market_cap_by_date("20211112", "20211113", "005930")
print(df)

 

이에 대한 결과 입니다.

 

                       시가총액       거래량          거래대금       상장주식수
날짜
2021-11-12  421466648030000  10087450  711487813500  5969782550

 

이제 여기서 시가총액의 데이터를 추출하면 됩니다. 하루만 보고 싶다면 시작날짜와 끝날짜를 동일하게 입력하면 됩니다. 하나 더 처리해줘야 할 게 있는데, 분기말의 일자가 주말일 경우 입니다. 

 

실제 위의 코드에선 11/12 (금) , 11/13 (토) 를 요청했으나 반환된건 12일만 나옵니다. 이를 통해 Pykrx 에서도 주말의 데이터는 따로 처리하지 않음을 알 수 있습니다. 

 

따라서 토요일이면 1일을 뺀 날짜를, 일요일이면 2일을 뺀 날짜의 데이터로 대체하는 방식을 취하겠습니다.

 

from pykrx import stock
from datetime import datetime

date_year = 2021
date_month = 11
date_day = 14
date = str(date_year) + str(date_month) + str(date_day) # "20211114"
date_formated = datetime.strptime(date,"%Y%m%d") # datetime format 으로 변환

# 5 : 토요일, 6: 일요일
if date_formated.weekday() == 5:
    date_day -= 1 # 토요일일 경우 1일을 뺀다
elif date_formated.weekday() == 6:
    date_day -= 2 # 일요일일 경우 2일을 뺀다

date = str(date_year) + str(date_month) + str(date_day) # "20211112"
date_2 = str(date_year) + '-' + str(date_month) + '-' + str(date_day) # "2021-11-12"
df = stock.get_market_cap_by_date(date, date, "005930") # 1일만 출력

print(date)
print(df.loc[date_2]['시가총액'])

 

이에 대한 결과값은 아래와 같습니다.

 

20211112
421466648030000

 

특별한 케이스로, 3분기말, 09-30 의 경우, 추석 연휴에 해당할 수도 있습니다. 다행히도 OpenDart 의 데이터 제공 범위인 2015년 부터 현재까진 2020년 한번을 제외하곤 없네요. 2030년 이전엔 2023년 추석이 있습니다.

2020년은 하루를 빼면 되고, 2023년의 3분기말엔 3일을 뺀 시가총액 데이터를 넣을 예정입니다. 

 

 

 

 

년도에 상관없는, 좀 더 강건한 로직을 작성하고 싶다면 천문데이터 API 를 통해 공휴일 날짜를 받아서 반영하거나 음력 달력 모듈을 받아 추석에 대한 공휴일 처리를 하셔야 하겠습니다.

 

3. 접목

전 포스팅의 코드와 합쳐보겠습니다. 코드가 커지는 만큼 먼저 추가되는 부분에 대해 설명을 하고 전체코드를 아래에 첨부하겠습니다. 이전 포스팅의 코드에서도 일부 미흡한 부분이 있어 이번 코드에 함께 수정하여 반영하였습니다.

 

컬럼이 추가된 만큼 초기 선언 및 코드 끝의 초기화 부분 df2 를 수정해야 합니다.

 

# 정리된 Data. index 가 없으면 추가가 안되므로 dummy를 하나 넣어둔다.
df2 = pd.DataFrame(columns=['유동자산', '부채총계', '자본총계', '매출액', '매출총이익', '영업이익',
                            '당기순이익', '영업활동현금흐름', '잉여현금흐름', '시가총액'], index=['1900-01-01'])

 

루프 내 초기화 하는 부분에선 시가총액 데이터와 년도 변수 (문자타입) 정의를 추가하였습니다.

 

        # 더미 리스트 초기화
        current_assets = [0, 0, 0, 0] # 유동자산
        liabilities = [0, 0, 0, 0] # 부채총계
        equity = [0, 0, 0, 0] # 자본총계
        revenue = [0, 0, 0, 0] # 매출액 
        grossProfit = [0, 0, 0, 0] # 매출총이익
        income = [0, 0, 0, 0] # 영업이익
        net_income = [0, 0, 0, 0] # 당기순이익
        cfo = [0, 0, 0, 0] # 영업활동현금흐름
        cfi = [0, 0, 0, 0] # 투자활동현금흐름
        fcf = [0, 0, 0, 0] # 잉여현금흐름 : 편의상 영업활동 - 투자활동 현금흐름으로 계산
        market_cap = [0, 0, 0, 0] # 시가총액
        date_year = str(i) # 년도 변수 지정

 

주말에 대한 처리와 3분기 추석에 대한 처리를 시행하기 위해서 날짜를 년 / 월 / 일로 쪼개서 선언 하였습니다.

그래서 보고서 종류에 따른 if else 문에는 단순히 월, 일을 정의하는 문장만 있습니다. 이후에 주말에 대한 처리, 추석에 대한 처리가 공통적으로 이어집니다.

그 다음 Pykrx 를 활용하여 시가총액 값을 추출합니다.

 

                if k == '11013': # 1분기
                    date_month = '03'
                    date_day = 31 # 일만 계산할꺼니까 이것만 숫자로 지정

                elif k == '11012': # 2분기
                    date_month = '06'
                    date_day = 30

                elif k == '11014': # 3분기
                    date_month = '09'
                    date_day = 30

                else: # 4분기. 1 ~ 3분기 데이터를 더한다음 사업보고서에서 빼야 함
                    date_month = '12'
                    date_day = 30
                    revenue[j] = revenue[j] - (revenue[0] + revenue[1] + revenue[2])
                    grossProfit[j] = grossProfit[j] - (grossProfit[0] + grossProfit[1] + grossProfit[2])
                    income[j] = income[j] - (income[0] + income[1] + income[2])
                    net_income[j] = net_income[j] - (net_income[0] + net_income[1] + net_income[2])
                    fcf[j] = fcf[j] - (fcf[0] + fcf[1] + fcf[2])

                path_string = date_year + '-' + date_month + '-' + str(date_day)

                # 날짜 계산을 위한 변수 정의
                date = date_year + date_month + str(date_day)
                date_formated = datetime.strptime(date,"%Y%m%d") # datetime format 으로 변환
                
                # 주말에 대한 처리
                if date_formated.weekday() == 5:
                    date_day -= 1 # 토요일일 경우 1일을 뺀다
                elif date_formated.weekday() == 6:
                    date_day -= 2 # 일요일일 경우 2일을 뺀다

                # 3분기 추석에 대한 처리. 2020년은 1일을 빼고 2023년은 3일을 뺀다.
                if date_month == '09' and date_year == '2020':
                    date_day -= 1
                elif date_month == '09' and date_year == '2023':
                    date_day -= 3

                date = date_year + date_month + str(date_day) # 뺀 날짜에 대해 재정의
                date_2 = date_year + '-' + date_month + '-' + str(date_day) # 재정의된 날짜의 YYYY-MM-DD 형태
                df3 = stock.get_market_cap_by_date(date, date, str(stocks)) # 시가총액 데이터프레임 호출
                market_cap[j] = df3.loc[date_2]['시가총액'] # 시가총액 데이터 추출

 

 

데이터프레임에 저장하는 부분과 데이터 재초기화 하는 부분에도 시가총액 column 을 추가했습니다.

 

                # 데이터프레임에 저장
                df2.loc[path_string] = [current_assets[j], liabilities[j], equity[j],
                                    revenue[j], grossProfit[j], income[j], net_income[j], cfo[j], fcf[j], market_cap[j]]                
                df2.tail()
            time.sleep(0.2) # 잦은 API 호출은 서버에서 IP 차단을 당하므로 호출 간격을 둔다.
    df2.drop(['1900-01-01'], inplace=True) # 첫 행 drop
    df2.to_excel(fileName) # 파일 저장. 저장할 때 파일의 경로지정을 해야 함. 각 종목코드별로 다른 이름으로 저장
    df2 = pd.DataFrame(columns=['유동자산', '부채총계', '자본총계', '매출액', '매출총이익', '영업이익',
                            '당기순이익', '영업활동현금흐름', '잉여현금흐름', '시가총액'], index=['1900-01-01'])

 

전체 코드는 아래와 같습니다.

 

import OpenDartReader
import pandas as pd
import time
from datetime import datetime
from pykrx import stock

api_key = ' ' # OpenDart API KEY
stock_names = ['005930'] # 삼성전자 종목코드
dart = OpenDartReader(api_key)

# 정리된 Data. index 가 없으면 추가가 안되므로 dummy를 하나 넣어둔다.
df2 = pd.DataFrame(columns=['유동자산', '부채총계', '자본총계', '매출액', '매출총이익', '영업이익',
                            '당기순이익', '영업활동현금흐름', '잉여현금흐름', '시가총액'], index=['1900-01-01']) 

# '11013'=1분기보고서, '11012' =반기보고서, '11014'=3분기보고서, '11011'=사업보고서
reprt_code = ['11013', '11012', '11014', '11011']

for stocks in stock_names:
    fileName = f'C:/Projects/stocks_sorting/data/result_{str(stocks)}.xlsx'
    for i in range(2015, 2016): # OpenDart는 2015년부터 정보를 제공한다.
        # 더미 리스트 초기화
        current_assets = [0, 0, 0, 0] # 유동자산
        liabilities = [0, 0, 0, 0] # 부채총계
        equity = [0, 0, 0, 0] # 자본총계
        revenue = [0, 0, 0, 0] # 매출액 
        grossProfit = [0, 0, 0, 0] # 매출총이익
        income = [0, 0, 0, 0] # 영업이익
        net_income = [0, 0, 0, 0] # 당기순이익
        cfo = [0, 0, 0, 0] # 영업활동현금흐름
        cfi = [0, 0, 0, 0] # 투자활동현금흐름
        fcf = [0, 0, 0, 0] # 잉여현금흐름 : 편의상 영업활동 - 투자활동 현금흐름으로 계산
        market_cap = [0, 0, 0, 0] # 시가총액
        date_year = str(i) # 년도 변수 지정
        
	    # '11013'=1분기보고서, '11012' =반기보고서, '11014'=3분기보고서, '11011'=사업보고서 순서대로 루프를 태운다.
        for j, k in enumerate(reprt_code): 
            df1 = pd.DataFrame() # Raw Data
            if str(type(dart.finstate_all(stocks, i, reprt_code=k, fs_div='CFS'))) == "<class 'NoneType'>":
                pass
            else: # 타입이 NoneType 이 아니면 읽어온다.
                df1 = df1.append(dart.finstate_all(stocks, i, reprt_code=k, fs_div='CFS')) 
                # 재무상태표 부분
                condition = (df1.sj_nm == '재무상태표') & (df1.account_nm == '유동자산') # 유동자산
                condition_2 = (df1.sj_nm == '재무상태표') & (df1.account_nm == '부채총계') # 부채총계
                condition_3 = (df1.sj_nm == '재무상태표') & \
                            ((df1.account_nm == '자본총계') | (df1.account_nm == '반기말자본') | (df1.account_nm == '3분기말자본') | (df1.account_nm == '분기말자본') | (df1.account_nm == '1분기말자본'))  #자본총계
                # 손익계산서 부분
                condition_4 = ((df1.sj_nm == '손익계산서') | (df1.sj_nm == '포괄손익계산서')) & ((df1.account_nm == '매출액') | (df1.account_nm == '수익(매출액)'))
                condition_5 = ((df1.sj_nm == '손익계산서') | (df1.sj_nm == '포괄손익계산서')) & (df1.account_nm == '매출총이익')
                condition_6 = ((df1.sj_nm == '손익계산서') | (df1.sj_nm == '포괄손익계산서')) & \
                                ((df1.account_nm == '영업이익(손실)') | (df1.account_nm == '영업이익'))
                condition_7 = ((df1.sj_nm == '손익계산서') | (df1.sj_nm == '포괄손익계산서')) & \
                                ((df1.account_nm == '당기순이익(손실)') | (df1.account_nm == '당기순이익') | \
                                (df1.account_nm == '분기순이익') | (df1.account_nm == '분기순이익(손실)') | (df1.account_nm == '반기순이익') | (df1.account_nm == '반기순이익(손실)') | \
                                (df1.account_nm == '연결분기순이익') | (df1.account_nm == '연결반기순이익')| (df1.account_nm == '연결당기순이익')|(df1.account_nm == '연결분기(당기)순이익')|(df1.account_nm == '연결반기(당기)순이익')|\
                                (df1.account_nm == '연결분기순이익(손실)'))
                # 현금흐름표 부분
                condition_8 = (df1.sj_nm == '현금흐름표') & ((df1.account_nm == '영업활동으로 인한 현금흐름') | (df1.account_nm == '영업활동 현금흐름'))
                condition_9 = (df1.sj_nm == '현금흐름표') & ((df1.account_nm == '투자활동으로 인한 현금흐름') | (df1.account_nm == '투자활동 현금흐름'))
                            
                current_assets[j] = int(df1.loc[condition].iloc[0]['thstrm_amount'])
                liabilities[j] = int(df1.loc[condition_2].iloc[0]['thstrm_amount'])
                equity[j] = int(df1.loc[condition_3].iloc[0]['thstrm_amount'])
                revenue[j] = int(df1.loc[condition_4].iloc[0]['thstrm_amount'])
                grossProfit[j] = int(df1.loc[condition_5].iloc[0]['thstrm_amount'])
                income[j] = int(df1.loc[condition_6].iloc[0]['thstrm_amount'])
                net_income[j] = int(df1.loc[condition_7].iloc[0]['thstrm_amount'])
                cfo[j] = int(df1.loc[condition_8].iloc[0]['thstrm_amount'])
                cfi[j] = int(df1.loc[condition_9].iloc[0]['thstrm_amount'])
                fcf[j] = (cfo[j] - cfi[j])
                
                if k == '11013': # 1분기
                    date_month = '03'
                    date_day = 31 # 일만 계산할꺼니까 이것만 숫자로 지정

                elif k == '11012': # 2분기
                    date_month = '06'
                    date_day = 30

                elif k == '11014': # 3분기
                    date_month = '09'
                    date_day = 30

                else: # 4분기. 1 ~ 3분기 데이터를 더한다음 사업보고서에서 빼야 함
                    date_month = '12'
                    date_day = 30
                    revenue[j] = revenue[j] - (revenue[0] + revenue[1] + revenue[2])
                    grossProfit[j] = grossProfit[j] - (grossProfit[0] + grossProfit[1] + grossProfit[2])
                    income[j] = income[j] - (income[0] + income[1] + income[2])
                    net_income[j] = net_income[j] - (net_income[0] + net_income[1] + net_income[2])
                    fcf[j] = fcf[j] - (fcf[0] + fcf[1] + fcf[2])

                path_string = date_year + '-' + date_month + '-' + str(date_day)

                # 날짜 계산을 위한 변수 정의
                date = date_year + date_month + str(date_day)
                date_formated = datetime.strptime(date,"%Y%m%d") # datetime format 으로 변환
                
                # 주말에 대한 처리
                if date_formated.weekday() == 5:
                    date_day -= 1 # 토요일일 경우 1일을 뺀다
                elif date_formated.weekday() == 6:
                    date_day -= 2 # 일요일일 경우 2일을 뺀다

                # 3분기 추석에 대한 처리. 2020년은 1일을 빼고 2023년은 3일을 뺀다.
                if date_month == '09' and date_year == '2020':
                    date_day -= 1
                elif date_month == '09' and date_year == '2023':
                    date_day -= 3

                date = date_year + date_month + str(date_day) # 뺀 날짜에 대해 재정의
                date_2 = date_year + '-' + date_month + '-' + str(date_day) # 재정의된 날짜의 YYYY-MM-DD 형태
                df3 = stock.get_market_cap_by_date(date, date, str(stocks)) # 시가총액 데이터프레임 호출
                market_cap[j] = df3.loc[date_2]['시가총액'] # 시가총액 데이터 추출

                # 데이터프레임에 저장
                df2.loc[path_string] = [current_assets[j], liabilities[j], equity[j],
                                    revenue[j], grossProfit[j], income[j], net_income[j], cfo[j], fcf[j], market_cap[j]]                
                df2.tail()
            time.sleep(0.2) # 잦은 API 호출은 서버에서 IP 차단을 당하므로 호출 간격을 둔다.
    df2.drop(['1900-01-01'], inplace=True) # 첫 행 drop
    df2.to_excel(fileName) # 파일 저장. 저장할 때 파일의 경로지정을 해야 함. 각 종목코드별로 다른 이름으로 저장
    df2 = pd.DataFrame(columns=['유동자산', '부채총계', '자본총계', '매출액', '매출총이익', '영업이익',
                            '당기순이익', '영업활동현금흐름', '잉여현금흐름', '시가총액'], index=['1900-01-01'])

 

코드를 실행하면 다음과 같은 데이터를 얻을 수 있습니다.

 

 

이제 원하는 지표값으로 계산하는 일만 남았습니다. 

다음 포스팅에서 위 결과값을 가지고 PER, NCAV 등의 데이터 값을 계산하는 걸 해보도록 하겠습니다.

 

 

 

728x90
반응형