if __name__ == “__main__”

 

 

이전 글 보러 가기

 

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

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

12_파이참에서 주피터노트북 열기

13_키움API 삼성전자 자동매매 개요

 

 

 

if __name__ == “__main__”

 

이 짧은 코드 한 줄이 나에게 많은 것을 깨우치게 해줬다. 깨달음은 다음 글에서 포스팅할 예정이다. 키움 주식 자동화 14번째 글은 많은 시간을 투자해서 알게 된 if __name__ == “__main__” 에 대한 글이다.

 

 

이 코드의 목적은 명확하다.

해당 모듈이 인터프리터에서 직접 실행된 경우에는 if절 아래의 내용을 실행하는 것이다.

 

 

이 짧은 문장을 이해하기 위해선 모듈을 실행시키는 방법에 대한 이해가 필요하다.

1. 실행시키고자 하는 모듈을 다른 모듈에서 import 해서 실행

2. 실행시키고자 하는 모듈을 인터프리터(interpreter)에서 직접 실행

 

 

주식 자동화 포스팅을 하면서 유튜브 프로그램 동산(이하 "프동")의 도움을 많이 받았다. 프동이 실습 모듈을 제작하고 실행하는 과정은 다음과 같았다.

 

1. __init__.py 에서 ui.py를 import

2. ui.py 에서 kiwoom.py를 import

 

__init__.py와 ui.py 모듈은 별다는 코드가 없었다. 키움 API를 사용하는 모든 내용은 kiwoom.py에 들어있었다. 이 상태에서 코드를 실행시킬 때 kiwoom.py 파일이 아닌 __init__.py 파일을 실행한다. 그럼 kiwoom.py 파일이 import(init ← ui ← kiwoom) 되어 실행된다. 이렇게 kiwoom.py 모듈이 다른 모듈에 의해 import 되어 실행되면, 오늘의 주제인 <<if __name__ == “__main__”>> 코드가 필요 없다.

 

### __init__.py
from ui.ui import *

class Main():
    def __init__(self):
        print("실행할 메인 class")

        Ui_class()

if __name__ == "__main__":
    Main()



### ui.py
from kiwoom.kiwoom import *
import sys
from PyQt5.QtWidgets import *

class Ui_class():
    def __init__(self):
        print("Ui_class 입니다.")
        self.app = QApplication(sys.argv)
        self.kiwoom = Kiwoom()
        self.app.exec()


### kiwoom.py
class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__()
        print("Kiwoom 클래스 입니다.")
    ...
	...
    ...

그러고 보니 인터프리터에서 직접 실행하는 init.py 모듈에도 마지막에 <<if __name__ == “__main__”>> 코드가 있었다....

 

 

 

모듈이 import 되지 않고 인터프리터(interpreter)에서 바로 실행한다는 것은, kiwoom.py 모듈을 단독으로 실행하는 것을 의미하며, 이럴 경우에는 <<if __name__ == “__main__”>> 코드가 필요하다.

 

 

 

그 이유는 다음과 같다.

 

__name__은 인터프리터(interpreter)가 실행 전에 만들어준 글로벌 변수이다.

모듈이 import 되어 실행되면 __name__ 변수에 현매 모듈 이름인 kiwoom이 할당된다.

하지만, 모듈을 인터프리터(interpreter)에서 직접 실행하면 __name__ 변수에 __main__이 할당된다.

 

그래서 if 절 다음에 나오는 코드들은 class 또는 def로 정의한 내용들이다.

 

 

 

도움받은 감사한 글

1. medium.com/@chullino/if-name-main-%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-bc48cba7f720

2. dojang.io/mod/page/view.php?id=2448

 

 

 

2021.01.22. 코리.

 

키움 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

 

 

 

지난 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 설치하기

 

 

이전글 보기

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. 코리.

+ Recent posts