키움 OPEN API 주식 자동화

 

 

이전 글 보러 가기

 

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

06_키움 KOA 설치하기

07_키움 KOA 이해하기

08_키움 KOA 이해하기_2

09_키움 OPEN API 계좌평가 잔고 가져오기

10_키움 OPEN API 일봉데이터 가져오기_1

 

 

 

이전 글에서 일봉데이터를 가져오기 위한 배경에 대해 학습했다. 이제 코드에 대해 알아보자. 코드 작성을 위한 전반적인 도움은 지난 글에서 설명한 유튜브 "프로그램 동산" 채널을 도움을 받았다.

 

 

먼저 조회할 종목 리스트를 만들어보자. 앞서 살펴본 바와 같이 키움 KOA에서 종목정보 관련 함수인 "GetCodeListByMarket()"를 활용할 수 있다.

 

 

키움 KOA 종목 리스트 만들기(GetCodeListByMarket)

 

GetCodeListByMarket()는 시장 구분 값만 파라미터로 보내주면, 해당 시장의 종목코드를 세미콜론(';)으로 구분하여 보내준다.

 

 

"0 장내"는 거래소에 상장되어 있는 주식과 채권 등을 모두 의미한다. 코스피(KOSPI), 코스닥(KOSDAQ) 등을 모두 포함한다.

 

"10 코스닥"은 미국의 벤처기업으로 구성된 시장인 나스닥을 본떠 만든 시장으로 벤처기업과 유망 중소기업을 위한 시장이다.

 

3 ELW, 8 ETF, 50, KONEX, 4 뮤추얼펀드, 5, 신주인수권, 6, 리츠, 9 하이얼펀드, 30 K-OTC 는 아직 별 관심이 없어서 PASS. 

 

 

from PyQt5.QtTest import *  # 일봉조회 타이머 걸기

# 종목 목록 가져오기
def get_code_list_by_market(self, market_code):
    code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
    code_list = code_list.split(";")[:-1]
    return code_list

# 종목 분석 실행용 함수
def calculator_fnc(self):
    code_list = self.get_code_list_by_market("10") # 10: 코스탁
    print("코스닥 갯수 %s" % len(code_list))

    for idx, code in enumerate(code_list):
        self.dynamicCall("DisconnectRealData(QString)", "4000")  # 스크린 연결 끊기
        print("%s / %s : KOSDAQ Stock Code : %s is updating..." % (idx+1, len(code_list), code))
        self.day_kiwoom_db(code=code)
        
# 일봉 가져오기
def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
    QTest.qWait(3600) # 3.6초 delay
    self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
    self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

# date 빈값은 오늘. None이 아니면 날짜를 입력하도록
    if date != None:
        self.dynamicCall("SetInputValue(QString, QString)", "기준일자", date)
    self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.screen_calculation_stock) #TR 서버로 전송
        self.calculator_event_loop.exec_()

 

종목 목록을 가져오기 위한 함수를 "get_code_list_by_market"로 만들고, market_code를 GetCodeListByMarket() 함수로 넘겨준다. 앞서 살펴본 바와 같이 GetCodeListByMarket() 함수는 종목코드를 세미콜론(;)으로 구분하여 넘겨주므로, 세미콜론을 지우기 위해 ".split(";")"을 사용한다. 끝에 [:-1]을 해준 이유는 끝자리에 남아있는 세미콜론을 지워주기 위함이다.

 

"get_code_list_by_market" 함수는 code_list를 return 해 준다.

 

 

calculator_fnc 라는 함수를 만들어서 

1. 위에서 만든 "get_code_list_by_market" 함수에 market_code를 "10"으로 넘겨주고

2. code_list 개수를 프린트하고

3. code_list에서 하나씩 빼와서

4. DisconnectRealData로 스크린 연결을 끊어주고 (이건 안 해도 됨)

5. 진행 상황을 프린트하고

6. 가지고 있는 code_list 하나를 "day_kiwoom_db"로 보내준다.

 

 

"day_kiwoom_db"라는 함수를 만들어서

1. code는 위에서 만든 "calculator_fnc"에서 넘겨받고

2. date는 빈 값으로 두고(빈 값은 오늘을 의미함)

3. sPrevNext는 일단 0으로 설정 (0은 "이전" 버튼을 누르지 않는 것임)

4. Qtest.qwait(3600)으로 이번 조회 후 다음 조회하기까지 3.6초를 기다려 준다. 기다림 없이 조회하면 키움 서버 과부하 방지를 위해 오류를 반환한다.

5. CommRqData로 데이터를 요청한다.

6. calculator_event_loop를 실행해준다.

 

 

 

def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):


    if sRQName == "주식일봉차트조회":
        code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
        code = code.strip()
        print("%s 일봉데이터 요청" % code)

        # 조회된 row 출력
        cnt = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
        print(cnt)

        # 조회 자료 리스트 만들기
        # GetCommDataEx 매소드 사용 검토
        # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
        # [['', '현재가', ...., '저가', ''] .... ['', '현재가', ...., '저가', '']]
        # [['', '현재가', '거래량', '거래대금', '날짜', '시기', '고가', '저가', '']]
        for i in range(cnt):
            data = []
            current_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "현재가") # 종가
            volume = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래량")
            trading_value = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래대금")
            date = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "일자")
            staring_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "시가")
            high_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "고가")
            low_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "저가")

			data.append("")
            data.append(current_price.strip())
            data.append(volume.strip())
            data.append(trading_value.strip())
            data.append(date.strip())
            data.append(staring_price.strip())
            data.append(high_price.strip())
            data.append(low_price.strip())
            data.append("")

			self.calcul_data.append(data.copy())

		print(len(self.calcul_data))
        
        
        # 일봉 600일 이상치 가져오기(페이지 넘기기)
        if sPrevNext == "2":
            self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
        else:
            print("총 일수 %s" % len(self.calcul_data))
            pass_success = False
        
            self.calculator_event_loop.exit()

 

 

trdata_slot 함수로 준비된 데이터를 활용해서 요청한다.

중간중간에 print를 활용해서 진행상황을 출력하도록 해야 오류 생성 시 디버깅이 가능하다.

 

일봉데이터를 요청하면 다음과 같은 리스트 형태로 반환된다.

[['', '현재가', '거래량', '거래대금', '날짜', '시기', '고가', '저가', '']]

 

반환된 값을 하나씩 data 리스트에 담아준다.

 

 

 

일봉을 조회하는 opt10081 TR은 한번 조회 시 600일치의 데이터를 보내준다. 600일 이전의 데이터를 가져오기 위해서는 sPrevNext를 "2"로 넘겨줘야 한다.

 

 

600일 이전의 데이터를 모두 받아왔으면, "calculator_event_loop"를 끊어준다.

 

 

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

 

2021.01.18. 코리.

 

 

 

키움 OPEN API 일봉데이터 가져오기

 

 

이전 글 보러 가기

 

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

06_키움 KOA 설치하기

07_키움 KOA 이해하기

08_키움 KOA 이해하기_2

09_키움 OPEN API 계좌평가 잔고 가져오기

 

 

 

이전까지 키움 OPEN API에 대한 기본적인 조회와 기능을 알아봤다면, 이제 데이터를 가져오는 방법이다. 그 시작으로 일봉데이터를 가져오는 방법을 학습한다.

 

 

영웅문 삼성전자 일봉 조회

 

 

영웅문에서 일봉차트를 조회하면 위와 같은 결과를 볼 수 있다. 보기에는 복잡해 보이는 저 차트를 그리는 데이터는 생각보다 간단한다.

 

종목의 시가, 종가, 저가, 고가, 거래량. 5가지 데이터만 있으면 캔들 차트를 그릴 수 있다.

 

 

일봉차트

 

 

시가, 종가, 고가, 저가 데이터로 일봉차트를 그릴 수 있고, 이동평균선(보라색, 파란색, 노란색 선)은 과거 20일, 60일, 120일 등의 평균 가격으로 그릴 수 있다. 

 

즉, 일봉데이터를 가져온다는 말은, 하루의 시가, 종가, 고가, 저가, 거래량 데이터를 가지고 온다는 말이다.

 

 

 

영웅문 일봉 조회, 600일

 

일봉차트를 조회하면 600일치만 가지고 온다. 위 차트에서 마지막 날짜가 2018년이다. 데이터 분석을 위해서는 더 많은 날짜의 데이터가 필요하다.

 

 

영웅문 일봉 조회, 9541일

 

과거 차트 조회 버튼을 계속 눌러주면 600일씩 늘어난다. 삼성전자의 경우, 9541일까지인 1985년까지 조회된다.

코드에서도 과거 자료가 있는 경우, 과거 차트 조회 버튼을 계속 눌러줘야 한다.

 

 

삼성전자 일봉 과거 데이터

 

과거 조회 버튼을 누를수록 화면에 전체 일봉 수(9541)가 늘어나는 것을 확인할 수 있다.

 

그러므로 "09_계좌평가 잔고 가져오기"에서 살펴본 "sPrevNext"를 이번에도 사용해야 한다. 

 

 

 

 

다음으로 조회할 종목의 리스트는 만들어야 한다. 시장 구분별로 장내, 코스닥, ELW, ETF, KONEX 등으로 구분할 수 있으며, 시장 구분에 대한 파라미터와 함께 데이터를 요청할 수 있다.

 

키움 OPEN API - KOA 종목 목록 가져오기 

 

키움 KOA에서 종목정보 관련 함수인 "GetCodeListByMarket()"를 제공한다. 시장 구분 값을 마라메터로 함께 제공하면 된다. 학습에서는 코스닥 구분 값이 10번을 사용한다.

 

 

 

키움 KOA 주식일봉차트조회

 

키움 KOA의 TR목록에서 opt10081번으로 주식 일봉 차트조회 요청을 할 수 있다. 위에서 살펴본 바와 같이, 한 번에 600개 데이터가 조회된다는 안내가 있다.

 

넘겨야 할 파라미터는 "종목코드, 기준일자, 수정주가구분" 3가지이다.

  - 종목코드는 위에서 살펴본 "GetCodeListByMarket()" 함수로 확보한다.

  - 날짜는 오늘을 기준으로 하려면 공란으로 둔다. 

  - 수정주가구분은 0과 1로 구분되는데, 1번이 수정주가구분이다. 삼성전자 주가가 2018년에 약 250만원이었는데, 이를 약 50:1로 액면분할하며 주가가 53,000원이 되었다. 수정주가구분을 0으로 주면 2018년 주가는 250만원, 현재 주가는 9만원으로 조회된다. 액면분할 등을 바로잡아 놓은 주가가 수정주가이다.

 

 

3가지 파라미터를 넘기면 받을 수 있는 데이터는 다음의 15개이다.

 

0. 종목코드

1. 현재가

2. 거래량

3. 거래대금

4. 일자

5. 시가

6. 고가

7. 저가

8. 수정주가구분

9. 수정비율

10. 대업종구분

11. 소업종구분

12. 종목정보

13. 수정주가이벤트

14. 전일종가

 

0~7번까지의 8개 데이터는 제공되는데, 8~14번까지의 7개 데이터는 조회/제공되지 않는다.

 

 

 

일봉데이터 조회 코드를 짜기 위한 기본 내용은 여기까지 이고, 코드는 다음 글에서 소개한다.

 

 

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

2021.01.14. 코리.

 

 

 

키움 OPEN API 계좌평가 잔고내역 가져오기

 

 

이전 글 보기

 

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

06_키움 KOA 설치하기

07_키움 KOA 이해하기

08_키움 KOA 이해하기_2

 

 

 

지난 05번 글에서, 키움 OPEN API에서 TR(transaction) 요청으로 예수금과 계좌잔고를 받아왔다.

 

오늘 계좌평가 잔고내역을 가지고 오고자 한다.

 

계좌평가 잔고내역은 다음 영웅문에서와 같이 내 계좌에 가지고 있는 종목들의 현황과 계좌잔고, 수익률 등의 정보를 담고 있는 화면이다. 영웅문에서는 화면번호 0391번이다.

 

 

키움 계좌평가 잔고내역

 

 

나의 모의 투가 계좌에는 총 22개 종목이 있다. 모의투자 금액으로 22개 종목을 담았는데 벌써 수익률이 5.34%로 48만원을 벌고 있다. 왜 모의투자만 버냐고....

 

 

 

계좌평가 잔고내역 조회를 하기 전에 몇가지 개념을 잡아야 한다. 계좌평가 잔고내역에서는 상당히 많은 정보가 있다.

 

1. 내 계좌 전체 평가액, 수익률, 추정자산, 매입, 손익

2. 내 계좌에 담겨있는 종목명

3. 내 계좌에 담겨있는 종목별 평가손익, 수익률, 매입가, 보유수량, 가능수량, 현재가

4. 화면 우측 상단에 있는 "다음" 버튼

 

 

1~3번은 직관적이라 설명은 생략하고, 4번에 대한 설명이 필요하다.

영웅문에서 계좌평가 잔고내역을 조회하면 종목 20개만 우선 조회된다. 보유 종목이 20개가 넘는다면, 창 우측 상단의 "다음" 버튼을 눌러 20개를 초과하는 종목 정보를 불러올 수 있다.

 

위 그림에서 봐도 "하이트진로 ~ SK이노베이션"이 20개이다. 총 수익률이 6.48%를 보이고 있다. 이 수익률은 나의 보유 종목 22개 중 우선 조회된 20개 종목에 대한 수익률이다.

 

이 상태에서 화면 우측 상단의 "다음" 버튼을 눌러주면 그 뒤에 숨어있던 "카카오게임, 빅히트" 종목이 드러난다. 그리곤 수익률이 5.34%로 감소한다. 이는 22개 종목에 대한 수익률이다.

 

이러한 방식이 키움 OPEN API에서도 동일하게 작동한다. "다음" 버튼을 눌러줄 필요가 있다는 것이다.

 

 

 

이제 키움 KOA에서 내용을 확인해 보자

 

키움 KOA - opw00018

 

키움 KOA에서 계좌평가잔고내역요청의 TR번호는 opw00018이다. 넘겨줘야하는 파라메터들에 대한 정보가 나타나 있다.

 

 

 

이번에는 키움 KOA에서 "GetRepeatCnt"에 대해 알아보자.

 

키움 KOA - GetRepeatCnt

 

GetRepeatCnt() 함수는 조회수신한 멀티데이터의 갯수(반복)수를 얻을수 있다. 예를들어 차트조회는 한번에 최대 900개 데이터를 수신할 수 있는데 이렇게 수신한 데이터갯수를 얻을때 사용한다. 이 함수는 반드시 OnReceiveTRData()이벤트가 호출될때 그 안에서 사용해야 한다.

 

지금은 계좌평가 잔고내역을 조회하고 있으며, 한번에 조회가능한 최대 데이터는 20개이다.

 

 

 

이제 코드를 알아보자.

코드 작성을 위한 전반적인 도움은 지난 글에서 설명한 유튜브 "프로그램 동산" 채널을 도움을 받았다.

 

 

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

        self.account_num = "81547491111"
        self.account_pw = "********"
        self.account_stock_dict = {}
        self.detail_account_mystock()


    def detail_account_mystock(self, sPrevNext="0"):
        # 계좌평가 잔고내역 요청
        # sPrevNext="0" : 종목 페이지(목록) 넘기지 않기
        print("계좌평가 잔고내역 요청하기 연속조회 %s" % sPrevNext)
        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(QString, QString)", "비밀번호", self.account_pw)
        self.dynamicCall("SetInputValue(QString, QString)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(QString, QString)", "조회구분", "2")
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)

        self.detail_account_info_event_loop.exec_()


	def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
        if sRQName == "계좌평가잔고내역요청":
            total_buy_money = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총매입금액")
            print("총매입금액 %s" % int(total_buy_money))

            total_profit_loss_rate = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총수익률(%)")
            print("총수익률(%s) : %s" % ("%", float(total_profit_loss_rate)))

            # 계좌평가잔고 개별 종목 Count 조회, 멀티데이터 가져오기
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "종목번호")
                code_nm = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "종목명")
                stock_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "보유수량")
                buy_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "매입가")
                learn_rate = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "수익률(%)")
                current_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "현재가")
                total_chegual_price = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "매입금액")
                possible_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "매매가능수량")

                if code in self.account_stock_dict:
                    pass
                else:
                    self.account_stock_dict.update({code:{}})

                #code = code.strip()[1:] # 공백지우고 종목코드 앞 알파벳 제외한 값 만들기(A:장내주식, J:ELW종목, Q:ETN종목)
                code_nm = code_nm.strip() # 공백지우기
                stock_quantity = int(stock_quantity.strip())
                buy_price = int(buy_price.strip())
                learn_rate = float(learn_rate.strip())
                current_price = int(current_price.strip())
                total_chegual_price = int(total_chegual_price.strip())
                possible_quantity = int(possible_quantity.strip())

                # account_stock_dict에 담기
                self.account_stock_dict[code].update({"종목명": code_nm})
                self.account_stock_dict[code].update({"보유수량": stock_quantity})
                self.account_stock_dict[code].update({"매입가": buy_price})
                self.account_stock_dict[code].update({"수익률(%)": learn_rate})
                self.account_stock_dict[code].update({"현재가": current_price})
                self.account_stock_dict[code].update({"매입금액": total_chegual_price})
                self.account_stock_dict[code].update({"매매가능수량": possible_quantity})

                cnt += 1

            print("계좌에 있는 종목 %s" % cnt)
            print("계좌에 있는 종목 %s" % self.account_stock_dict)

            # 종목이 20개 초과라 다음 페이지 클릭이 필요한 경우
            # 종목이 20개 초과인 경우, sPrevNext가 2로 반환됨
            if sPrevNext == "2" :
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_account_info_event_loop.exit()

 

"def detail_account_mystock(self, sPrevNext="0"):"에서 목록이 몇개인지 모르므로, sPrevNext는 우선 0으로 지정한다. 0은 "다음" 버튼을 누르지 않는 것을 의미한다.

 

SetInputValue함수로 넘길 파라메터(아이디, 비번 등)를 넘겨준다. 그것들을 모아 CommRqData 함수로 데이터를 넘긴다.

 

 

"def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):" TR보내는 슬롯을 만들어 작업한다.

 

"total_buy_money"와 "total_profit_loss_rate"는 내 계좌의 총정보를 의미한다. 가장 위의 영웅문 그림에서 "총수익률, 총손익" 등을 의미한다.

 

 

"rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)"로 행 수를 rows 변수에 담아 둔다. 그 아래 for문에서 종목 하나씩 관련 정보를 요청한다. code가 account_stock_dict에 있으면 넘어가고, 없으면 딕셔너리를 업데이트(자료 추가하기)를 실행한다. 

 

"self.account_stock_dict.update({code:{}})"는 다음으로 대체하여 사용할 수 있다.

"self.account_stock_dict[code] = {}"

 

수신한 데이트 전처리를 해주고, 만들어 놓은 account_stock_dict에 자료를 업데이트 한다.

 

"cnt+= 1" cnt 변수를 1 증가시켜 준다.

 

 

종목이 20개를 초과하여 sPrevNextrk "2"로 반환되는 경우, "sPrevNext="2" 로 넘겨 "다음버튼"을 누를 수 있도록 해준다.

 

 

 

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

2021.01.13. 코리.

 

 

 

키움 KOA 이해하기_2

 

이전 글 보기

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

06_키움 KOA 설치하기

07_키움 KOA 이해하기

 

 

 

 

키움 KOA의 개요는 다음과 같다.

 

OpenAPI(Application Programing Interface)는 
국내주식상품과 코스피200 지수선물/옵션, 주식선물을 거래할 수 있는 거래/분석프로그램을 개발 할 수 있는 일종의 프로그램이며 COM형태로 제공합니다. 

주요기능은 시세데이터 조회와 실시간 데이터제공, 주문기능, 조건검색기능(주식만 가능)을 제공하며 모두 로그인 이후 가능합니다.

 

여기서 COM은 OCX를 의미한다. OCX는 "07. 키움 KOA 이해하기"에서 다루었다.

 

주요기능은 "시세데이터 조회, 실시간 데이터제공, 주문기능, 조건검색기능(주식만 가능)"이다. 이 정도면 내가 만들고자 하는 것을 하기엔 문제가 없어 보인다.

 

 

 

키움 API의 통신동작은 다음과 같다.

 

[통신동작] OpenAPI함수호출과 이벤트는 모두 비동식 방식으로 서버에 시세조회나 주문등을 함수호출로 요청하면 처리 결과를 전용 이벤트를 호출해서 전달합니다.

여기서 수신데이터를 가져오려면 반드시 이벤트 리턴전에 적절한 데이터 획득함수를 사용해서 얻어와야 합니다. 이벤트가 호출하기 전에 데이터 획득함수를 사용하거나 이벤트 리턴후에 호출하게 되면 혹은 임의로 이벤트를 호출하면 정상적인 데이터를 얻을수 없습니다.

지금까지 설명을 조회요청을 예를들어 정리하면 다음과 같습니다. 이외 OpenAPI기능도 관련 함수와 이벤트 차이가 있을뿐 전체 동작은 동일합니다.

기능요청(조회함수 CommRqData, CommKwRqData 호출) --->
이벤트 호출(OnReceiveTRData ) --->
데이터 획득 (GetCommData함수) 

 

요청은 반드시 전용 이벤트를 호출해서 전달한다. 수신한 데이터는 이벤트 리턴 전에 얻어와야 한다. 이벤트를 리턴하면 정상적인 데이터를 얻을 수 없게 된다.

 

CommRqData, CommKwRqData, OnReceiveTRData, GetCommData...

유튜브 프로그램 동산 채널을 보면서 공부한 코드의 의미를 하나씩 깨우쳤다.

 

 

[화면번호] 화면번호는 서버에 조회나 주문등 필요한 기능을 요청할때 이를 구별하기 위한 키값으로 이해하시면 됩니다. 0000(혹은 0)을 제외한 임의의 숫자를 사용하시면 되는데 갯수가 200개로 한정되어 있기 때문에 이 갯수를 넘지 않도록 관리하셔야 합니다. 만약 사용하는 화면번호가 200개를 넘는 경우 조회결과나 주문결과에 다른 데이터가 섞이거나 원하지 않는 결과를 나타날 수 있습니다. 

 

화면번호(스크린번호)는 "05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)"에서 살펴본 내용이다.

 

 

 

[이벤트] 시세조회나 조건검색요청등 관련함수 호출로 요청하면 서버에서 이를 처리해서 결과를 OpenAPI의 특정함수를 호출해서 알려주는데 이 함수를 이벤트라고 합니다.

조회를 예로 들면 SetInputValue(), CommRqData()함수로 조회를 하면 조회결과를 만들어서 OpenAPI로 전달하고 조회한 사용자의 OnReceiveTRData를 호출하게 됩니다.

이벤트는 일반함수와 구별하기 위해 이름앞에 On~에 있으며 OnEventConnect, OnReceiveTRData, OnReceiveRealData등이 있습니다. 로그인, 조회, 주문, 조건검색등 기능별로 함수와 이벤트가 분류되어 있으니 용도에 맞는 함수와 이벤트를 사용해야 합니다.

 

SetInputValue(), CommRqData(), OnReceiveTRData, OnEventConnect, OnReceiveTRData, OnReceiveRealData 등 코딩하면서 봤던 함수와 이벤트 이름들이 다 여기에 기인한 것들이다.

 

사용 목적과 이유를 모르고 코딩할 때는 궁금한 게 많았는데, 작동 원리를 확인하니 마음이 편해진다.

 

 

 

 

이런 내용들이 "KOA-개발가이드-키움OPenAPI"에 다 있었는데, 안본게 후회된다. 이제라도 봐서 다행이다. 그때 봤다면,,,, 지금처럼 무슨 말인지 이해를 했을까??

 

 

 

참고한 내용

- 키움 KOA

- 키움 OpenAPI+ 개발가이드

 

 

 

2021.01.09. 코리.

 

 

키움 KOA Studio 이해하기

 

 

이전 글 보기

 

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

06_키움 KOA 설치하기

 

 

 

코드만 주구장창 치다 보니 뭔가 정리가 되지 않는 기분이 들었다. OPEN API 설명서 격인 키움 KOA에 대해 이해하는 시간이 필요했다.

 

대부분의 내용은 키움 홈페이지 OPEN API에 등재된 개발가이드와 KOA 설명 페이지에서 확인할 수 있다.

 

우선 키움 OPEN API는 프로그램 확장성을 고려하여 OCX 컨트롤러로 제작을 지원한다. OCX는 OLE Control Extensions을 의미하며, OLE는 Object Linking and Embedding(객체 연결 삽입)를 의미한다.

 

무슨 말인지 모르겠다. 네이버 지식백과는 OCX를 다음과 같이 정의한다.

OLE(object linking and embedding)에 의해 작성된 사용자 정의 컨트롤 기능으로 프로그램 확장자를 OCX에 대응한 애플리케이션에서 호출할 수 있다. OLE의 명칭이 ActiveX로 변경되면서 ActiveX 컨트롤이 정식 명칭이 되었다. 사용자 정의 컨트롤은 제3자 벤더와 사용자가 독자적으로 만든 윈도용 프로그램 부품의 총칭이다. 스크롤 바와 같은 단순한 것에서부터 스프레드시트와 철자 검사 프로그램, 웹 서버의 브라우저 등의 복잡한 기능을 갖춘 것까지 그 범위가 다양하다.
[네이버 지식백과] OCX (컴퓨터인터넷IT용어대사전, 2011. 1. 20., 전산용어사전편찬위원회)

 

 

 

키움 KOA

 

키움 KOA는 위와 같은 화면을 구성하고 있다.

 

1번 작업창은 다음의 기능을 수행한다.

   - 영웅문 HTS의 화면에 해당되는 서비스

   - 전체 TR 서비스 목록

   - TR의 IO

   - 실시간 타입과 FID

 

2번 정보창은 다음의 기능을 수행한다.

   - 선택된 TR에 대한 정보

   - TR의 input값 설명

   - 코딩 샘플(C++)

   - 실시간 필드 정보

 

3번 입력창은 다음의 기능을 수행한다.

   - 선택된 TR에 대한 input값 입력

   - input값 설명

 

4. 결과창은 다음의 기능을 수행한다.

   - TR 요청에 대한 결과

   - 실시간 수신 데이터

   - 조회 TR 히스토리

 

그리고 화면 하단에 "실시간 목록", "TR목록", "종목정보", "개발가이드", "화면목록"의 탭이 있다.

 

 

 

키움 KOA

 

함수를 선택하고자 할 때 함수 앞에 LONG, void, BSTR과 같은 구분자가 있다. 이들의 의미는 다음과 같다.

 

LONG : C 등의 언어에서 64비트 숫자를 데이터 타입을 말한다. 2**63개의 숫자를 표현할 수 있다.  [1]

void : C 등의 언어에서 호출자에게 결과값을 제공하지 않는 함수의 결과를 위한 타입이다. [2]

BSTR : Basic string or binary string을 의미하며, 문자열을 타입이다. [3]

 

즉, LONG는 숫자, BSTR은 문자 타입을 의미하며, void는 반환하는 값이 없다는 구분이다.

C언어 능숙자에겐 별거 아니겠지만, 나에겐 오랜 검색이 필요했다.

 

 

OPEN API 제어 및 편집을 위한 인터페이스 매소드는 다음과 같다. [4]

ID 타입 이름 설명
1 LONG CommConnect 로그인 윈도우를 실행한다
2 void CommTerminate  이상 지원하지 않는 함수
3 LONG CommRqData 통신 데이터를 송신한다
4 BSTR GetLoginInfo 로그인 정보를 반환한다
5 LONG SendOrder 주식주문 Tran 을 송신한다
6 LONG SendOrderCredit 주식 신용주문 Tran 을 송신한다
7 void SetInputValue  
8 LONG SetOutputFID  
9 BSTR CommGetData 이 함수대신 용도에 맞는 전용 함수를 사용할 것
  -조회 정보 요청 GetCommData()
  -실시간정보 요청 GetCommRealData()
  -체결정보 요청 GetChejanData()
10 void DisconnectRealData 리얼 시세를 끊는다
11 LONG GetRepeatCnt 수신 받은 데이터의 반복 개수를 반환한다
12 LONG CommKwRqData 관심종목을 조회 한다
13 BSTR GetAPIModulePath OpenApi 모듈이 설치되어 있는 디렉토리를 반환한다
14 BSTR GetCodeListByMarket 장구분별 종목코드 리스트를 반환한다
15 LONG GetConnectState 통신 접속 상태를 반환한다
16 BSTR GetMasterCodeName 종목코드의 종목명을 반환한다
17 LONG GetMasterListedStockCnt 종목코드의 상장주식수를 반환한다
18 BSTR GetMasterConstruction 종목코드의 감리구분을 반환한다
19 BSTR GetMasterListedStockDate 종목코드의 상장일을 반환한다
20 BSTR GetMasterLastPrice 종목코드의 전일가를 반환한다
21 BSTR GetMasterStockState 종목코드의 종목상태를 반환한다
22 LONG GetDataCount 레코드의 반복개수를 반환한다
23 BSTR GetOutputValue 수신 데이터를 반환한다
24 BSTR GetCommData 수신 데이터를 반환한다
25 BSTR GetCommRealData 실시간 데이터를 반환한다
26 BSTR GetChejanData 체결잔고 데이터를 반환한다
27 BSTR GetThemeGroupList 테마코드와 그룹의 리스트를 반환한다
28 BSTR GetThemeGroupCode 테마코드에 해당하는 종목코드를 반환한다
29 BSTR GetFutureList 지수선물 코드 리스트를 반환한다
30 BSTR GetFutureCodeByIndex 지수선물 코드를 반환한다
31 BSTR GetActPriceList 지수옵션 행사가 리스 트를 반환한다
32 BSTR GetMonthList 지수옵션 월물 리스트를 반환한다
33 BSTR GetOptionCode 지수옵션 종목코드를 반환한다
34 BSTR GetOptionCodeByMonth 같은 행사가 다른 월물의 종목코드를 반환한다
35 BSTR GetOptionCodeByActPrice 같은 월물 다른 행사가의 종목코드를 반환한다
36 BSTR GetSFutureList 주식선물 코드 리스트를 반환한다
37 BSTR GetSFutureCodeByIndex 주식선물 코드를 반환한다
38 BSTR GetSActPriceList 주식옵션 행사가를 반환한다
39 BSTR GetSMonthList 주식옵션 월물을 반환한다
40 BSTR GetSOptionCode 주식옵션 코드를 반환한다
41 BSTR GetSOptionCodeByMonth 주식옵션 월물만 변경된 코드를 반환한다
42 BSTR GetSOptionCodeByActPrice 주식옵션 행사가만 변 경된 코드를 반환한다
43 BSTR GetSFOBasisAssetList 주식선옵 기초자산코드와 이름을 반환한다
44 BSTR GetOptionATM 지수옵션 ATM 을 반환한다
45 BSTR GetSOptionATM 주식옵션 ATM 을 반환한다
46 BSTR GetBranchCodeName 회원사 코드와 이름을 반환한다
47 BSTR CommInvestRqData 지원하지 않는 함수
48 LONG SetInfoData 아이디별 자동로그 인이 접속이 필요할 때 사용한다
49 LONG SetRealReg 실시간 등록을 한다
50 void SetRealRemove 종목별 실시간 해제 (SetRealReg로 등록한 종목만 해제 가능)
51 LONG GetConditionLoad 서버에 저장된 사용자 조건식을 가져온다
52 BSTR GetConditionNameList 조건검색에 필요한 조건명 리스트를 받아온다
53 void SendCondition 조건검색 조회 TR 을 송신한다
54 void SendConditionStop 실시간 조건검색을 중지한다
55 Variant GetCommDataEx 차트 조회한 데이터 전부를 배열로 받아온다

 

 

OPEN API 컨트롤 이벤트를 목록은 다음과 같다. 이벤트 목록은 이름이 "On"으로 시작한다. [4]

 

ID 타입 이름 설명
1 void OnReceiveTrData Tran 수신시 이벤트
2 void OnReceiveRealData 실시간 시세 이벤트
3 void OnReceiveMsg 수신 메시지 이벤트
4 void OnReceiveChejanData 주문 접수 확인 수신시 이벤트
5 void OnEventConnect 통신 연결 상태 변경시 이벤트
6 void OnReceiveRealCondition 조건검색 실시간 편입 이탈종목 이벤트
7 void OnReceiveTrCondition 조건검색 조회응답 이벤트
8 void OnReceiveConditionVer 로컬에 사용자조건식 저장 성공여부 응답 이벤트

 

 

 

인터페이스 메소드와 이벤트 목록은 자주 보면서 눈에 익어야 코딩이 원활하겠다.

지금까지 별생각 없이 입력을 했는데, KOA에 대해 공부를 조금 하니 왜 그렇게 코딩을 해야 하는지, 하드 조각모음 하듯 조금씩 이해되고 있다. 

 

 

 

도움받은 고마운 글들

[1] techterms.com/definition/long

[2] ko.wikipedia.org/wiki/Void_%ED%83%80%EC%9E%85

[3] docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr

[4] 키움 OPEN API+ 개발 가이드(첨부 참고)

kiwoom_openapi_plus_devguide_ver_1.5.pdf
2.16MB

 

 

 

2021.01.06. 코리.

 

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

 

키움 KOA 설치하기

 

 

이전글 보기

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

05_키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

 

 

지난 5번째 글을 작성하다가 깨우친게 있었다. KOA를 자주 사용해야 하는데 설치 방법에 대해 언급하지 않았다.

사실.... 설치랄 것도 없는데;;; 그래도 기본 설정을 위해 알아야 한다.

 

 

키움 KOA

 

 

KOA는 키움 API 사용설명서이다.

 

API에 요청할 수 있는 것이 무엇인지 알려주며,

시험삼아 요청할 수 있게 해주고

요청하기 위해 어떤 함수와 파라미터를 사용해야 하는지 

등등 많은 것을 알려준다.

 

KOA Studio에서 KOA가 무슨 약자인지는 모르겠지만, 추정하면.... Kiwoom Open Api Studio가 아닐까 한다.

 

 

위 사진에서 처럼 KOA를 다운로드 받는다. 압축파일이 하나 다운로드 되는데, 그 안에 파일이 2개 있다.

KOA 실행파일을 더블 클릭하면 KOA가 실행된다. 별도의 설치 과정없이 그냥 실행된다.

압축파일 속 파일 2개를 키움 API 설치 폴더로 옮겨 둔다.

 

 

나는 키움 OPEN API를 C 드라이브의 "Kiwoom" 폴더의 "OpenApi"에 설치했다. 그래서 다음의 폴더에 옮겨두었다.

 

키움 KOA 파일 위치

 

 

매번 저 폴더로 이동하기 귀찮으니깐, KOAStudioSA 파일을 우클릭하여 "시작화면에 고정"해서 사용 중이다.

 

 

2021.01.05. 코리.

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

키움 OPEN API에 TR 요청하기(feat. 예수금과 계좌잔고 받아오기)

 

 

지난 글 보기

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

04_키움 open api 로그인 하기

 

 

 

지난 시간에 키움 api 자동 로그인까지 성공했다.

로그인을 했으니 나의 계좌 정보를 받아와야 하겠다. 대표적으로 예수금과 계좌평가잔고내역 받아오기를 해보았다.

코드 작성을 위한 전반적인 도움은 지난 글에서 설명한 유튜브 "프로그램 동산" 채널을 도움을 받았다.

 

 

조회하고자 하는 내용은 영웅문에서 다음의 내용이다.

나는 모의투자로 1,000만원을 신청해둔 상태이며, 아직 매매를 하지 않아 1,000만원이 모두 예수금 잔액으로 잡혀있다.

 

영웅문 예수금상세현황 조회

 

영웅문 계좌평가잔고내역 조회

 

 

영웅문에서 화면으로 조회되는 것을 OPEN API로 조회하기 위해서는 TR(Transaction)을 요청해야 하며, 어떤 요구사항들이 있는지 알아보기 위해 KOA를 실행한다.

 

 

키움 KOA TR 요청 - 예수금 검색

 

KOA에서 하단의 "TR목록"을 클릭하고, 상단 검색란에 "예수금"을 입력한 뒤 엔터키 말고 마우스로 "다음"을 클릭한다.

opw00001을 찾아 클릭하면 위와 같은 화면이 조회된다. 받을 수 있는 자료 목록이 왼쪽에 나열되고, C++ 기준 예제 코드가 중앙 상단에 위치하고 있다. 오른쪽에 있는 조회 창에 계좌번호 등의 정보를 입력하면, 하단에 출력된다.

 

나의 예수금이 1,000만원으로 조회되었지만, 앞에 0이 많이 붙어 숫자가 아닌 text 형식임을 추측할 수 있다.

 

 

키움 KOA TR 요청 - 예수금 검색

 

 

위에서 조회한 데이터를 요청하는 방법은 KOA 하단의 "개발가이드"에서 찾을 수 있다. 각종 조회와 실시간데이터처리 중 "OnReceiveTrData"를 활용할 수 있다. 함께 넘겨야하는 파라미터들도 확인할 수 있다.

 

 

 

 

이제 코드로 넘어간다.

지난 시간의 코드와 중복되는 코드는 생략하여 "def" 부분만 표기한다.

from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
from config.errorCode import *

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

        ### event loop 모음 ###############################
        self.login_event_loop = None
        self.detail_account_info_event_loop = None
        self.detail_account_info_event_loop_2 = None
        ##################################################

        ### 변수 모음 #####################################
        self.account_num = None
        self.account_pw = "********"
        ##################################################

        self.detail_account_info()
        self.detail_account_mystock()

    def get_ocx_instance(self):
    def event_slots(self):
        self.OnReceiveTrData.connect(self.trdata_slot) # 예수금 받기 이벤트 생성
    def signal_login_commConnect(self):
    def login_slot(self, errCode):
    def get_account_info(self):

    def detail_account_info(self):
        # 예수금 조회를 위한 Open API 조회 함수 입력값을 설정
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", self.account_pw)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2")
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        self.detail_account_info_event_loop = QEventLoop()
        self.detail_account_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        # 계좌평가 잔고내역 요청
        # sPrevNext="0" : 싱글데이터 받아오기(종목합계 데이터)
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", self.account_pw)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2")
        self.dynamicCall("CommRqData(String, String, int, String)", "계좌평가잔고내역요청", "opw00018", sPrevNext, "2000")
        self.detail_account_info_event_loop_2 = QEventLoop()
        self.detail_account_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
        # TR SLOT 만들기
        '''
        TR 요청을 받는 구역, slot임
        :param sScrNo: 스크린 번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청 ID, TR코드
        :param sRecordName: 사용안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''

        # 예수금 등 조회 하기
        if sRQName == "예수금상세현황요청":
            deposit = self.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "예수금")
            print("예수금 %s" % int(deposit))

            ok_deposit = self.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "출금가능금액")
            print("출금가능금액 %s" % int(ok_deposit))

            self.detail_account_info_event_loop.exit()

        # 계좌평가잔고 조회 하기
        if sRQName == "계좌평가잔고내역요청":
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "총매입금액")
            print("총매입금액 %s" % int(total_buy_money))

            total_profit_loss_rate = self.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "총수익률(%)")
            print("총수익률 %s" % float(total_profit_loss_rate))

            self.detail_account_info_event_loop_2.exit()

 

 

 

 

조회 요청을 한 뒤, 응답이 오기 전에 다음 라인의 코드가 실행되지 않도록 이벤트 루프를 __init__안에 생성한다.

"self.detail_account_info_event_loop = None"
"self.detail_account_info_event_loop_2 = None"

 

예수금과 계좌잔고 조회를 실행할 수 있도록 __init__안에 마련한다.

"self.detail_account_info()"

"self.detail_account_mystock()"

 

"def event_slots"에 예수금을 받기 위한 이벤트를 만들어 준다(self.OnReceiveTrData.connect(self.trdata_slot)). "OnReceiveTrData"는 위 KOA에서 살펴봤다.

 

 

"def detail_account_info(self):"는 예수금 TR 요청을 위한 공간이다.

SetInputValue로 넘길 값을 형태를 지정해주고 dynamicCall로 데이터를 전송한다. 넘겨야 하는 데이터는 위 KOA에서 예수금 조회할 때 넘긴 것과 동일하다.

 

넘길 데이터를 다 입력했으면, 조회할 데이터는 이벤트 내부에서 OPEN API 조회 함수인 "CommRqData()"를 호출해서 서버로 전송한다. 이때 함께 전송할 자료는 요청이름(각자 네이밍 가능), TR번호, preNext, 화면번호(스크린넘버-하단에 별도 설명)이다.

 

조회 후 결과가 도착하기 전에 다음 코드가 실행되지 않도록 PyQt의 Event Loop를 열어준다.

 

 

"def detail_account_mystock(self, sPrevNext="0"):"는 예수금 조회와 같은 내용이다. 다만, 계좌평가잔고내역을 요청하는 코드이다.

 

 

"def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):"로 TR 데이터 슬롯을 열어준다. 예수금과 계좌평가잔고 코드를 입력한다. 각 요청한 TR을 받은 뒤 열어둔 Event Loop를 닫아준다.

 

 

코드 실행 결과

 

 

 

 

 

아.... 길다....

 

위에서 넘어간 화면번호(스크린넘버)는 키움 OPEN API가 TR 요청에 의해 데이터를 넘겨주면서 그룹을 지어서 넘겨준다. 그 그룹의 이름을 "화면번호(스크린넘버)"라고 부른다. [1]

 

화면번호(스크린넘버)는 0과 0000을 제외한 4자리 숫자로 구성하여 200개까지 만들 수 있다.

그리고 각 화면번호(스크린넘버)에는 100개의 TR 요청 결과를 담을 수 있다.

 

TR 요청을 중구난방으로 하게 되면, 관리가 안되니 일종의 grouping 기능을 넣어준 것이라 해석된다.

 

예를 들어, 코스피200의 일봉을 요청하는데

화면번호 1000번 : 전자통신 30 종목

화면번호 2000번 : 화학 40 종목

화면번호 3000번 : 바이오 60 종목

화면번호 4000번 : 제조 70개 종목

등으로 그룹을 지어 결과를 저장할 수 있는 방식이다. 

 

화면번호(스크린넘버)는 삭제할 수 있고, 삭제되면 거기에 포함된 TR 요청은 모두 삭제되는 등 관리적 기능도 포함되어 있다.

 

 

 

도움받은 곳

[1] www1.kiwoom.com/nkw.templateFrameSet.do?m=m1408000000

    : 위 고객 문의 게시판에서 "스크린번호"를 검색하면 관련 내용을 찾아볼 수 있다.

 

 

 

파이썬을 학습하며 남기는 블로그입니다.
질문, 지적, 조언은 항상 환영합니다.

 

 

2021.01.05. 코리.

 

04. 키움 open api 로그인 하기

 

이전 글 보러 가기

01_시작

02_기본설정

03_pykiwoom vs. 직접코딩

 

 

하루 사이에 대문이 조금 바뀌었다.

IDE를 vs code에서 pycharm으로 변경했다. vs code가 전반적으로 많은 사람들이 사용하지만, python 사용자들은 pycharm을 더 많이 사용한다는 글을 보았다. 그리고, pycharm이 python 문법을 작성하는게 더 편리했다. 

 

 

키움 open api에 로그인하기 위한 코드는 다음과 같다.

코드 작성을 위한 전반적인 도움은 지난 글에서 설명한 유튜브 "프로그램 동산" 채널을 도움을 받았다.

 

 

from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
from config.errorCode import *

필요한 패키지를 import 한다.

"PyQt5.QAxContainer"는 ActiveX를 비롯한 OCX에 대한 관리를 위한 것이고 [1]

"PyQt5.QtCore"는 event loop를 사용하기 위함이고  [2]

세 번째 라인은 에러 메시지를 관리하기 위함이다. 이건 아래에서 다시 설명한다.

 

 

 

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

        ### event loop 모음 ###############################
        self.login_event_loop = None
        ##################################################

        ### 변수 모음 #####################################
        self.account_num = None
        ##################################################

        self.get_ocx_instance()
        self.event_slots()
        self.signal_login_commConnect()
        self.get_account_info()

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot)

    def signal_login_commConnect(self):
        self.dynamicCall("CommConnect()")
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec()

    def login_slot(self, errCode):
        print(errors(errCode))
        self.login_event_loop.exit()

    def get_account_info(self):
        account_list = self.dynamicCall("GetLogininfo(String)", "ACCNO")
        self.account_num = account_list.split(';')[0]
        print("나의 보유 계좌번호 %s" % self.account_num)

 

"get_ocx_instance"는 QAxWidget에 있는 setControl 사용하여, 설치된 경로(레지스트리)를 지정("KHOPENAPI.KHOpenAPICtrl.1")하여야 한다.

 

"event_slots"은 자동로그인 이벤트를 생성하기 위함이다.

 

"signal_login_commConnect"는 네트워크에 데이터를 전송하고(dynamicCall), 자동 로그인하기 위함이다.

 

키움 KOA CommConnect 설명

 

"self.login_event_loop = QEventLoop()" 가 실행되면, 아이디와 비밀번호를 입력하라는 창이 출력된다. 자동 로그인을 하기 위해서는 다음의 조치가 필요하다.

 

 

키움 open api 자동 로그인 설정

 

윈도우 작업표시줄 오른쪽 아래에 빨간색 상자로 표기된 아이콘에 마우스 우클릭하여 자동로그인을 설정한다.

 

처음 로그인을 하거나, 버전 업데이트 등으로 버전 처리가 필요한 경우, 버전처리 알림 창이 뜨는데 다음을 잘 읽어보고 순서대로 해야 성공한다.

1. 알림창을 그대로 둔다. (확인을 눌러 닫지 않는다.)

2. 키움 관련 프로그램을 모두 종료한다.(KOA, OPEN API 등)

3. 알림창을 "확인"을 눌러 닫는다.

4. 코드를 다시 실행한다.

 

키움 open api 버전처리 알림창

 

"login_event_loop.exec()"는 로그인이 완료될 때까지 다음 코드 실행을 막기 위함이다. 로그인하는 과정은 필요한 파일 등을 다운로드하기 위해 시간이 다소 소요되는데, 이 코드 없이는 아직 로그인 준비가 진행 중인 상황에서 다음 코드를 실행해버리는 결과가 나타난다. 

 

 

 

"login_slot"은  로그인 상태를 확인하고, 로그인 완료 시 위에서 생성한 event loop를 종료(login_event_loop.exit())하는 코드이다.

 

가장 위에 있는 import 단계에서 "config.errorCode"를 import 했다.

현재 나의 작업 폴더에 config 폴더를 만들고 그 안에 errorCode.py 파일을 하나 만들어서 다음의 코드를 입력해 두어야 한다.

 

 

def errors(err_code):
    err_dic = {
        0: ("OP_ERR_NONE", "정상처리"),
        -10: ("OP_ERR_FAIL", "실패"),
        -100: ("OP_ERR_LOGIN", "사용자정보교환실패"),
        -101: ("OP_ERR_CONNECT", "서버접속실패"),
        -102: ("OP_ERR_VERSION", "버전처리실패"),
        -103: ("OP_ERR_FIREWALL", "개인방화벽실패"),
        -104: ("OP_ERR_MEMORY", "메모리보호실패"),
        -105: ("OP_ERR_INPUT", "함수입력값오류"),
        -106: ("OP_ERR_SOCKET_CLOSED", "통신연결종료"),
        -200: ("OP_ERR_SISE_OVERFLOW", "시세조회과부하"),
        -201: ("OP_ERR_RQ_STRUCT_FAIL", "전문작성초기화실패"),
        -202: ("OP_ERR_RQ_STRING_FAIL", "전문작성입력값오류"),
        -203: ("OP_ERR_NO_DATA", "데이터없음"),
        -204: ("OP_ERR_OVER_MAX_DATA", "조회가능한종목수초과"),
        -205: ("OP_ERR_DATA_RCV_FAIL", "데이터수신실패"),
        -206: ("OP_ERR_OVER_MAX_FID", "조회가능한FID수초과"),
        -207: ("OP_ERR_REAL_CANCEL", "실시간해제오류"),
        -300: ("OP_ERR_ORD_WRONG_INPUT", "입력값오류"),
        -301: ("OP_ERR_ORD_WRONG_ACCTNO", "계좌비밀번호없음"),
        -302: ("OP_ERR_OTHER_ACC_USE", "타인계좌사용오류"),
        -303: ("OP_ERR_MIS_2BILL_EXC", "주문가격이20억원을초과"),
        -304: ("OP_ERR_MIS_5BILL_EXC", "주문가격이50억원을초과"),
        -305: ("OP_ERR_MIS_1PER_EXC", "주문수량이총발행주수의1%초과오류"),
        -306: ("OP_ERR_MIS_3PER_EXC", "주문수량은총발행주수의3%초과오류"),
        -307: ("OP_ERR_SEND_FAIL", "주문전송실패"),
        -308: ("OP_ERR_ORD_OVERFLOW", "주문전송과부하"),
        -309: ("OP_ERR_MIS_300CNT_EXC", "주문수량300계약초과"),
        -310: ("OP_ERR_MIS_500CNT_EXC", "주문수량500계약초과"),
        -340: ("OP_ERR_ORD_WRONG_ACCTINFO", "계좌정보없음"),
        -500: ("OP_ERR_ORD_SYMCODE_EMPTY", "종목코드없음")
        }

    result = err_dic[err_code]

    return result

 

위 코드에서는 로그인을 포함한 모든 오류 코드를 담아 두고, 필요시 가져다 쓰고자 함이다.

 

자동로그인에서 정상적으로 로그인이 되면, "print(errors(errCode))"에 의해 다음과 같은 출력물이 나타난다. 

"('OP_ERR_NONE', '정상처리')"

 

오류코드는 KOA에서 확인할 수 있다.

KOA OPEN API 오류코드

 

 

"get_account_info"는 로그인이 되었으니, 사용자 정보를 가져오기 위한 부분이다.

"dynamicCall("GetLogininfo(String)", "ACCNO")" 코드로 사용자 정보를 호출한다.

 

이렇게 호출된 계좌번호는 "8154749111;" 와 같이 끝에 세미콜론이 붙어있다. 아마 아이디가 2개 이상인 경우 구분하기 위한 목적으로 보인다. 

 

"account_list.split(';')[0]"으로 콜론을 지워주고, 첫 번째 값을 가져와서 사용한다.

 

 

 

이제 키움 OPEN API에 로그인하고, 계좌정보까지 가지고 왔다.

 

 

 

도움받은 감사한 글들

[1] docs.huihoo.com/pyqt/PyQt5/QAxContainer.html

[2] docs.huihoo.com/pyqt/PyQt5/QtCore.html

 

 

 

2020.01.04. 코리.

 

+ Recent posts