< 4. 주식 데이터 불러오기 (키움 open api 주식 알고리즘 테스트 프로그래밍) >


1. 키움 open api를 이용한 주식 알고리즘 테스트 프로그래밍

2. DB 설계

3. DB 구현


해당 포스트를 읽기 전에 위의 포스트들을 읽기 바란다. 또한 키움 open api 사용법을 모른다면 "파이썬으로 배우는 알고리즘 트레이딩" 사이트에서 공부하길 추천한다. 여기서는 키움 open api 사용법에 대해서 설명하지 않는다.


우리는 키움 open api를 사용해서 KODEX 코스닥 150 레버리지 분봉 데이터와 일봉 데이터를 불러와서 DB에 저장할 것이다. 다음은 키움 open api로 해당 데이터들을 불러오는 코드이다.(전체 코드는 마지막에...)

# KODEX 코스닥 150 레버리지 종목의 분봉 데이터를 요청한다. # 약 100일치 분봉 데이터를 요청한다. def req_minute_data(self): self.set_input_value("종목코드","233740") self.set_input_value("틱범위", 1) self.set_input_value("수정주가구분", 0) self.comm_rq_data("opt10080_req", "opt10080", 0, "1999") for i in range(50): time.sleep(0.2) self.set_input_value("종목코드", "233740") self.set_input_value("틱범위", 1) self.set_input_value("수정주가구분", 0) self.comm_rq_data("opt10080_req", "opt10080", 2, "1999") print("코스닥 레버리지 분봉 데이터 저장 성공") # KODEX 코스닥 150 레버리지 종목의 일봉 데이터를 요청한다. def req_day_data(self): self.set_input_value("종목코드","233740") self.set_input_value("기준일자", "20171108") self.set_input_value("수정주가구분",0) self.comm_rq_data("opt10081_req", "opt10081",0,"2000") print("코스닥 레버리지 일봉 데이터 저장 성공")

'opt10080' 은 분봉 데이터를 요청한다. 틱범위로 1을 주면 1분 단위 분봉 데이터를 받을 수 있다.  분봉 데이터 한 번 요청에 약 900개 데이터를 받는다. 하루에 약 350개의 분봉 데이터가 있기 때문에 우리가 구현한 알고리즘을 100일 정도 테스트 하기 위해서는 50번 정도 분봉 데이터를 요청해야 한다. 따라서 위 코드를 보면 'for i in range(50)'으로 50번 'self.comm_rq_data()'를 호출한다. 참고로 세 번째 인자값으로 2가 들어가야 이전에 받은 데이터의 다음 데이터를 요청한다. 

'opt10081'은 일봉 데이터를 요청한다. 한 번 요청하면 900일 데이터를 받기 때문에 약 100일 정도 데이터만 필요한 우리는 한 번만 호출하면 된다. 종목 코드 '233740'은 KODEX 코스닥 150 레버리지를 의미한다.


다음은 'opt10080'과 'opt10081'로 요청한 데이터를 처리하는 코드이다.    

# 서버에게 받은 분봉 데이터를 kosdaq_leve 테이블에 저장한다. # 또한 kosdaq_start 테이블에 매일 시가 정보를 저장한다. def _opt10080(self,rqname,trcode): data_cnt = self._get_repeat_cnt(trcode,rqname) for i in range(data_cnt): print("분봉 데이터 저장중") day = self._comm_get_data(trcode, "",rqname, i, "체결시간") high = self._comm_get_data(trcode, "", rqname, i, "현재가") low = self._comm_get_data(trcode, "", rqname, i, "저가") if(high[0] == '-'): high = high[1:] if(low[0] == '-'): low = low[1:] self.db.insert_Leve(day,int(high),int(low)) #self.db.insert_Leve(day, abs(int(high)), abs(int(low))) if day[8:] == "090000": start = self._comm_get_data(trcode, "",rqname, i, "시가") if(start[0] == '-'): start = start[1:] self.db.insert_Start(day,int(start)) #self.db.insert_Start(day,abs(int(start))) self.db.con.commit() # 서버에게 받은 일봉 데이터를 DB에 저장한다. def _opt10081(self,rqname, trcode): for i in range(150): print("일봉 데이터 저장중") day = self._comm_get_data(trcode, "",rqname, i, "일자") end = self._comm_get_data(trcode, "",rqname, i,"현재가") start = self._comm_get_data(trcode, "",rqname, i,"시가") self.db.insert_Leve_Day(day,start,end) self.db.commit()

'_opt10081()' 메서드는 서버에게 받은 KODEX 코스닥 150 레버리지 일봉 데이터를 'self.db.insert_Leve_Day()'로 DB에 insert한다. self.db는 마지막 부분 전체 코드에서 보다시피 '__init__'부분에 선언했다. 'insert_Leve_Day()' 코드는 이전 포스트에 있다. 그리고 우리는 약 100일치 데이터만 필요하기에 여분을 포함해 150일치 데이터를 받는다.

'_opt10080()' 메서드는 서버에게 받은 분봉 데이터를 'self.db.insert_Leve()'로 DB에 저장한다. 또한 'self.db.insert_Start()'로 하루 시가 데이터를 저장한다. 구체적으로 보면 체결시간, 현재가, 저가 데이터를 받는다. 여기서 현재가, 저가 데이터의 형식에 유의해야 한다. 예로 현재가가 15000이라면 가끔 -15000 데이터를 받는다. 왜 '-'를 붙이는 데이터가 있는 지 확실하진 않지만 아마 음봉이거나 직전 분봉의 현재가보다 낮아지면 '-'를 붙이는 거 같다. 그래서 '-'가 붙은 데이터는 '-'를 떼야 한다. 위 코드를 보다시피 'high'와 'low'의 첫 번째 문자를 확인하는 방법과 절대값을 씌우는 방법이 있다. 이건 취향대로 택일하면 될 듯하다. 그 다음 'if day[8:] == "090000":' 부분을 보자. 체결 시간 데이터 'day'는 분봉 데이터의 일자로써  yyyymmddhhmmss( y:년, m:달, d:일, h:시, m:분, s:초)  형식을 가진다. 주식 장 시작은 09시 00분 00초에 하기에 'if day[8:] == "090000":' 로 요청한 데이터가 장 시작 분봉 데이터인지 확인하고 맞으면 시초가를 불러와 저장한다. 물론 '_opt10081()'에서 불러온 시가를 이용해도 되지만 'opt10081()'과 'opt10080()'에서 불러온 데이터의 갯수가 다르고 데이터의 형식도 조금씩 달라서 '_opt10080()'에서 시가를 저장했다. 


다음은 주식 데이터를 불러오는 Stock.py의 전체 소스이다. 위에서 설명한 내용을 제외한 부분은 첫 번째 포스트에서 말했던 "파이썬으로 배우는 알고리즘 트레이딩" 사이트에 나와있다. 참고하길 바란다.

from PyQt5.QAxContainer import * from PyQt5.QtCore import * import pandas as pd import Db import time class Stock(QAxWidget): def __init__(self): super().__init__() self.db = Db.StockDb() self.db.open_Db() self._create_kiwoom_instance() self._set_signal_slots() # 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 입력값을 서버 통신 전에 입력 # ex. SetInputValue("종목코드","000660") def set_input_value(self,id,value): self.dynamicCall("SetInputValue(QString,QString)", id, value) # 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 def _receive_tr_data(self, screen_no, rqname, trcode, record_name, next, unused1, unused2, unused3, unused4): print("receive_tr_data call") if next == '2': self.remained_data = True else: self.remained_data = False if rqname == "opt10080_req": self._opt10080(rqname, trcode) elif rqname == "opt10081_req": self._opt10081(rqname, trcode) try: self.tr_event_loop.exit() except AttributeError: pass # KODEX 코스닥 150 레버리지 종목의 분봉 데이터를 요청한다. # 약 100일치 분봉 데이터를 요청한다. def req_minute_data(self): self.set_input_value("종목코드","233740") self.set_input_value("틱범위", 1) self.set_input_value("수정주가구분", 0) self.comm_rq_data("opt10080_req", "opt10080", 0, "1999") for i in range(50): time.sleep(0.2) self.set_input_value("종목코드", "233740") self.set_input_value("틱범위", 1) self.set_input_value("수정주가구분", 0) self.comm_rq_data("opt10080_req", "opt10080", 2, "1999") print("코스닥 레버리지 분봉 데이터 저장 성공") # KODEX 코스닥 150 레버리지 종목의 일봉 데이터를 요청한다. def req_day_data(self): self.set_input_value("종목코드","233740") self.set_input_value("기준일자", "20171108") self.set_input_value("수정주가구분",0) self.comm_rq_data("opt10081_req", "opt10081",0,"2000") print("코스닥 레버리지 일봉 데이터 저장 성공") # 서버에게 받은 분봉 데이터를 kosdaq_leve 테이블에 저장한다. # 또한 kosdaq_start 테이블에 매일 시가 정보를 저장한다. def _opt10080(self,rqname,trcode): data_cnt = self._get_repeat_cnt(trcode,rqname) for i in range(data_cnt): print("분봉 데이터 저장중") day = self._comm_get_data(trcode, "",rqname, i, "체결시간") high = self._comm_get_data(trcode, "", rqname, i, "현재가") low = self._comm_get_data(trcode, "", rqname, i, "저가") if(high[0] == '-'): high = high[1:] if(low[0] == '-'): low = low[1:] self.db.insert_Leve(day,int(high),int(low)) #self.db.insert_Leve(day, abs(int(high)), abs(int(low))) if day[8:] == "090000": start = self._comm_get_data(trcode, "",rqname, i, "시가") if(start[0] == '-'): start = start[1:] self.db.insert_Start(day,int(start)) #self.db.insert_Start(day,abs(int(start))) self.db.con.commit() # 서버에게 받은 일봉 데이터를 DB에 저장한다. def _opt10081(self,rqname, trcode): for i in range(150): print("일봉 데이터 저장중") day = self._comm_get_data(trcode, "",rqname, i, "일자") end = self._comm_get_data(trcode, "",rqname, i,"현재가") start = self._comm_get_data(trcode, "",rqname, i,"시가") self.db.insert_Leve_Day(day,start,end) self.db.commit() if __name__ == "__main__": stock = Stock() stock.comm_connect() stock.req_minute_data() stock.req_day_data()

'stock.comm_connect()'를 통해 키움에 로그인하고 분봉과 일봉데이터를 DB에 저장한다.


지금까지 DB 구축과 우리가 필요한 주식 데이터를 불러왔다. 다음 포스트부터는 DB에 저장한 데이터로 적용할 알고리즘을 구현할 것이다.

+ Recent posts