주식 자동매매 시스템

파이썬을 이용한 주식 자동매매 시스템

이미지
파이썬을 이용한 주식 자동매매 시스템 INDEX 환경구축 키움증권 API - 연결테스트 키움증권 API - 계좌정보 조회 키움증권 API - 주문 키움증권 API - 종목정보 가져오기 포트폴리오 - 종목, 업종별 자산 포트폴리오 한국투자증권 API API reference 키움 OpenAPI+ 개발가이드 한국투자증권 OpenAPI 다운로드 및 가이드 Design https://www.design-seeds.com/in-nature/succulents/cacti-color-2/ https://create.piktochart.com/dashboard

파이썬을 이용한 주식 자동매매 시스템 3 - 계좌정보 조회



키움 API의 기본적인 통신


'CommRqData'라는 Method를 통해 데이터를 송신하고, 
'OnReceiveTrData'를 통해 데이터를 받아와 처리하는 방식이다. 


LONG CommRqData(BSTR sRQName, BSTR sTrCode, long nPrevNext, BSTR sScreenNo)
  • sRQName – 사용자구분 명 
  • sTrCode - Tran명 입력 
  • nPrevNext - 0:조회, 2:연속 
  • sScreenNo - 4자리의 화면번호 

void OnReceiveTrData(LPCTSTR sScrNo, LPCTSTR sRQName, LPCTSTR sTrCode,LPCTSTR sRecordName, LPCTSTR sPreNext, LONG nDataLength, LPCTSTR sErrorCode, LPCTSTR sMessage, LPCTSTR sSplmMsg)
  • sScrNo – 화면번호 
  • sRQName – 사용자구분 명 
  • sTrCode – Tran 명 
  • sRecordName – Record 명 


대부분의 TR 통신은 위 두개의 함수로 조회하고 받아볼 수 있다. 파이썬으로 구현하기 위해 이 전에 작성했던 KiwoomTrade class에 아래 함수들을 추가 했다.


CommRqData


    def set_input_value(self, id, value):
        self.dynamicCall("SetInputValue(QString, QString)", id, value)
	
    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_()

OnReceiveTrData


    def _set_signal_slots(self):
        self.OnEventConnect.connect(self._event_connect)
        self.OnReceiveTrData.connect(self._receive_tr_data)
        

    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

        exec('self._' + rqname.split('_')[0] + '(rqname, trcode)')

        try:
            self.tr_event_loop.exit()
        except AttributeError:
            pass
            
    # GetCommData -- CommGetData (문서에서는 GetCommData 쓰라고 되어있는데 안됨)
    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()
        

주고 받는 데이터를 구분하기 위해 sRQName 을 사용하는데, TR code를 포함하여 'TrCode_req' 포맷을 사용했고, _receve_tr_data 에서 '_TrCode' 함수를 호출하도록 했다.
따라서 아래와 같이 사용하는 TrCode마다 처리해주는 함수를 정의해줘야 한다.

ex) TrCode opw00001
    kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")
    def opw00001(self, rqname, trcode):


기본 통신 코드가 완성됐기 때문에, 계좌정보를 조회하는 Tr을 보내보도록 하자.


TrCode: opw00001 - 예수금상세현황요청 

계좌번호 = 전문 조회할 보유계좌번호 
- SetInputValue("계좌번호" , "입력값 1"); 
비밀번호 = 사용안함(공백) 
- SetInputValue("비밀번호" , "입력값 2"); 
Open API 조회 함수를 호출해서 전문을 서버로 전송
- CommRqData( "RQName" , "opw00001" , "0" , "화면번호");

위와 같이 계좌번호와 비밀번호를 써주고 'opw00001' TrCode를 전송하면된다. python 코드는 아래와 같다.
    kiwoom.set_input_value("계좌번호", account_number)
    kiwoom.set_input_value("비밀번호", password)
    kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")
        

데이터를 처리하는 부분은 KiwoomTrade class안에 아래와 같이 정의한다.
    def _opw00001(self, rqname, trcode):
        self.d2_deposit = self._comm_get_data(trcode, "", rqname, 0, "d+2추정예수금")
  


TrCode: opw00018 - 계좌평가잔고내역요청


 1. Open API 조회 함수 입력값을 설정합니다.
	계좌번호 = 전문 조회할 보유계좌번호
	SetInputValue("계좌번호"	,  "입력값 1");

	비밀번호 = 사용안함(공백)
	SetInputValue("비밀번호"	,  "입력값 2");

 2. Open API 조회 함수를 호출해서 전문을 서버로 전송합니다.
	CommRqData( "RQName"	,  "opw00018"	,  "0"	,  "화면번호");  
이 Tr 또한 계좌번호와 비밀번호를 써주고 'opw00018' TrCode를 전송하면된다. python 코드는 아래와 같다.
    kiwoom.set_input_value("계좌번호", account_number)
        kiwoom.set_input_value("비밀번호", password)

    kiwoom.comm_rq_data("opw00018_req", "opw00018", 0, "2000")

데이터를 처리하는 부분은 KiwoomTrade class안에 아래와 같이 정의한다. 아래 code를 통해 single data와 multi data를 처리하는 방법의 차이를 볼 수 있다. multi data를 처리하기 위해서는 _get_repeat_cnt 함수가 필요한데, 아래 전체 코드에 추가하도록 하겠다.
     def _opw00018(self, rqname, trcode):
        total_purchase_price = self._comm_get_data(trcode, "", rqname, 0, "총매입금액")
        total_eval_price = self._comm_get_data(trcode, "", rqname, 0, "총평가금액")
        total_eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, 0, "총평가손익금액")
        total_earning_rate = self._comm_get_data(trcode, "", rqname, 0, "총수익률(%)")
        estimated_deposit = self._comm_get_data(trcode, "", rqname, 0, "추정예탁자산")

        rows = self._get_repeat_cnt(trcode, rqname)
        for i in range(rows):
            name = self._comm_get_data(trcode, "", rqname, i, "종목명")
            quantity = self._comm_get_data(trcode, "", rqname, i, "보유수량")
            purchase_price = self._comm_get_data(trcode, "", rqname, i, "매입가")
            current_price = self._comm_get_data(trcode, "", rqname, i, "현재가")
            eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, i, "평가손익")
            earning_rate = self._comm_get_data(trcode, "", rqname, i, "수익률(%)")

            print(name, quantity, purchase_price, current_price, eval_profit_loss_price, earning_rate)

전체 코드 (import account - account.py 는 계좌 관리를 위해 따로 만든 파일이다)
from SystemTrade import *
import sys
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
import account

class KiwoomTrade(QAxWidget, SystemTrade):
def __init__(self):
super().__init__()
self._create_kiwoom_instance()
self._set_signal_slots()

def _create_kiwoom_instance(self):
self.setControl("KHOPENAPI.KHOpenAPICtrl.1")

def _set_signal_slots(self):
self.OnEventConnect.connect(self._event_connect)
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()

def get_login_info(self, tag):
ret = self.dynamicCall("GetLoginInfo(QString)", tag)
return ret

def get_code_list_by_market(self, market):
code_list = self.dynamicCall("GetCodeListByMarket(QString)", market)
code_list = code_list.split(';')
return code_list[:-1]

def get_master_code_name(self, code):
code_name = self.dynamicCall("GetMasterCodeName(QString)", code)
return code_name

def get_connect_state(self):
ret = self.dynamicCall("GetConnectState()")
return ret

def set_input_value(self, id, value):
self.dynamicCall("SetInputValue(QString, QString)", id, value)

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):
if next == '2':
self.remained_data = True
else:
self.remained_data = False

exec('self._' + rqname.split('_')[0] + '(rqname, trcode)')

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

def _opw00001(self, rqname, trcode):
self.d2_deposit = self._comm_get_data(trcode, "", rqname, 0, "d+2추정예수금")

def _opw00018(self, rqname, trcode):
total_purchase_price = self._comm_get_data(trcode, "", rqname, 0, "총매입금액")
total_eval_price = self._comm_get_data(trcode, "", rqname, 0, "총평가금액")
total_eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, 0, "총평가손익금액")
total_earning_rate = self._comm_get_data(trcode, "", rqname, 0, "총수익률(%)")
estimated_deposit = self._comm_get_data(trcode, "", rqname, 0, "추정예탁자산")

rows = self._get_repeat_cnt(trcode, rqname)
for i in range(rows):
name = self._comm_get_data(trcode, "", rqname, i, "종목명")
quantity = self._comm_get_data(trcode, "", rqname, i, "보유수량")
purchase_price = self._comm_get_data(trcode, "", rqname, i, "매입가")
current_price = self._comm_get_data(trcode, "", rqname, i, "현재가")
eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, i, "평가손익")
earning_rate = self._comm_get_data(trcode, "", rqname, i, "수익률(%)")

print(name, quantity, purchase_price, current_price, eval_profit_loss_price, earning_rate)

if __name__ == "__main__":
app = QApplication(sys.argv)
kiwoom = KiwoomTrade()
kiwoom.comm_connect()

account_number = kiwoom.get_login_info("ACCNO")
account_number = account_number.split(';')[0]

# d2 예수금
kiwoom.set_input_value("계좌번호", account_number)
kiwoom.set_input_value("비밀번호", account.kiwoom['simple_pw'])
kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")
print(kiwoom.d2_deposit)

# 총매입금액, 총평가금액, 총평가손익금액, 총수익률, 추정예탁자산을
kiwoom.set_input_value("계좌번호", account_number)
kiwoom.comm_rq_data("opw00018_req", "opw00018", 0, "2000")

재테크 관련 함께 보면 좋은 글

이 블로그의 인기 게시물

Linux에서 CSV파일 사용방법

R에서 외부 데이터 이용하기 (Excel, csv)

[R 함수] aggregate, apply 사용 방법