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

(주식 자동 매매) 키움증권 OpenAPI 4종목 현재가 조회(opt10001)

봄이오네 2022. 9. 26. 08:01
반응형

1. 들어가며

지난 번에는 삼성전자(005930)의 현재가를 받아보았다.
이번에는 4종목의 현재가pandas모듈의 데이터프레임에 담아보자
* 4종목 : 삼성전자(005930), 경동나비엔(009450),
아프리카TV(067160), 토니모리(214420)

그런데, 왜 하필 4종목의 현재가만 구현하는 것일까?
필자의 관심사는 해외선물 자동매매거래이다.
* 4종목 : 해외선물의 가스(NG), 골드(GC), 나스닥(NQ), 원유(CL)

해외선물의 시세조회 및 거래를 위한 OpenAPI 사용을 위해서는,
OpenAPI 한달 이용료가 $170달러이다.
환율 1,423원('22.9.23 기준)으로 계산해 보면,
우리 돈 241,910원, 즉 240천원을 매달 키움 측에 지불해야 한다.


아직 코딩 초창기인 나에게, 240천원을 매달 계산하면서,
KOA StudioSA 및 OpenAPI-W에서 받아오는 데이터를 활용하여,
수익 모델을 짜는 것은 사실상 불가하다.

먼저는 국내 주식 모델을 고민해보고, 내년 1월부터 해외선물로 발을 돌릴 것이다.
잡설이 너무 길었다. 본론으로 들어가보자.


2. 사전 준비

KOA StudioSA에 접속하여,
입력 및 출력 정보를 확인할 필요가 있다.

① TR목록에서 opt10001을 조회
② 입력정보에서는 종목코드만 필요하다는 것을 확인
③ 출력 예정인 데이터는 종목코드와 현재가 2가지를 확인
④ opt10001 TR을 이용하기 위해서는 SetInputValue, CommRqdata 2개 함수 활용
⑤ 종목코드 입력 → 4종목 입력할 예정
⑥ 출력되는 정보는 종목코드와 현재가, 2가지

< 그림1. 4종목의 현재가를 받더라도 코드는 비슷하다. >

 


3. 코드 구현

  • 키움증권의 OpenAPI에 접속하여 로그인을 하고,
  • for문을 활용하여 삼성전자, 경동나비엔, 아프리카TV, 토니모리 4가지를 요청/수신한다.


5줄 : DataFrame 활용을 위해 pandas모듈을 임포트한다.
6줄 : 10초마다 동일 함수를 반복하기 위해 threading모듈을 임포트한다.

15줄 : 요청한 trdata를 받기위해 OnReceiveTrData와 trdata_get함수를 연결
24줄 : 키움증권 OpenAPI에서 받아온 데이터를 pandas모듈의
DataFrame에 넣어주기 위해 딕셔너리형 변수 선언
(53줄~54줄에서 키움증권에서 얻어온 정보를 업데이트 해준다)

26줄 : 관심종목(interesting_lists)의 전역변수(광역변수) 선언하는 것이 핵심이다.
→ 함수 어디서든 활용하기 위해 선언
27줄 : 관심종목(interesting_lists)에 문자형으로 각 종목의 코드번호 기재
(여기서는 65줄의 for문에 넣어주기 위함)

36줄 : rq_data_opt100001 임의의 함수를 선언하되,
4종목을 돌리기 위해 "stock_code" 변수 자리를 만든다.
(65줄의 for문을 통해 4종목이 66줄의 함수로 들어가서 순차적으로 실행)
38줄 : dynamicCall 함수을 통해 SetInputValue함수의 종목코드를 키움서버에 입력
39줄 : dynamicCall 함수을 통해 CommRqdata함수 opt10001 TR을 키움서버에 요청
40줄 : tr_event_loop를 통해 데이터 요청/수신되는 동안 다음 코드 실행를 막기 위한 선언
41줄 : tr_event_loop 실행

42줄 : 38줄~39줄을 통해 키움서버에 입력/요청하여 데이터를 받아오면 저장할 함수 선언
43줄 : 요청했던 이름이 "opt_10001"이면, 아래 실행
44줄 : 키움서버로부터 받아온 코드번호
45줄 : 키움서버로부터 받아온 현재가

48줄 : 받아온 코드번호의 앞/뒤에 0000 혹은 빈칸이 있을 수 있으므로 제거(strip)
49줄 : 1분 전 종가보다 현재 종가가 낮을 경우 (-)가 나올 수 있기에,
양수로 만들기 위해 절대값(abs)를 붙인다.
53줄 : 데이터프레임의 종목코드에 "받아온 종목코드" 추가
54줄 : 데이터프레임의 현재가에 "받아온 현재가" 추가
56줄 : 데이터를 받아왔으면, tr_event_loop 종료 필요(exit)

64줄 : 10초마다 실행하기 위한 threading를 위해 current_price24의 임의의 함수 선언
65줄 : 26줄 광역변수 선언된, 27줄의 종목이 들어있는 리스트의 for문 선언
66줄 : 위의 class 안에 있는 rq_data_opt10001에 4개 종목코드를 순차적으로 넣어준다.

68줄 : 받아온 데이터를 stock_code, current_price1의 columns에 넣는다.
→ 24줄의 딕셔너리 선언된 부분에 넣는다.
69줄 : for문이 돌아가면서 df_current_price_gubun은 이전 가격이 누적되어 출력된다.
→ 마지막 내용이 최종이므로 .tail(n=1)을 써서, 마지막 1개만 얻어온다.
70줄 : 데이터프레임으로 들어간 종목코드와 현재가를 출력
71줄 : threading.Timer를 활용하여, 10초마다 실행
72줄 : 64줄~71줄까지의 함수 내용을 실행


4. 전체코드

import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd
import threading

# 현재가 tr요청으로 받기

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")

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

        print("로그인 중입니다.")
        self.kiwoom.dynamicCall("CommConnect()")
        
        # 로그인 이벤트 연결
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.current_price_gubun = {'stock_code': [], 'current_price1': []}

        global interesting_lists
        interesting_lists = ["005930", "009450", "067160", "214420"]

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

    def rq_data_opt10001(self, stock_code):
        print("27줄에서 입력받은 종목코드를 키움서버에 요청합니다.")
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", stock_code)
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "opt_10001", "opt10001", 0, "0303")
        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_10001":
            stock_code = self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10001", "주식기본정보요청", 0, "종목코드")
            current_price1 = self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10001", "주식기본정보요청", 0, "현재가")

            stock_code = stock_code.strip()
            current_price1 = abs(int(current_price1))
            # print(stock_code)  # 결과값 출력하는 곳
            # print(current_price1)  # 결과값 출력하는 곳

            self.current_price_gubun['stock_code'].append(stock_code)
            self.current_price_gubun['current_price1'].append(current_price1)
        try:
            self.tr_event_loop.exit()
        except AttributeError:
            pass

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

    def current_price24():
        for interesting_list in interesting_lists:
            btl.rq_data_opt10001(interesting_list)

            df_current_price_gubun = pd.DataFrame(btl.current_price_gubun, columns=['stock_code', 'current_price1'])
            df_current_price_gubun = df_current_price_gubun.tail(n=1)
            print(df_current_price_gubun)
        threading.Timer(10, current_price24).start()
    current_price24()
    app.exec_()

5. 마치며

4종목의 현재가를 10초마다 받아오는 코드를 알아보았다.
위 코드를 실행시키기 위해, 거의 한달을 신경쓴것 같다.
오늘 하루만 shift + f10을 200번은 누른 듯 하다.

앞으로 넘어야할 산이 많을거 같다. 이제 하나 넘은 듯 하다.ㅠㅠ
그래도 한달간 고민했던 내용이 해결되니, 기분은 한결 좋아지는구나.

다음에는 키움증권의 OpenAPI에서 받아온 데이터를
pandas모듈의 데이터프레임이 아닌,
변수에 넣는 방법을 알아보자.
* 활용처 : 현재가는 언제/어디서든 사용할 수 있도록 전역변수(global)로 선언할 예정이며,
현재가 > 매수가이면 칼같이 매도할 것이다.

반응형