1. 국내주식/1-2. 키움 OpenAPI (사용)

(주식 자동 매매) 키움증권 OpenAPI 매수한 4종목의 매수가격, 매수량 받아오기(opt10085)

봄이오네 2022. 9. 28. 23:42
반응형

1. 들어가며

지난 시간에는 키움증권 OpenAPI를 통해 현재가를 전역변수에 담아 출력해 보았다.

이번 시간에는 매수한 4종목의 매수가격, 매수량을 받아오는 코드에 대해 알아보자.
매수가격 및 매수량은 익절/손절을 구하기 위해 반드시 구현할 필요가 있다.
* 4종목 : 삼성전자(005930), 경동나비엔(009450), 아프리카TV(067160), 토니모리(214420)

  • (익절) 현재가격 > 매수가격 + 2,000원이면, 현재가(시장가)로 매도
  • (손절) 현재가격 < 매수가격 - 3,000원이면, 현재가(시장가)로 매도

2. 사전준비

1) 화면번호 찾기

어제 모의계좌를 통해 4종목을 매수하였다.
매수한 4종목의 매수가격 및 매수량을 알아보기 위해 영웅문4에 접속하여
어떤 카테고리에서 제공하는지 열심히 찾아보았다.
믿기지 않겠지만, 주식은 주로 모바일로 매매를 하다보니,
영웅문4에서 경로 찾는데도 시간이 걸린다. -_-;;
화면번호 0345(실시간계좌관리)에서 제공하고 있다.

< 그림1. 모의계좌에서 조회한 계좌 잔고 >

 

2) 화면목록 찾기

그렇다면 KOA StudioSA에서는 영웅문4의 정보(현재가격, 매수량 등)를 키움서버로부터 어떻게 받아올까?
KOA StudioSA의 화면목록(①)에서 화면목록(②)을 통해 0345를 입력하니,
실시간잔고(③)가 나온다.
2개의 TR이 있는데, 비교적 익숙한 OPT10085(④, 계좌수익률요청)을 활용할 예정이다.

<그림2. 영웅문4의 실시간계좌관리를 KOA Studio에서 찾는 화면 >

 

3) KOA StudioSA에서 출력된 내용

KOA StudioSA의 화면목록(①)의 TR목록(②)에서 "10085"을 입력하면
opt10085(③, 계좌수익률요청)이 나온다.
요청해야할 TR 내용은(④)은 SetInputValue와 CommRqData 2함수이다.
또한, 입력해야될 내용(⑤)은 계좌번호를 입력해주면,
화면 중앙에서 출력(⑥)되는 데이터를 활용할 수 있다.

현재 모의계좌에는 삼섬전자, 경동나비엔, 아프리카TV, 토니모리 4종목이 있다.
받아오는 형태를 보면,
[0][0]
[0][1]
[0][2]
...
[1][0]
[1][1]
[1][2]

특정값에 매칭되어 출력이 된다.
특정값과 특정값이 연계된다는 것은 향후 구현할 때,
딕셔너리 형태로 구현할 필요가 있다고 생각된다.

< 그림3. KOA Studio의 opt10085을 이용하여 데이터를 받아온 화면 >


3. 코드 구현

  • 키움증권의 OpenAPI에 접속/로그인하고,
  • 계좌에 있는 종목의 내용을 받기 위해 {딕셔너리}형태를 선언하고
  • 보유종목인 4개를 입력하여 프로그램을 실행한 후
  • 전역변수 형태로 보유종목의 매수가격, 매수량을 받을 예정이다.

 

< 그림4. 계좌 내의 정보를 받아오기 위해 19줄의 딕셔너리를 만들어 준다 >

※ 로그인과 관련된 내용은 생략한다.
19줄 : 계좌에 있는 종목의 내용을 받기 위해 딕셔너리를 생성한다.
(딕셔너리 안에 아무것도 없는 것은 초기화를 의미한다.)
22줄 : 보유한 4종목의 코드번호를 넣어준다.
26줄 : 모의계좌 번호를 프로그램의 어느곳에서도 사용할 수 있도록 전역변수(global) 선언
27줄 : 모의계좌번호 10자리를 넣어준다.

< 그림5. 45줄의 for문을 활용하여 데이터를 얻어온다 >

36줄 : 계좌번호 전달을 통해 키움서버에서 데이터를 얻어올 것이므로,
rq_data_opt10085의 임의의 함수를 만든다.
37줄 : KOA StudioSA에서 확인했듯이, SetInputValue 함수를 통해 키움서버에 계좌번호를 입력한다.
38줄 : KOA StudioSA에서 확인했듯이, CommRqData 함수를 통해 키움서버에 계좌번호를 요청한다.
39줄 : 데이터를 요청하는 동안, 다음코드의 실행을 막을 목적으로 이벤트 루프를 선언한다.
40줄 : 이벤트 루프를 실행한다.( exec_() )

41줄 : 키움서버에서 데이터를 수신하기 위해, trdata_get 임의의 함수를 만든다.
42줄 : 키움에서 받아오는 rqname가 38줄에서 요청한 "opt_10085"이면, 아래 실행
43줄 : GetRepeatCnt는 키움증권 OpenAPI에서 제공하며, 데이터의 개수를 제공한다.
여기서는 계좌에 있는 종목의 숫자를 제공한다.
4종목을 보유하고 있으므로 "4"를 반환한다.
GetRepeatCnt함수를 rows에 넣어서 변수화 시킨다..
(바로 아래의 for문에서 활용하기 위함)

44줄 : for문과 range를 사용하여 4종목의 데이터를 순차적으로 받아온다.
→ 위의 KOA StudioSA에서 확인했듯이, 계좌번호를 넣고 조회하면,
내가 필요한 정보 외에도, 다른 정보들이 많이 수신된다.
45줄 : 수신받은 데이터 중 코드번호(stock_code)를 변수화함 → for문을 통해 데이터 받아오기 위함
46줄 : 수신받은 데이터 중 매수가격(stock_buy_price)를 변수화함
47줄 : 수신받은 데이터 중 매수량(stock_qty)를 변수화함

50줄 : 계좌 안에 코드번호가 있으면, 패쓰(=다음 코드로 넘어가라)
52줄 : 계좌 안에 코드번호가 없으면, 초기화하라

55줄 : 최초 받아온 매수가격(stock_buy_price)은 문자형이므로,
정수(int)화 시키고, 절대값(abs)를 붙여서 양수화 시킨다.
56줄 : 최초 받아온 매수량은 문자형이므로, 정수형(int)으로 태 변환한다.

58줄 : 계좌에 있는 종목에 대해 "매수가격"를 추가(딕셔너리에서 추가는 update 사용)
* 계좌에 있는 종목은 {stock_code, stock_buy_price} 형태인데,
매수량(stock_buy_price)를 추가함
59줄 : 계좌에 있는 종목에 대해 "매수량"을 추가

61줄 : 종목코드가 23줄에서 선언한 리스트의 첫번째[0]과 같다면,
62줄~63줄 : 매수가격과 매수량은 향후 사용할 것이므로 전역변수 선언
64줄 : 첫번째로 받은 종목의 매수가격(stock_buy_price)를 stock_sbuy0에 넣어라.
65줄 : 첫번째로 받은 종목의 매수량(stock_qty)를 stock_qty0에 넣어라
66줄 : 윗에서 받은 첫번째 종목코드, 매수가격, 매수량을 출력하라 (print)

< 그림6. threading.Timer을 이용하여 5초마다 매수가격/매수량을 받아온다 >

※ 61줄~87줄 : 종목코드가 23줄의 리스트["005930", ... , "214420"]와 비교하여 출력

95줄 : threading.Timer(타이머) 활용을 위해, qty4() 함수를 만들어준다.
96줄 : 36줄의 rq_data_opt10085 함수에 27줄의 계좌(deposit_num)를 넣어서 실행하라.
97줄 : qty4() 함수를 5초마다 실행하라.
99줄 : threading.Timer를 적용하기 위해서는 1번은 실행해 주어야 한다.
99줄의 qty4() 명령어를 통해 95줄의 함수를 실행하고, threading.Timer가 5초마다 실행됨

< 그림7. 5초마다 현재가를 받아온 화면


4. 전체 코드

import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        print("로그인 시작!")

        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)

        self.kiwoom.dynamicCall("CommConnect()")
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.account_stock_dict = {}  # 딕셔너리 생성

        # 보유종목
        global jongmok_lists
        jongmok_lists = ["005930", "009450", "067160", "214420"] # 삼성전자, 경동나비엔, 아프리카TV, 토니모리

        # 계좌번호
        global deposit_num
        deposit_num = "8032508011"

    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
        else:
            print('로그인 실패했습니다!')
        self.login_event_loop.exit()

    def rq_data_opt10085(self, deposit_num_name):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "계좌번호", deposit_num_name)
        self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opt_10085", "opt10085", "0", "8877")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    def trdata_get(self, sScrNo, rqname, strcode, sRecordName, sPreNext, nDataLength, sErrorCode, sMessage, sSplmMsg):
        if rqname == 'opt_10085':
            rows = self.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", strcode, rqname)
            for i in range(rows):
                stock_code = self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", strcode, rqname, i, "종목코드").strip()
                stock_buy_price = self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", strcode, rqname, i, "매입가")
                stock_qty = self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", strcode, rqname, i, "보유수량")

                if stock_code in self.account_stock_dict:
                    pass
                else:
                    self.account_stock_dict[stock_code] = {}

                stock_buy_price = abs(int(stock_buy_price))
                stock_qty = int(stock_qty)

                self.account_stock_dict[stock_code].update({"매입가": stock_buy_price})
                self.account_stock_dict[stock_code].update({"보유수량": stock_qty})

                if stock_code == jongmok_lists[0]:
                    global stock_sbuy0
                    global stock_sqty0
                    stock_sbuy0 = stock_buy_price
                    stock_sqty0 = stock_qty
                    print("종목코드 {}의 매입가는 {}원이고, 보유수량은 {}개이다.".format(jongmok_lists[0], stock_sbuy0, stock_sqty0))

                elif stock_code == jongmok_lists[1]:
                    global stock_sbuy1
                    global stock_sqty1
                    stock_sbuy1 = stock_buy_price
                    stock_sqty1 = stock_qty
                    print("종목코드 {}의 매입가는 {}원이고, 보유수량은 {}개이다.".format(jongmok_lists[1], stock_sbuy1, stock_sqty1))

                elif stock_code == jongmok_lists[2]:
                    global stock_sbuy2
                    global stock_sqty2
                    stock_sbuy2 = stock_buy_price
                    stock_sqty2 = stock_qty
                    print("종목코드 {}의 매입가는 {}원이고, 보유수량은 {}개이다.".format(jongmok_lists[2], stock_sbuy2, stock_sqty2))

                else:
                    global stock_code_sbuy3
                    global stock_code_sqty3
                    stock_sbuy3 = stock_buy_price
                    stock_sqty3 = stock_qty
                    print("종목코드 {}의 매입가는 {}원이고, 보유수량은 {}개이다.".format(jongmok_lists[3], stock_sbuy3, stock_sqty3), '\n')

        self.tr_event_loop.exit()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()

    def qty4():
        btl.rq_data_opt10085(deposit_num)
        threading.Timer(10, qty4).start()

    qty4()

    app.exec_()

5. 마치며

지금까지 매수하여 계좌에 보관 중인 4종목의
매수가격과 매수량을 알아보았다.

익절/손절을 위해 매도를 위해 매수가격은 반드시 파악하여야 한다.
향후 위 코드는 사용자가 매수한 수량이 있을 때만,
실행 될 수 있도록 구현할 예정이다.
보유하지도 않았는데, 굳이 현재가를 조회할 필요는 없어 보인다.

또한 키움 OpenAPI의 설명에 따르면,
데이터 조회 횟수 제한은 1초당 5회이다.
자주 현재가를 조회하면, 서버부하가 생겨서 제한하고 있다.
* 키움증권의 조회 제한 : https://www.kiwoom.com/h/common/bbs/VBbsBoardBWOAZView

현재가와 매수가를 각각 구현하였다.
이 다음에는 1분봉 받는 방법을 알아보자.

반응형