< 2. 실 거래 후 보완된 ETF 변동성 돌파 전략 - 데이터 수집>


1. 개요


이번 포스트에선 데이터 수집에 대해 설명하겠습니다. 먼저 데이터를 수집할 ETF 종목은 아래와 같습니다. 거래량 상관없이 대부분의 ETF 종목을 선정했습니다. 종류가 겹치는 ETF 종목만 배제했습니다. 예로 Kodex 200과 Tiger 200 종목이 있다면 Tiger 200 종목을 제외했습니다. 



위 종목들을 키움증권 api를 이용해 2007년 1월 1일부터 일봉 데이터를 수집했습니다. 이에 대한 코드 설명은 이전에 많이 설명했으니 생략하고 아래에 코드를 첨부하겠습니다. 

수집 외에 별도로 해야할 게 있습니다. 이전 포스트에서 언급했던 거래량 문제를 다뤄야 합니다. 백테스팅할 때 모든 종목들을 대상으로 하는 게 아니라 당일날 거래량이 꽤 있을 것 같다고 예측되는 종목들을 대상으로만 백테스팅을 해야 합니다. 따라서 전날 거래 대금 또는 이전 3일 동안 평균 대금이 1억 이상인 종목들을 대상으로만 백테스팅할 예정입니다. 아래는 이에 대한 전체 코드입니다. 이전 제 포스트를 봤다면 쉽게 코드를 해석할 수 있다고 생각해 설명은 하지 않겠습니다.


Kiwoom.py

from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
import time
import pandas as pd
from datetime import date, timedelta

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__()

        self.day = (date.today() - timedelta(1)).strftime('%Y%m%d')
        print(self.day)
        self.stock_info = pd.DataFrame(columns=("Open","High","Low","Close","Volume"))


        self._create_kiwoom_instance()
        self._set_signal_slots()
        self._comm_connect()

# COM을 사용하기 위한 메서드
    def _create_kiwoom_instance(self):
        self.setControl("KHOPENAPI.KHOpenAPICtrl.1")

    def _set_signal_slots(self):
        # 로그인할 시 OnEventConnect 이벤트 발생
        self.OnEventConnect.connect(self._event_connect)
        # tr후 이벤트 발생
        self.OnReceiveTrData.connect(self._receive_tr_data)

    # 로그인 메서드, 로그인 과정에서 프로그램이 진행되면 안 되기 때문에
    # 이벤트 루프 생성
    def _comm_connect(self):
        self.dynamicCall("CommConnect()")
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

    # 로그인 성공 여부 메서드
    def _event_connect(self, err_code):
        if err_code == 0:
            print("connected")
        else:
            print("disconnected")
        self.login_event_loop.exit()

    # tr을 서버에 전송한다
    def comm_rq_data(self, rqname, trcode, next, screen_no):
        self.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, next, screen_no)
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    # 서버한테 받은 데이터를 반환한다.
    def _comm_get_data(self, code, real_type, field_name, index, item_name):
        ret = self.dynamicCall("CommGetData(QString, QString, QString, int, QString)", code,
                               real_type, field_name, index, item_name)
        return ret.strip()

    # 서버한테 받은 데이터의 갯수를 반환한다.
    def _get_repeat_cnt(self, trcode, rqname):
        ret = self.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
        return ret

    # 시장 전체 종목 코드를 리턴한다. ( 0: 장내, 8: ETF, 10:코스닥 )
    def get_code_list_by_market(self, market):
        ret = self.dynamicCall("GetCodeListByMarket(QString)", market)
        return ret


    # 종목 이름 반환. 반환 값이 ""이면 code에 해당하는 종목 없음
    def is_stock(self, code):
        ret = self.dynamicCall("GetMasterCodeName(QString)", [code])
        return ret

    # tr 입력값을 서버 통신 전에 입력
    # ex. SetInputValue("종목코드","000660")
    def set_input_value(self, id, value):
        self.dynamicCall("SetInputValue(QString,QString)", id, value)

    def _receive_tr_data(self, screen_no, rqname, trcode, record_name, next, unused1, unused2, unused3, unused4):
        if next == '2':
            self.remained_data = True
        else:
            self.remained_data = False

        if rqname == "opt10081_req":
            self._opt10081(rqname, trcode)

        try:
            self.tr_event_loop.exit()
        except AttributeError:
            pass


    # 종목의 일자, 시가, 종가 데이터를 요청
    def req_stock_daily_value(self, code):
        date= '20070101'
        self.stock_info = pd.DataFrame(columns=("Open", "High", "Low", "Close","Volume"))

        print("종목 일봉 데이터 요청")
        self.set_input_value("종목코드", code)
        self.set_input_value("기준일자", self.day)
        self.set_input_value("수정주가구분", "1")
        self.comm_rq_data("opt10081_req", "opt10081", 0, "2000")
        temp_data = self.stock_info[self.stock_info.index.values <= date]
        if temp_data.empty is False:
            self.stock_info = self.stock_info[self.stock_info.index.values>date]
            self.remained_data = False


        while self.remained_data:
            print("종목 일봉 데이터 추가 요청")
            time.sleep(0.2)
            self.set_input_value("종목코드", code)
            self.set_input_value("기준일자", self.day)
            self.set_input_value("수정주가구분", "1")
            self.comm_rq_data("opt10081_req", "opt10081", 2, "2000")

            temp_data = self.stock_info[self.stock_info.index.values <= date]
            if temp_data.empty is False:
                self.stock_info = self.stock_info[self.stock_info.index.values > date]
                break
        self.stock_info.index.name = "Date"
        return self.stock_info

    # 종목/지수 일자, 시가,저가,고가, 종가 데이터를 서버에서 가져오고 stock_info 에 저장
    def _opt10081(self,rqname,trcode):
        data_cnt = self._get_repeat_cnt(trcode, rqname)
        for i in range(data_cnt):
            date = self._comm_get_data(trcode, "", rqname, i, "일자")
            open = self._comm_get_data(trcode, "", rqname, i, "시가")
            low = self._comm_get_data(trcode, "", rqname, i, "저가")
            High = self._comm_get_data(trcode, "", rqname, i, "고가")
            close = self._comm_get_data(trcode, "", rqname, i, "현재가")
            volume = self._comm_get_data(trcode, "", rqname, i, "거래량")
            daily_data = pd.DataFrame({'Open':open, 'Low':low, 'High':High,'Close':close, 'Volume':volume}, index=[date])
            self.stock_info = daily_data.append(self.stock_info)



Main.py

import Kiwoom as ki import sys from PyQt5.QtWidgets import * import Db as db # 선정한 etf 종목 코드 code_list =['226980', '223190', '266360', '266370', '278540', '117700', '102960', '244580', '091160', '140700', '102780', '091170', '091180', '102970', '117680', '266420', '069500', '278530', '229720', '156080', '247780', '279530', '211900', '244670', '213610', '247790', '117460', '140710', '229200', '226490', '244660', '237350', '139260', '138530', '217790', '228810', '098560', '211560', '228800', '174350', '261060', '267300', '261070', '277650', '138540', '228790', '139290', '139270', '139230', '139240', '170350', '227570', '261140', '210780', '157520', '251590', '161510', '280920', '266550', '104520', '105780', '270800', '234310', '122630', '243880', '243890', '233740', '252670', '251340', '150460', '174360', '168580', '219900', '261250', '101280', '196030', '205720'] app = QApplication(sys.argv) dB = db.StockDB("qhdks12#$") kiwoom = ki.Kiwoom() for code in code_list: data = kiwoom.req_stock_daily_value(code) data['Close'] = data['Close'].astype('int64') data['Volume'] = data['Volume'].astype('int64') data['Code'] = code # 당일 거래 대금 계산 data['Price'] = data['Volume']*data['Close'] # 이전 3일 평균 거래 대금 계산 data['Mean'] = data['Price'].rolling(window=3).mean().shift(1) data['Shift'] = data['Price'].shift(1) # 이전 3일 평균 거래 대금 또는 전날 거래 대금이 1억이상이면 당일날 거래 가능한 종목으로 선정 data['is_trade'] = (data['Mean'] >= 100000000) | (data['Shift'] >= 100000000) data = data.drop(columns=['Price', 'Mean', 'Shift']) # 마지막 일봉 데이터는 오버나잇할 시 매도 가격 데이터가 존재하지 않기 때문에 0 data.loc[data.index[-1], 'is_trade'] = 0 dB.insert_stock(data)



참고로 필자는 컴퓨터 공학과를 재학 중인 대학생입니다. 따라서 코드가 완벽할 수 없습니다. 알고리즘이나 코드가 비효율적이거나 오류가 있다면 댓글 달아주세요..

+ Recent posts