본문 바로가기
AyoProject/Ayotera-Trade

[AT] 31. 우량주 종목 자동 예측 및 선정 (4-5)

by 청양호박이 2021. 3. 10.

오랫만에 업데이트를 진행합니다. 이번에는 간단하게 저평가 종목 추출의 마지막 항목인 통합 이력기록을 위한 이력저장로직을 구현해 보겠습니다. 현재까지 진행된 내용을 history상으로 회상하기 위해서 목차를 가져와 보았습니다.

 

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

 

우량주 종목에 대해서 저평가 종목을 추출하는 내용을 완성하기 위해서는 아래의 추가 단계가 필요하고, 앞으로 당분간은 해당 구성을 구현해 보도록 하겠습니다. 

 

  1. Dart OpenData로 부터 전체 종목의 재무제표정보 추출 (완료)
  2. 영업이익률 / 현금흐름표를 기준으로 우량주 종목을 선정 (완료)
  3. 선정된 우량주 종목을 Database에 저장 (완료)
  4. 키움증권 API로 부터 3번에 저장된 우량주 종목에 대한 지표를 추출 (완료)
  5. 추출된 종목별 지표를 Database에 저장 (완료)
  6. 통합 이력기록을 위해서 작업 이력을 이력테이블 생성 및 저장
  7. 저평가 종목을 포함한 최종 종목을 선정 및 화면에 렌더링 (완료)

 

이번에는 구성된 프로그램에 대해서 아무래도 일배치로 진행될 것이기 때문에, 해당 이력을 저장하는 로직을 구현하는 것 입니다. 마지막 항목에 전체 동작로직에는 크게 영향이 없기 때문에 가볍게 보고가면 될 것 같습니다.

 

기업은 어느 기간에는 엄청나가 잘 나가다가, 또 어떤 기간에는 힘든 시간을 보내곤 합니다. 이 힘든시기를 잘 이겨낸 기업은 다시 날개를 달고 날아가게 되고... 그렇지 않은 기업은 결국 사업을 정리하는 수순을 밟게 됩니다. 이렇게 정리되는 기업이 분기별로 분기말에 없어지는 것도 아니고 정해진 일정을 알 수 없기 때문에... DART(전자공시시스템)에서 제공하는 Corp 정보나, 증권사 API에서 제공하는 상장기업의 종목 정보를 매일 업데이트를 해주어야 합니다.

 

또한, 이러한 정보를 가지고 주기적으로 가장 최근 업데이트된 재무정보를 가져오고 우량주 종목을 판단해야 합니다. DART를 보고 있으면 특정기간에 분기자료가 업데이트 되지만 수정되서 다시 올라오는 경우도 있고 상황은 다양할 수 있습니다. 또한, 이런 정보들은 우량주 판단기준에 영향을 미치기 때문에... 어떤 종목은 기존에 우량주에 속해있다가 대상에서 제외될 수도 있습니다. 

 

결국, 우리가 일배치로 주기적으로 돌려야 하는 로직은 아래와 같습니다. 

그렇다면, 하고자 하는 이력기록은 각 단계별로 총 4가지의 이력을 저장하면 되는 것 입니다. 그럼 차근차근 진행해 보겠습니다. 

 

 

1. 이력저장 Table 생성


이력저장을 위해서는 아래의 5개의 정보를 저장하기로 하겠습니다. 

 

  • 프로그램명 : 해당 로직을 구동하는 프로그램의 이름
  • 프로그램 작업 : 해당 프로그램이 실제로 구현하는 로직의 기술
  • 작업 수 : 해당 프로그램을 통해서 도출된 결과물의 수
  • 일자 : 프로그램이 동작한 일자 / 시간
  • 비고 : 추가적인 정보
DROP TABLE batch_record_info;

CREATE TABLE batch_record_info(
	prog_name VARCHAR(50),
	prog_work VARCHAR(100),
	prog_work_cnt INT,
	ins_date DATETIME,
	remark TINYTEXT
);

 

 

2. 이력저장 공통코드 제작


이력저장은 정해진 단일DB에 저장되는 값만 다르기 때문에, 공통코드로 제작하고 필요한 프로그램에서 재활용하기로 하겠습니다. 

 

이를 위해서는 해당 로직을 class로 생성해 줍니다. 

 

[BatchRecordInsert.py]

import pymysql as mariadb

class BatchRecordInsert:
    def __init__(self):
        self.mariadb_conn = mariadb.connect(host='localhost', port=3306, user='root', password='@tedd40@', db='at_project')
        self.mariadb_cur = self.mariadb_conn.cursor()
        self.sql_dml = '''
            INSERT INTO batch_record_info(prog_name, prog_work, prog_work_cnt, ins_date, remark)
            VALUES (%s, %s, %s, NOW(), %s)
        '''

    def dbInsert(self, sql_data):
        try:
            self.mariadb_cur.execute(self.sql_dml, sql_data)
        except mariadb.Error as error:
            print(error)
        self.mariadb_conn.commit()

        self.mariadb_cur.close()
        self.mariadb_conn.close()

간단하게... 클래스 instance를 생성하면, 생성자로 DB연결을 수행하고, sql_dml까지 세팅해 놓습니다. 그리고 각 프로그램에서 이력저장을 위해서는 dbInsert( )메서드를 호출하고, parameter에 sql_data를 넣어서 수행하는 방법을 사용하였습니다. 

 

이제 각 프로그램 단위에서는, 이 클래스를 호출해서 이력을 저장하면 됩니다.

 

[호출방법]

import BatchRecordInsert as BRI

# DB에 우량주 선정이력 저장
bri = BRI.BatchRecordInsert()
sql_data = ('OpenDartSuperiorStocks', 'Dart의 재무재표분석을 통한 우량주 선정', len(superior_stocks), bsns_year+'년'+bsns_quarter+'분기')
bri.dbInsert(sql_data)

예를 들어보면, 다음과 같습니다. 간단하죠?? 향후 이력저장 Table이 바뀔 경우에는 해당 class를 우선적으로 수정하고, 파라미터까지 바뀔경우에만 호출하는 각 프로그램단위에서 parameter정보만 수정하면 됩니다.

 



 

 

3. 각 프로그램 별 이력저장 세팅


[DART corp Info]

# 프로그램 명 : OpenDartCorpCode
# 프로그램 작업 : Dart에 등록된 회사코드 저장

# DB에 batch 이력 저장
bri = BRI.BatchRecordInsert()
sql_data = ('OpenDartCorpCode', 'Dart에 등록된 회사코드 저장', len(list), '')
bri.dbInsert(sql_data)

 

[OPEN DART 재무제표 정보]

# 프로그램 명 : OpenDartFnlttSinglAcntAll
# 프로그램 작업 : Dart에 등록된 주식종목의 재무재표정보 저장

# 각 종복별 정보를 DB에 저장하는 로직은 수정 필요
try:
    a = mariadb_cur.execute(sql_dml, sql_data)
    if a == 1: try_count += 1
except mariadb.Error as error:
    print(error)

# DB에 batch 이력 저장
bri = BRI.BatchRecordInsert()
sql_data = ('OpenDartFnlttSinglAcntAll', 'Dart에 등록된 주식종목의 재무재표정보 저장', try_count, this_year+'년'+str(quarter_info+1)+'분기')
bri.dbInsert(sql_data)

해당 프로그램에서는 한가지 수정이 필요한 부분이 있습니다. 실제로 OPEN DART를 통해서 가져온 정보를 DB에 저장하고자 할 때, 값이 있는 경우는 업데이트를 하지 않는 Query문이 들어가 있습니다. 따라서 실질적인 DB저장에 대한 수치를 이력에 저장하기 위해서는 DB에 Insert하는 부분의 결과를 받아서 return이 1일 경우 count를 +1해야 실질적인 이력 결과가 들어가게 됩니다.

 

a = mariadb_cur.execute(sql_dml, sql_data)
if a == 1: try_count += 1

 

execute의 결과로 insert를 실질적으로 수행을 하면 return 1, 중복된 값이 있어서 실질적으로 insert를 안하면 return 0이 됩니다. 따라서, 실질적인 결과에 따라서 return 1일 경우만 count를 +1 하게 되는 것 입니다. 그렇다면, 정상적으로 동작하는지는 다음 프로그램에서 확인해 보겠습니다. 

 

[우량주선정로직]

# 프로그램 명 : OpenDartSuperiorStocks
# 프로그램 작업 : Dart의 재무재표분석을 통한 우량주 선정

# DB에 우량주 선정이력 저장
bri = BRI.BatchRecordInsert()
sql_data = ('OpenDartSuperiorStocks', 'Dart의 재무재표분석을 통한 우량주 선정', len(superior_stocks), bsns_year+'년'+bsns_quarter+'분기')
bri.dbInsert(sql_data)

# DB에 신규로 추가된 우량주 종목 저장 이력 저장
bri = BRI.BatchRecordInsert()
sql_data = ('OpenDartSuperiorStocksInsert', 'Dart의 재무재표분석을 통한 우량주 종목 저장', table_insert_cnt, bsns_year+'년'+bsns_quarter+'분기')
bri.dbInsert(sql_data)

해당 프로그램에서는 2개의 이력을 저장합니다. 로직에 의해서 선정된 우량주 개수를 저장하는 이력과 선정된 종목이 기존에 없을 경우, 업데이트할 때 이력을 저장하는 로직이 그것 입니다. 

 

이 중에서 신규로 추가된 우량주 종목을 DB에 저장한 이력을 저장하는 로직은 바로 전에... 신규 재무제표 정보가 있을 경우 업데이트한 결과를 저장하는 로직과 동일합니다. 

    try:
        a = mariadb_cur.execute(sql_dml, sql_data)
        if a == 1: table_insert_cnt += 1
    except mariadb.Error as error:
        print(error)

execute를 요청하고 return 1일 경우만 insert_cnt를 +1해줍니다. 그럼 일단 실험을 통해서 확인해 보겠습니다. 우선 현재 상태의 2020년 1분기 선정된 우량주 개수는 총 133개 입니다.

이 상태에서, OpenDartSuperiorStocks 프로그램을 돌리면 다음과 같이 이력이 DB에 저장됩니다. 

이제는 아래 query를 통해서 선정된 우량주 항목중에 한개를 삭제해 줍니다.

DELETE FROM quarter_superior_stocks WHERE stocks_id = '009410' AND bsns_year = '2020' AND bsns_quarter = '1';

다시 2020년 1분기 선정된 우량주 개수를 확인해 보면... 132개 입니다.

이 상태에서, OpenDartSuperiorStocks 프로그램을 돌리면 다음과 같이 이력이 DB에 저장됩니다. 

총 우량주 개수는 133개로 선정되었고, 기존에 삭제된 항목이 있기 때문에 Insert 이력에 1로 표기되었습니다. 정상적으로 동작하네요. 

 

이렇게 구현이 완료되었습니다. 다음부터는 실험실을 돌려보도록 하겠습니다.

 

- Ayotera Lab -

댓글