본문 바로가기
AyoProject/Ayotera-Trade

[AT] 24. 우량주 종목 자동 예측 및 선정 (2)

by 청양호박이 2020. 12. 24.

이번에는 지난 시간에 이어서... 우량주 종목을 자동으로 예측하는 시스템은 만들어 보겠습니다. 다시한번 강조하지만 이런 도구들도 결국 내가 투자를 하기 위한 기반 자료에 불과하다는 것은 유념해야 합니다. 

 

지난 시간을 잠시 정리해보면, 전자공시시스템에서 제공하는 OPEN DART를 이용해서 [삼성전자]라는 종목에 대한 재무제표 정보들을 가져와서 확인을 했었습니다. 이를 위해서 API를 통해 Zip File (Binary)를 Get으로 호출해서 가져오고, 그 정보를 가지고 Json으로 제공하는 API를 Get해서 필요한 정보를 추출해 보았습니다. 

 

그럼 지금까지의 정보를 기준으로 모든 종목에 대한 고유번호를 저장하고, 각 종목별로 재무제표 정보를 저장하는 로직을 구현해 보겠습니다.

 

  • 우량주 종목 선정을 위한 기반 데이터 수집 방안 (전자공시시스템 활용) - 1개 종목기준
  • 정리된 데이터 수집 방안에 대해서 코스피, 코스닥 전체 종목에 대한 데이터를 DBMS에 적재
  • 우량주 종목 선정 로직을 적용하여 대상 종목 추출
  • 추출된 우량주 종목에 대해서 저평가 종목 추출
  • 추출된 종목에 대해서 실험실 진행

 

그럼 차근차근 진행해 보겠습니다.

 

 

1. 고유번호 저장


재무제표를 가져오는 API를 활용하기 위해서는, 반드시 해당 종목에 대한 고유번호가 필요합니다. 따라서 받아온 고유정보 xml에 대해서 DB에 저장하여 활용할 수 있도록 개발이 되어야 합니다. 실질적으로 구현하는 방법은 간단합니다. xml파일에 대해서 일부 정보를 추출하는 부분은 구현하였기 때문에... 전체정보를 Python에서 제공하는 DataFrame으로 구성하여 한번에 넣어주면 됩니다.

 

이 부분은 앞에 MACD편에서도 했었기 때문에... 크게 어렵지 않을 것 같습니다. 일단 진행되는 부분을 기술하다가... 필요하다면 기존의 글을 몇개 넣어드릴 수 있도록 하겠습니다.

 

2020/02/24 - [AyoProject/Ayotera-Trade] - [AT] 08. mariadb(mysql) connection with python (1)

2020/02/26 - [AyoProject/Ayotera-Trade] - [AT] 09. mariadb(mysql) connection with python (2)

 

이렇게 2가지를 우선 보고 오신다면, Python의 DataFrame을 바로 mariadb에 변환하여 저장하는 방법을 이해할 수 있을 것 입니다. to_sql이라는 마법으로~~ 그럼 소스코드를 보기로 하지요!!

 

[소스 코드]

import requests
import pandas as pd
import pymysql as mariadb
from sqlalchemy import create_engine
from datetime import datetime
from io import BytesIO
from zipfile import ZipFile
from xml.etree.ElementTree import parse

mariadb.install_as_MySQLdb()

# Zip FILE (binary)
url = 'https://opendart.fss.or.kr/api/corpCode.xml'
params = {'crtfc_key':'<API KEY>'}

res = requests.get(url,params)

print("corpCode API 호출 결과 : ", res.status_code)
print("corpCode API Header 정보 : ", res.headers['content-type'])
print("corpCode API Encoding 방식 : ", res.encoding)

with ZipFile(BytesIO(res.content)) as zipfile:
    zipfile.extractall('c:\\corpCode')

# xml 호출하여 읽어오기
xmlTree = parse('c:\\corpCode\corpCode.xml')

root = xmlTree.getroot()
list = root.findall('list')

print("corpCode 갯 수 : ", len(list))

# xml 정보 list 화
corp_name = [x.findtext('corp_name') for x in list]
corp_code = [x.findtext('corp_code') for x in list]
stock_code = [x.findtext('stock_code') for x in list]
modify_date = [x.findtext('modify_date') for x in list]

# DataFrame으로 제작
df = pd.DataFrame()
df['corp_name'] = corp_name
df['corp_code'] = corp_code
df['stock_code'] = stock_code
df['modify_date'] = modify_date

print(df)

# DB에 저장
engine = create_engine('mysql://root:' + '<my password>' + '@localhost/<my project name>', encoding='utf-8')
df.to_sql('corp_code', engine, if_exists='replace', index=True, index_label=None, chunksize=500)

꽤 길어보이지만... 사실은 알고보면 이전에 구현했었던, Zip File (Binary)형태의 Get방식으로 제공하는 API에 대한 구현부분이 주를 이루고... 실제로 DataFrame으로 변환하여 DB에 저장하는 부분은 다소 짧습니다.

 

제가 사용한 방식은 xml로 받은 데이터를 각 항목별로 list화 시키고... 그 list를 pandas.DataFrame으로 변환하였습니다. 그리고 sqlalchemy를 활용해서 DataFrame to Sql로 바로 DB화 하였습니다. 이를 위해서 아래의 3가지 모듈을 import 해주어야 합니다.

import pandas as pd
import pymysql as mariadb
from sqlalchemy import create_engine

그 다음에는 xml으로 받아온 정보들을 각 항목별로 list화 해줍니다. 이때는 Python의 표현식을 이용하여 간단하게 list화가 가능하게 됩니다. 그리고 DataFrame객체를 1개 만들어서 생성한 4개의 list를 추가해 주면 됩니다. 그렇게 하면 그 결과는...

corpCode 갯 수 :  83201


               corp_name corp_code stock_code modify_date
0                     다코  00434003               20170630
1                   일산약품  00434456               20170630
2                  굿앤엘에스  00430964               20170630
3                   한라판지  00432403               20170630
4      크레디피아제이십오차유동화전문회사  00388953               20170630
...                  ...       ...        ...         ...
83196              비앤비레저  01503577               20200928
83197               파이런텍  01504293               20201007
83198          에이치디가양제일차  01504682               20201007
83199          에이치디가양제이차  01504691               20201007
83200          에이치디가양제삼차  01504707               20201007

총 83,201개의 데이터를 정상적으로 가져옴을 확인했습니다.

 

이제 마지막 2줄이 DB에 저장하는 코드입니다. 별로 길지 않기 때문에 내용은 생략하고... 바로 결과를 확인해 보겠습니다. 

 

[DB저장 결과]

Seq가 0부터 시작하기 때문에 정상적으로 총 83,201개가 저장이 되었습니다.

 

 

2. 종목 별 재무제표 정보 저장


우선 종목 별 재무제표를 가져오기 전에... 한가지 쿼리를 실행해 보겠습니다. 해당 쿼리는... 키움증권의 API를 통해서 받아온 종목리스트와 OPEN DART를 통해서 받아온 전자공시 고유번호를 매핑하여 실제로 존재하는 주식종목에 대해서 리스트를 받아오는 쿼리입니다.

SELECT 
	A.stocks_id,
	A.stocks_type,
	A.stocks_name,
	B.stock_code,
	B.corp_name,
	B.corp_code
FROM stocks_info AS A
LEFT JOIN corp_code AS B
ON A.stocks_id = B.stock_code
AND A.stocks_name = B.corp_name;

현재 stocks_info에는 증권사에서 제공하는 API를 통해서 받아온 종목리스트가 저장되어 있고, corp_code에는 OPEN DART로 부터 받아온 정보가 저장되어 있습니다. 이 쿼리의 결과로 상호간의 데이터 정합성 및 원하는 데이터의 집합을 얻을 수 있습니다.

정확하게 일치하는 것을 확인 할 수 있습니다. 그렇다면 해당 데이터를 python코드를 통해서 가져와 보도록 하겠습니다. 역시나 어려울건 없죠??

 

[소스 코드 및 결과]

import requests
import pandas as pd
import pymysql as mariadb
from datetime import datetime

mariadb_conn = mariadb.connect(host='localhost', port=3306, user='root', password='@tedd40@', db='at_project')
mariadb_cur = mariadb_conn.cursor()

# 전체 종목 데이터 가져오기
sql_dml = '''SELECT 
                 A.stocks_id,
                 A.stocks_type,
                 A.stocks_name,
                 B.stock_code,
                 B.corp_name,
                 B.corp_code
             FROM stocks_info AS A
             LEFT JOIN corp_code AS B
             ON A.stocks_id = B.stock_code
             AND A.stocks_name = B.corp_name'''
mariadb_cur.execute(sql_dml)
sList = mariadb_cur.fetchall()
print("총 데이터 수 : ", len(sList))

for i in range(5):
    print(sList[i])
    
=====================================================================================
총 데이터 수 :  3043
('950130', '10', '엑세스바이오', '950130', '엑세스바이오', '00956028')
('152550', '0', '한국ANKOR유전', '152550', '한국ANKOR유전', '00907013')
('900070', '10', '글로벌에스엠', '900070', '글로벌에스엠', '00783246')
('900120', '10', '씨케이에이치', '900120', '씨케이에이치', '00800084')
('096300', '0', '베트남개발1', '096300', '베트남개발1', '00626710')

Python코드로 아주 정확하게 가져옴을 확인할 수 있습니다. 이제... 각 종목에 대해서 재무제표 정보를 저장하는 로직을 구현해 보겠습니다. 나중에 사용하려면 일단 저장이 우선이겠죠??

 

  • 저장을 위한 db테이블 설계 및 생성
  • 전체 항목에 대해서 유효한 정보를 추출하여 생성한 db에 저장

 

자 그렇다면 DB를 설계해 볼까요?? 우선은 단편적으로 영업이익, 영업활동 현금흐름에 중점을 두고 진행을 할 마음을 가지고 있었기 때문에 해당 항목을 중심으로 설계를 해 보겠습니다. 또한, 모든 종목에 각 데이터를 한 테이블에 저장할 것이기 때문에 아래와 같이 구성이 됩니다.

 

[Table 생성]

해당 테이블에는 종목코드, 년도, 분기, 항목명, 값 이렇게 총 5개의 필드로 구성합니다.

CREATE TABLE fin_stat_info(
	idx int(100) NOT NULL AUTO_INCREMENT PRIMARY KEY,
	stocks_id VARCHAR(10),
	bsns_year VARCHAR(4),
	bsns_quarter VARCHAR(1),
	account_nm VARCHAR(50),
	thstrm_amount VARCHAR(30)
);

이제 Table도 생성이 되었으나, 각 종목에 대해서 재무데이터를 가져와 보도록 하겠습니다. 여기서 고려할 점은 전체 종목의 개수는 3,043개 이고... 일년에 총 4개의 분기가 존재합니다. 따라서 1년치 전체 데이터를 가져오기위해서 OPEN DART에 호출하는 API건수는!!!

 

    - 3,043 x 4 = 12,172 건

 

따라서, 1일 허용요청건인 10,000건을 초과 하게 됩니다. 결국 초기에 데이터를 적재하기 위해서는 분기별로 따로따로 데이터를 요청해야 합니다. 해당 로직을 적용한 코드는 아래와 같습니다.

 

[소스 코드]

# 전체 종목에 대한 정보 가지고 오기 (년도별로)
this_year = '2015'
quarter_info = 0
reprt_code = ['11013', '11012', '11014', '11011']

sql_dml = '''
    INSERT INTO fin_stat_info(stocks_id, bsns_year, bsns_quarter, account_nm, thstrm_amount)
    VALUES (%s, %s, %s, %s, %s)
'''

for i in sList:
    url = 'https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json'
    params = {'crtfc_key': 'bfe9f4ffc4c74b32a94efed354cebd7b2e389caa',
              'corp_code': i[5], 'bsns_year': this_year, 'reprt_code': reprt_code[quarter_info], 'fs_div': 'CFS'}

    res = requests.get(url, params)

    if res.json()['status'] == '013':
        print(i, this_year, quarter_info+1, "데이터 없음")
    elif res.json()['status'] == '000':
        print(i, this_year, str(quarter_info+1), "데이터 존재 DB에 저장시작")
        for k in range(len(res.json()['list'])):
            if res.json()['list'][k]['account_nm'] == '영업이익' or res.json()['list'][k]['account_nm'] == '영업이익(손실)':
                sql_data = (i[0], this_year, str(quarter_info+1), '영업이익 3개월', res.json()['list'][k]['thstrm_amount'])
                try:
                    mariadb_cur.execute(sql_dml, sql_data)
                except mariadb.Error as error:
                    print(error)
                sql_data = (i[0], this_year, str(quarter_info+1), '영업이익 누적', res.json()['list'][k]['thstrm_add_amount'])
                try:
                    mariadb_cur.execute(sql_dml, sql_data)
                except mariadb.Error as error:
                    print(error)
            if res.json()['list'][k]['account_nm'] == '영업활동 현금흐름':
                sql_data = (i[0], this_year, str(quarter_info+1), '영업이익 현금흐름', res.json()['list'][k]['thstrm_amount'])
                try:
                    mariadb_cur.execute(sql_dml, sql_data)
                except mariadb.Error as error:
                    print(error)
mariadb_conn.commit()

print("작업종료!!")

각 년도, 분기를 수동으로 지정하게 하고... 전체 종목에 대해서 그 년도, 분기에 해당하는 API를 요청하는 방식으로 진행합니다. API를 호출해서 가져오는 방식은 기존에 작성된 내용으로 갈음하도록 하겠습니다. 그럼 2015년 1분기에 대해서 실행을 해보면...

 

아마 2015년 1분기 데이터를 지워진 경우가 많기 때문에, 4분기 데이터로 진행해 보겠습니다. 대신 호출한 API의 개수차이를 비교해 보겠습니다. 

 

[2015년 4분기 프로그램 실행 전]

[2015년 4분기 프로그램 실행 후]

어떤가요?? 정확히 2015년 4분기 데이터에 대해서 3,043건 API를 호출했습니다. 그렇다면 DB에 저장된 데이터를 조회해 보겠습니다. 

 

이렇게 모든 구현이 마무리가 되었습니다. 그럼 다음으로 우량주를 선정하는 로직을 적용하여 대상 종목을 추려내 보도록 하겠습니다.

 

- Ayotera Lab -

댓글