본문 바로가기
파이썬으로 종목 스크리너 만들기

뇌동매매 금지 - 3. 분기별 재무정보 조회해보기

by 유티끌 2022. 9. 12.

OpenDartReader를 이용하여 각 종목의 재무정보를 조회해봅니다.


재무 정보 조회하기

OpenDart API Key 발급받기

재무 정보를 조회해야하는데 어떻게 조회해야할지 막막합니다.
일단 기업에서 매 분기마다 발표하는 재무제표를 기반으로한 공시정보를 조회해야합니다.
OpenDartReader는 전자재무공시시스템에서 Api key를 이용하여 재무정보를 손쉽게 조회할 수 있게 도와줍니다.

https://opendart.fss.or.kr

여기서 회원가입을 하신다음, API Key를 발급받으면 됩니다. 개인의 경우 한도는 1일 최대 1만회로 제한되어있으며 자주 조회를 하면 조금씩 조회한도를 늘려주는 것 같긴합니다. 단, 무자비한 호출속도를 사용하면 아이피 제한이 걸릴수도 있다 하니 참고하셔야겠습니다.

재무정보 조회 준비하기

앞서 뽑았던 기본적인 정보를 가지고 조회를 진행할 겁니다.

재무정보를 조회할 조건들 작성하기

본격적인 추출에 앞서, 미리 정보 추출 시에 사용할 조건들을 생성합니다.
저는 krx_condition.py 라는 파이썬파일을 생성하고 아래와 같이 첨부했습니다. 이것도 다른분들 블로그에서 퍼왔습니다..

def get_condition1(df):
    # 유동자산
    return (df.sj_nm == '재무상태표') & ((df.account_nm == '유동자산') | (df.account_nm == 'II. 유동자산') |
                                    (df.account_nm == 'Ⅰ.유동자산') | (df.account_nm == '유동자산 합계') | (
                                            df.account_nm == 'I.유동자산') |
                                    (df.account_nm == 'I. 유동자산') | (df.account_nm == 'l. 유동자산'))  # 유동자산


def get_condition2(df):
    # 부채총계
    return (df.sj_nm == '재무상태표') & ((df.account_nm == '부채총계') | (df.account_nm == '부  채  총  계') |
                                    (df.account_nm == '부채 총계'))  # 부채총계


def get_condition3(df):
    # 자본 총계
    return (df.sj_nm == '재무상태표') & \
           ((df.account_nm == '자본총계') | (df.account_nm == '반기말자본') | (df.account_nm == '3분기말자본') |
            (df.account_nm == '분기말자본') | (df.account_nm == '1분기말자본') | (df.account_nm == '자  본  총  계') |
            (df.account_nm == '기말') | (df.account_nm == '자본 총계') | (df.account_nm == '기말자본 잔액') |
            (df.account_nm == '분기말') | (df.account_nm == '반기말') | (df.account_nm == '자 본 총 계') |
            (df.account_nm == '분기말 잔액') | (df.account_nm == '반기말 잔액'))  # 자본총계


def get_condition4(df):
    # 매출액 부분
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & ((df.account_nm == '매출액') |
                                                                (df.account_nm == '수익(매출액)') | (
                                                                        df.account_nm == '매출') | (
                                                                        df.account_nm == '영업수익') |
                                                                (df.account_nm == '수익') | (
                                                                        df.account_nm == '영업수익(매출액)') | (
                                                                        df.account_nm == 'Ⅰ.매출') |
                                                                (df.account_nm == 'I.매출액') | (
                                                                        df.account_nm == 'Ⅰ.매출액') | (
                                                                        df.account_nm == 'I. 매출액') |
                                                                (df.account_nm == '매출액(영업수익)'))


def get_condition5(df):
    # 매출총이익
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & \
           (
                   (df.account_nm == '매출총이익') | (df.account_nm == '매출 총이익') |
                   (df.account_nm == '매출총이익(손실)') | (df.account_nm == 'Ⅲ.매출총이익') |
                   (df.account_nm == 'III.매출총이익') | (df.account_nm == 'Ⅲ.매출총이익') |
                   (df.account_nm == 'III. 매출총이익(손실)') | (df.account_nm == 'III. 매출총이익') |
                   (df.account_nm == '매출총이익(영업수익)') | (df.account_nm == 'II.재료비')
           )


def get_condition6(df):
    # 영업이익, 영업손실
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & \
           ((df.account_nm == '영업이익(손실)') | (df.account_nm == '영업이익') |
            (df.account_nm == '영업손실(이익)') | (df.account_nm == '영업손익') |
            (df.account_nm == '계속영업이익(손실)') | (df.account_nm == 'Ⅳ.영업이익') |
            (df.account_nm == 'VI.영업이익(손실)') | (df.account_nm == 'V.영업이익') | (df.account_nm == 'V. 영업이익(손실)') |
            (df.account_nm == 'IV. 영업이익') | (df.account_nm == '영업이익 (손실)') | (df.account_nm == '영업손실'))


def get_condition7(df):
    # 당기 순 이익
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서') | (df.sj_nm == '현금흐름표')) & \
           ((df.account_nm == '당기순이익(손실)') | (df.account_nm == '당기순이익') |
            (df.account_nm == '분기순이익') | (df.account_nm == '분기순이익(손실)') | (df.account_nm == '반기순이익') |
            (df.account_nm == '반기순이익(손실)') | (df.account_nm == '연결분기순이익') | (df.account_nm == '연결반기순이익') |
            (df.account_nm == '연결당기순이익') | (df.account_nm == '연결분기(당기)순이익') | (df.account_nm == '연결반기(당기)순이익') |
            (df.account_nm == '연결분기순이익(손실)') | (df.account_nm == '당기순손익') | (df.account_nm == 'Ⅶ.당기순이익') |
            (df.account_nm == 'VIII.당기순이익(손실)') | (df.account_nm == 'XIII. 당기순이익(손실)') | (
                    df.account_nm == '반기연결순이익(손실)') |
            (df.account_nm == '연결당기순이익(손실)') | (df.account_nm == '당기의 순이익') | (df.account_nm == '분기기순이익(손실)') |
            (df.account_nm == '분기순손익') | (df.account_nm == '분기순손실') | (df.account_nm == '반기순손익') | (
                    df.account_nm == '분기연결순손실') |
            (df.account_nm == '분기연결순이익(손실)'))


# 현금흐름표 부분
def get_condition8(df):
    ## 영업활동 현금흐름
    return (df.sj_nm == '현금흐름표') & ((df.account_nm == '영업활동으로 인한 현금흐름') | (df.account_nm == '영업활동 현금흐름') |
                                    (df.account_nm == '영업활동현금흐름') | (df.account_nm == '영업활동으로인한 현금흐름') | (
                                            df.account_nm == '영업활동으로인한순현금흐름') |
                                    (df.account_nm == 'Ⅰ. 영업활동으로 인한 현금흐름') | (df.account_nm == 'I.영업활동현금흐름') | (
                                            df.account_nm == '영업활동으로인한현금흐름') |
                                    (df.account_nm == '영업활동으로 인한 순현금흐름') | (df.account_nm == '영업활동으로인한 순현금흐름') | (
                                            df.account_nm == '1.영업활동현금흐름') |
                                    (df.account_nm == 'Ⅰ.영업활동현금흐름') | (df.account_nm == '영업활동으로 인한 현금 흐름') | (
                                            df.account_nm == 'I. 영업활동현금흐름') |
                                    (df.account_nm == 'I. 영업활동으로 인한 현금흐름') | (df.account_nm == 'I.영업활동으로 인한 현금흐름') |
                                    (df.account_nm == '영업활동순현금흐름 합계') | (df.account_nm == 'Ⅰ. 영업활동현금흐름') |
                                    (df.account_nm == '영업으로부터 창출된 현금흐름') | (df.account_nm == 'Ⅰ. 영업활동으로 인한 순현금흐름'))


def get_condition9(df):
    # 투자활동으로인한 현금흐름
    return (df.sj_nm == '현금흐름표') & ((df.account_nm == '투자활동으로 인한 현금흐름') | (df.account_nm == '투자활동 현금흐름') |
                                    (df.account_nm == '투자활동현금흐름') | (df.account_nm == '투자활동으로인한 현금흐름') | (
                                            df.account_nm == '투자활동으로인한순현금흐름') |
                                    (df.account_nm == 'Ⅱ. 투자활동으로 인한 현금흐름') | (df.account_nm == 'II.투자활동현금흐름') | (
                                            df.account_nm == '투자활동으로인한현금흐름') |
                                    (df.account_nm == '투자활동으로 인한 순현금흐름') | (df.account_nm == '투자활동으로인한 순현금흐름') | (
                                            df.account_nm == '2.투자활동현금흐름') |
                                    (df.account_nm == 'Ⅱ.투자활동현금흐름') | (df.account_nm == 'Ⅱ. 투자활동현금흐름') | (
                                            df.account_nm == 'II. 투자활동으로 인한 현금흐름') |
                                    (df.account_nm == 'II.투자활동으로 인한 현금흐름') | (df.account_nm == 'II. 투자활동현금흐름') |
                                    (df.account_nm == '투자활동순현금흐름 합계') | (df.account_nm == '투자활동으로부터의 순현금유입(유출)') | (
                                            df.account_nm == 'Ⅱ. 투자활동으로 인한 순현금흐름'))


def get_condition10(df):
    # 자산총계
    return (df.sj_nm == '재무상태표') & ((df.account_nm == '자산총계') | (df.account_nm == '자  산  총  계') |
                                    (df.account_nm == '자산 총계') | (df.account_nm == '자본과부채총계'))  # 자산총계


def get_condition11(df):
    # 매출 원가
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & (df.account_nm == '매출원가')


def get_condition12(df):
    # 순이익
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & (df.account_nm == '법인세비용차감전순이익(손실)')


def get_condition13(df):
    # 지출비용
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & (df.account_nm == '법인세비용(혜택)')


def get_condition14(df):
    return ((df.sj_nm == '손익계산서') | (df.sj_nm == '포괄손익계산서')) & (df.account_nm == 'II.재료비')  # 008770 매출총이익 계산하기 위한 것

def get_condition15(df):
    return (df.sj_nm == '현금흐름표') & ((df.account_nm == '유형자산의 증가') | (df.account_nm == '유형자산의증가') |
                                    (df.account_nm == ' 유형자산의 취득') | (df.account_nm == ' 유형자산의취득') |
                                    (df.account_nm == '유형자산의취득') | (df.account_nm == '유형자산의 취득')
                                    )

전자 재무공시라고는 하지만, 왜 똑같은 정보에도 여러 표현을 쓰는지는 잘 모르겠습니다. 이미 많은 곳에서 쓰고있다보니 통일을 못하는 것 같기도 하고, 회사에서도 오래된 회사에서는 섣불리 명칭의 변경을 하지 못하는 것 같긴한데...
무튼 공시정보에서 해당 정보들을 뽑아내기 위한 조건으로 사용할 값들을 미리 생성해놓습니다.

변하지 않는 분류코드, 구분자 등의 상수 선언

일단 extract.py 의 생성자에 다음과 같이 추가합니다.

import time
from datetime import datetime
import numpy as np
import extract_data.krx_condition as CONDITION
from .basic_factor_data.korean_market_factor_data import KoreanMarketFactorData
import OpenDartReader
from config.api_key import OPEN_DART_KEY
import pandas as pd


class Extract:
    def __init__(self):
        self.factor_data = KoreanMarketFactorData()
        self.dart = OpenDartReader(OPEN_DART_KEY)  # config/api_key.py에서 api key의 설정이 필요함.
        self.report_code = [
            '11013',  # "1분기보고서":
            '11012',  # "반기보고서":
            '11014',  # "3분기보고서":
            '11011'  # "사업보고서"
        ]

        self.indicators = [
            '유동자산',
            '부채총계',
            '자본총계',
            '자산총계',
            '매출액',
            '매출총이익',
            '영업이익',
            '당기순이익',
            '영업활동현금흐름',
            '잉여현금흐름'
        ]

        self.financial_column_header = ["종목코드", "연도", "시가총액"] + self.indicators

데이터의 컬럼이나, 조회용으로 쓰일 값들을 미리 클래스의 변수로 선언해줍니다.

    def __find_financial_indicator(self, stock_code, year):
        current_assets = [0, 0, 0, 0]  # 유동자산
        liabilities = [0, 0, 0, 0]  # 부채총계
        equity = [0, 0, 0, 0]  # 자본총계
        total_assets = [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(year)  # 년도 변수 지정

extract.py 안에 종목코드와 연도를 전달받은 메소드를 생성하시고, 위와 같이 선언합니다. 배열의 길이가 4인 이유는 각 분기별 보고서의 데이터를 저장하기 위함입니다.

        no_report_list = ['035420', '035720', '036570', '017670', '251270', '263750', '030200', '293490',
                     '112040', '259960', '032640', '180640', '058850']  # 매출총이익 계산 못하는 회사들

2천여개가 넘어가는 종목들 중에, 중견기업 이상인 기업들 중에서도 매출총이익 항목을 계산하지 못하는 항목이 있습니다.
인터넷의 어느분께서 정리를해주셨는데 정말 감사합니다. 저도 써먹습니다.

그 다음, 데이터를 담을 변수를 선언합니다.

       data = []
       record = []

재무제표에서 정보 추출하기

조건도 작성했으니, 이제부터 본격적으로 추출작업을 진행합니다. 다시 extract.py에서 작업을 진행합니다.

에러 핸들링용 메소드

정보값을 직접 추출해낼때 에러핸들링을 해줄 메소드를 정의합니다.

    def __check_index_error(self, report, condition):
        """
        간혹가다가 리포트에서 값을 조회할 수 없는 회사들이 있음. 이럴때는 해당 컬럼값에 그냥 -1을 넣어주기로 에러 핸들링.
        :param report:
        :param condition:
        :return:
        """
        try:
            #에러가 없으면 각 재무정보 데이터에서 당기금액 부분을 추출
            return int(report.loc[condition].iloc[0]['thstrm_amount'])
        except IndexError:
            return -1
        except ValueError:
            return -1

반복문 내에서 종목별 연결재무제표 정보 추출 및 조건 선언

이후 다시 추출작업을 하는 메소드 __find_financial_indicator 로 돌아와서 반복문을 작성합니다.

 for j, report_name in enumerate(self.report_code):
            # 연결 재무제표 불러오기
            report = self.dart.finstate_all(stock_code, year, report_name, fs_div='CFS')

각 분기보고서별 구분번호를 가지고 있는 report_code 를 이용해서 enumerate를 이용하여 반복문을 실행합니다. enumerate를 사용하면 해당 배열의 요소의 인덱스와 요소값을 사용할 수 있습니다.
여기서 OpenDartReader의 finstate_all 을 이용하여 연결재무제표를 불러옵니다.
이 메소드를 이용할때에 필요한 값으로는 종목코드, 연도, 분기별보고서 구분번호, 보고서 종류의 값을 넘깁니다. 연결재무제표는 CFS 입니다.

            if report is None:  # 리포트가 없다면
                pass

            else:
                condition1 = CONDITION.get_condition1(report)
                condition2 = CONDITION.get_condition2(report)
                condition3 = CONDITION.get_condition3(report)
                condition4 = CONDITION.get_condition4(report)
                condition5 = CONDITION.get_condition5(report)
                condition6 = CONDITION.get_condition6(report)
                condition7 = CONDITION.get_condition7(report)
                condition8 = CONDITION.get_condition8(report)
                # condition9 = CONDITION.get_condition9(report)
                condition10 = CONDITION.get_condition10(report)
                condition15 = CONDITION.get_condition15(report)

반복문의 내부에서 조건문을 사용합니다. 만약 report를 조회했는데 값이 없다면, 그냥 생략하고 다음 반복문으로 넘어갑니다. 그렇지 않고 값이 존재한다면~ 부터 추출작업이 시작합니다.

앞서 작성한 보고서에서 값을 추출해내기 위한 값들을 미리 변수에 저장합니다.

유동자산, 부채총계, 자본총계

                # 유동자산
                current_assets[j] = self.__check_index_error(report, condition1)
                # 부채총계
                liabilities[j] = self.__check_index_error(report, condition2)
                # 자본총계
                equity[j] = self.__check_index_error(report, condition3)

매출액

         # 매출액 계산
                if stock_code == '003550':  # LG의 경우, 매출이 쪼개져있으므로 매출원가 + 매출총이익을 더한다.
                    revenue[j] = self.__check_index_error(report, CONDITION.get_condition11(report)) + \
                                 self.__check_index_error(report, condition5)
                else:
                    revenue[j] = self.__check_index_error(report, condition4)

매출총이익

               #매출총이익 계산
                if stock_code == '011810':  # 매출총이익 항목이 없는 회사도 있다. 이 경우, 매출액 - 매출원가로 계산.
                    grossProfit[j] = revenue[j] - self.__check_index_error(report,
                                                                           CONDITION.get_condition11(report))
                elif stock_code in no_report_list:  # 매출총이익도 없고 이를 계산할 매출원가도 없다.
                    grossProfit[j] = 1
                elif stock_code == '008770':
                    grossProfit[j] = revenue[j] - self.__check_index_error(report,
                                                                           CONDITION.get_condition14(report))
                else:
                    grossProfit[j] = self.__check_index_error(report, condition5)

영업이익, 당기순이익

                #영업이익
                income[j] = self.__check_index_error(report, condition6)

                #당기순이익
                if stock_code == '008600':
                    net_income[j] = self.__check_index_error(report, CONDITION.get_condition12(
                        report)) - self.__check_index_error(report, CONDITION.get_condition13(report))
                else:
                    net_income[j] = self.__check_index_error(report, condition7)

영업활동현금흐름, 투자활동현금흐름, 유형자산의 증가, 자산총계

일부 현금흐름의 경우, 각 분기별 현금흐름을 구하기 위해 현재값에서 이전 분기의 값을 빼주는 식으로 계산합니다.
특히 많은 부분에서 4분기의 경우, 1~3분기의 합을 뺴는 것으로 구해줍니다.
투자활동현금흐름은 일단 코드는 구현해놓았으나 (일단 퍼온거라...ㅋㅋ;) 지금 당장은 사용할 것 같지 않아 코드상에서는 코멘트처리를 하였습니다.

                #영업활동 현금흐름
                cfo[j] = self.__check_index_error(report, condition8)
                #투자활동 현금흐름
                # cfi[j] = self.__check_index_error(report, condition9)
                #유형자산의 증가
                capex[j] = self.__check_index_error(report, condition15)
                #자산총계
                total_assets[j] = self.__check_index_error(report, condition10)

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

                elif report_name == '11012':  # 2분기
                    date_month = '06'
                    date_day = 30
                    cfo[j] = cfo[j] - cfo[j - 1]  # 현금흐름은 2분기부터 시작
                    # cfi[j] = cfi[j] - cfi[j - 1]  # 현금흐름은 2분기부터 시작
                    capex[j] = capex[j] - capex[j-1]

                elif report_name == '11014':  # 3분기
                    date_month = '09'
                    date_day = 30
                    cfo[j] = cfo[j] - (cfo[j - 1] + cfo[j - 2])
                    # cfi[j] = cfi[j] - (cfi[j - 1] + cfi[j - 2])
                    capex[j] = capex[j] - (capex[j-1] + capex[j-2])

                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])
                    cfo[j] = cfo[j] - (cfo[j - 1] + cfo[j - 2] + cfo[j - 3])
                    # cfi[j] = cfi[j] - (cfi[j - 1] + cfi[j - 2] + cfo[j - 3])
                    capex[j] = capex[j] - (capex[j-1] + capex[j-2] + capex[j-3])

잉여현금흐름

영업활동현금흐름에서, CAPEX (유형자산의 증가 항목) 을 빼어 계산합니다.

                #잉여현금흐름
                fcf[j] = (cfo[j] - capex[j])

날짜 계산 후 분기별 마지막 영업일의 시가총액 계산

                # 날짜 계산
                date_day = self.__check_weekend(date_year, date_month, date_day)
                date = date_year + date_month + str(date_day).zfill(2)
                date_string = date_year + '-' + date_month + '-' + str(date_day).zfill(2)

                #각 분기별 마지막 영업일의 시가총액
                market_cap_df = self.factor_data.stock.get_market_cap_by_date(date, date, stock_code)

                try:
                    market_cap[j] = market_cap_df.loc[date_string]["시가총액"]
                except KeyError:
                    print(market_cap_df)
                    market_cap[j] = 0

각 분기별 시가총액을 넣습니다. 단 얻어오지 못할 경우에는 0을 넣습니다. (해당 종목의 상장 이전 날짜인 경우 데이터가 없음)

날짜 계산

날짜를 계산하는 메소드는 아래와 같이 구성해보았습니다.

    def __check_weekend(self, date_year, date_month, date_day):
        """
        주말인지 아닌지를 판단.
        :param date_year:
        :param date_month:
        :param date_day:
        :return:
        """
        date = date_year + date_month + str(date_day)
        date_formated = datetime.strptime(date, "%Y%m%d")  # datetime format 으로 변환

        if date_formated.weekday() == 5:
            if date_month == '12':
                date_day -= 2  # 연말의 경우 2일을 뺀다.
            else:
                date_day -= 1  # 토요일일 경우 1일을 뺀다.
        elif date_formated.weekday() == 6:
            if date_month == '12':
                date_day -= 3  # 연말의 경우 3일을 뺀다.
            else:
                date_day -= 2  # 일요일일 경우 2일을 뺀다.
        elif date_formated.weekday() == 4 and date_month == '12':
            date_day -= 1  # 연말인데 금요일이면 1일을 뺀다.

        # 추석에 대한 처리
        if date_month == '09' and date_year == '2020':
            date_day -= 1
        elif date_month == '09' and date_year == '2023':
            date_day -= 3

        return date_day

데이터 합치기

                record = [stock_code, date_string, market_cap[j], current_assets[j], liabilities[j], equity[j],
                          total_assets[j],
                          revenue[j], grossProfit[j], income[j], net_income[j], cfo[j],
                          fcf[j]]

조건문 else의 제일 마지막에서, 하나의 배열로 만들어줍니다.
순서대로 아래와 같습니다.

  • 종목코드
  • 날짜
  • 시가총액
  • 유동자산
  • 부채총계
  • 자본총계
  • 매출액
  • 매출총이익
  • 영업이익
  • 당기순이익
  • 영업활동현금흐름
  • 잉여현금흐름

각 분기별 데이터의 수집이 모두 끝나면, 하나의 Dataframe으로 만들어줍니다.

이후 반복문의 제일 마지막에서, Dataframe에 차곡차곡 저장해줍니다.

            data.append(record)

그후 모든 반복문이 종료되면 dataframe을 return합니다.

    return data

메소드의 구현순서대로 작성을 하여서 알기 쉬우실 것이라 생각합니다..

이렇게 하나의 종목에 대해, 최근 3개년에 대해 각각의 분기별 데이터를 취합합니다.

extract.py

    def extract_finance_data(self, finance_years, df):
        pd.set_option('display.max_columns', None) ##디버깅용 옵션
        pd.options.display.float_format = '{:.2f}'.format

        data = []

        count = 1
        for row in df.itertuples():
            print(f"extracting {count}/{len(df)} {row[2]}...") #check progress
            count += 1
            for year in finance_years:
                dt = self.__find_financial_indicator(row[1], year)
                data += dt

            time.sleep(0.3) #너무 빠르게 호출하면 IP차단 확률이 있으므로 속도 제한.

finance_years 는 3개연도 정수 형태의 배열값입니다. 이것은 반복문에서도 쓰입니다.
df 는 앞선 포스팅에서 기본적으로 조회했던 데이터들입니다.

여기서 발생하는 문제... 너무 많고 오래 걸린다.

그런데 이렇게 되면 문제가 발생합니다. 모든 주식 종목수는 대략 2천3~400개인데, 이것들 하나하나에 직전 3개년의 4개분기의 데이터를 모두 조회해야합니다.

2300? x 3 x 4 ....

그러나 OpenDart의 개인용 API Key의 일일호출제한은 1만건입니다. 데이터베이스를 운용한다면, 데이터를 계속해서 넣는 작업을 며칠간 진행해서 넣을 순 있겠지만, 저는 운용할 생각이 없습니다.

이것을 해결하기 위한 방법과 함께 분기별 팩터데이터 계산은 다음 포스트에서 서술합니다.

ref

반응형

댓글