open_api.py 15.1 KB
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
from PyQt5.QtTest import *
from pandas import DataFrame
import sys
import errCode

TR_REQ_TIME_INTERVAL = 3600

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

        # event_loop list
        self.login_event_loop=None
        self.detail_account_info_event_loop=QEventLoop()
        self.calculator_event_loop=QEventLoop()

        # variable list
        self.account_no=None            # 계좌번호
        self.account_stock_dict={}      # 보유종목
        self.not_account_stock_dict={}  # 미체결종목
        self.calc_data=[]               # 종목분석용

        # account variable list
        self.user_money=0
        self.user_money_percent=0.5

        # screen number list
        self.screen_my_info="2000"
        self.screen_calculation_stock="4000"

        self._get_instance()
        self._event_slot()
        self.comm_connect()
        self.account_info()             # 계좌정보 가져오기
        self.detail_account_info()      # 예수금 가져오기
        self.detail_account_mystock()   # 계좌평가잔고내역 가져오기
        self.not_concluded_account()    # 미체결 종목 조회

        self.calculator_fnc() # 종목분석용용

    #get ocx controller to use kiwoom open api
    def _get_instance(self):
        try:
            self.setControl("KHOPENAPI.KHOpenAPICtrl.1")
        except Exception as e:
            print(e)
            sys.exit()

    # event slot to use open api
    def _event_slot(self):
        try:
            self.OnEventConnect.connect(self.login_slot)    # for login
            self.OnReceiveTrData.connect(self.trdata_slot)  # for tr data
        except Exception as e:
            print(e)
            sys.exit()

    # login event slot
    # param code : if login success, code=0
    def login_slot(self,code):
        try:
            result=errCode.errors(code)
            if code==0:
                print("Connected - ",result[0],result[1])
            else:
                print("Failed to connect",result[0],result[1])
            self.login_event_loop.exit()
        except Exception as e:
            print(e)
            sys.exit()

    # try login
    def comm_connect(self):
        try:
            self.dynamicCall("CommConnect()")
            self.login_event_loop = QEventLoop()    # wait for login complete
            self.login_event_loop.exec_()
        except Exception as e:
            print(e)
            sys.exit()

    # get user information
    # param tag: ACCNO - 보유계좌리스트
    #            ACCOUNT_CNT - 보유계좌 수
    #            USER_ID - 사용자 ID
    #            USER_NAME - 사용자 이름
    #            KEY_BSECGB - 키보드 보안 해제 여부 (0 : 정상, 1: 해지)
    #            FIREW_SECGB - 방화벽 설정여부 (0 : 미설정, 1: 설정, 2 : 해지)
    #            GetServerGubun - 접속서버 구분 (1 : 모의투자, 나머지 : 실서버)
    # return: tag를 통해 요청한 정보(ret) 반환
    def get_login_info(self,tag):
        try:
            ret=self.dynamicCall("GetLoginInfo(QString)",tag)
            return ret
        except Exception as e:
            print(e)
            sys.exit()

    # print user account information
    def account_info(self):
        try:
            account_number=self.get_login_info("ACCNO")
            self.account_no=account_number.split(";")[0]
            print("계좌번호 : ",self.account_no)
        except AttributeError as e:
            print(e)
            sys.exit()

    # detail information about deposit
    def detail_account_info(self):
        self.dynamicCall("SetInputValue(String,String)","계좌번호",self.account_no)
        self.dynamicCall("SetInputValue(String,String)","비밀번호",'0000')
        self.dynamicCall("SetInputValue(String,String)","비밀번호입력매체구분",'00')
        self.dynamicCall("SetInputValue(String,String)","조회구분",'2')
        self.dynamicCall("CommRqData(String,String,int,String)","예수금상세현황요청","opw00001","0",self.screen_my_info)

        self.detail_account_info_event_loop.exec_()

    # detail information about account
    def detail_account_mystock(self,sPreNext="0"):
        self.dynamicCall("SetInputValue(QString,QString)","계좌번호",self.account_no)
        self.dynamicCall("SetInputValue(QString,QString)","비밀번호",'0000')
        self.dynamicCall("SetInputValue(QString,QString)","비밀번호입력매체구분",'00')
        self.dynamicCall("SetInputValue(QString,QString)","조회구분",'2')
        self.dynamicCall("CommRqData(QString,QString,int,QString)","계좌평가잔고내역요청","opw00018",sPreNext,"2000")

        self.detail_account_info_event_loop.exec_()

    # detail information about outstanding order
    def not_concluded_account(self,sPrevNext="0"):
        self.dynamicCall("SetInputValue(QString,QString)","계좌번호",self.account_no)
        self.dynamicCall("SetInputValue(QString,QString)","체결구분",'1')
        self.dynamicCall("SetInputValue(QString,QString)","매매구분",'0')
        self.dynamicCall("CommRqData(QString,QString,int,QString)","실시간미체결요청",'opt10075',sPrevNext,self.screen_my_info)

        self.detail_account_info_event_loop.exec_()

    # slot receiving tr request
    # param sScrNo: 스크린번호
    # param sRQName: 요청했을 때 지은 이름
    # param sTrCode: 요청 id, tr코드
    # param sRecordName: 레코드 이름
    # param sPrevNext: 다음 페이지가 있는지 여부. "2" : 다음페이지 존재, "0" or "" : 다음페이지 없음
    def trdata_slot(self,sScrNo,sRQName,sTrCode,sRecordName,sPrevNext):
        if sRQName=="예수금상세현황요청":
            deposit=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,0,"예수금")
            possible=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,0,"출금가능금액")
            order_possible=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,0,"주문가능금액")

            self.user_money=int(deposit)*self.user_money_percent
            self.user_money=self.user_money/4

            print("예수금        :   ",int(deposit))
            print("출금가능금액   :   ",int(possible))
            print("주문가능금액   :   ",int(order_possible))

            self.detail_account_info_event_loop.exit()

        elif sRQName=="계좌평가잔고내역요청":
            total_buy_money = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, 0, "총매입금액")
            total_profit_rate=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,0,"총수익률(%)")
            print("총매입금액    :   ",int(total_buy_money))
            print("총수익률(%)   :   ",float(total_profit_rate))

            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=code.strip()[1:]

                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_nm=code_nm.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)
                possible_quantity=int(possible_quantity)

                acd=self.account_stock_dict[code]

                acd.update({"종목명":code_nm})
                acd.update({"보유수량":stock_quantity})
                acd.update({"매입가":buy_price})
                acd.update({"수익률(%)":learn_rate})
                acd.update({"현재가":current_price})
                acd.update({"매입금액":total_chegual_price})
                acd.update({"매매가능수량":possible_quantity})

                cnt+=1

            print("계좌 종목 :   ",self.account_stock_dict)
            print("계좌 종목 수 : ",cnt)

            if sPrevNext=="2":
                self.detail_account_mystock(sPreNext="2")      # If the next page exists, repeat the process
            else:
                self.detail_account_info_event_loop.exit()

        elif sRQName=="실시간미체결요청":
            rows=self.dynamicCall("GetRepeatCnt(QString,QString)",sTrCode,sRQName)
            for i in range(rows):
                code=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,i,"종목번호")
                code=code.strip()
                code_nm = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "종목명")
                order_no = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "주문번호")
                order_status = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "주문상태") # 접수, 확인, 체결
                order_quantity = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "주문수량")
                order_price = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "주문가격")
                order_gubun = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "주문구분") # 매도, 매수,
                not_quantity= self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "미체결수량")
                ok_quantity = self.dynamicCall("GetCommData(QString,QString,int,QString)", sTrCode, sRQName, i, "체결량")

                code_nm=code_nm.strip()
                order_no=int(order_no.strip())
                order_status=order_status.strip()
                order_quantity=int(order_quantity.strip())
                order_price=int(order_price.strip())
                order_gubun=order_gubun.lstrip("+").lstrip('-')
                not_quantity=int(not_quantity.strip())
                ok_quantity=int(ok_quantity.strip())

                if order_no in self.not_account_stock_dict:
                    pass
                else:
                    self.not_account_stock_dict[order_no]={}

                nasd=self.not_account_stock_dict[order_no]

                nasd.update({"종목코드":code})
                nasd.update({"주문명":code_nm})
                nasd.update({"주분번호":order_no})
                nasd.update({"주문상태":order_status})
                nasd.update({"주문수량":order_quantity})
                nasd.update({"주문가격":order_price})
                nasd.update({"주문구분":order_gubun})
                nasd.update({"미체결수량":not_quantity})
                nasd.update({"체결량":ok_quantity})

                print("미체결 종목",self.not_account_stock_dict[order_no])

            self.detail_account_info_event_loop.exit()

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

            cnt=self.dynamicCall("GetRepeatCnt(QString,QString)",sTrCode,sRQName)
            print("데이터 일수", cnt)

            # 한 번 조회시 600일치까지 조회 가능
            for i in range(cnt):
                data=[]

                current_price=self.dynamicCall("GetCommData(QString,QString,int,QString)",sTrCode,sRQName,i,"현재가") # 종가
                value=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,"일자")
                start_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(value.strip())
                data.append(trading_value.strip())
                data.append(date.strip())
                data.append(start_price.strip())
                data.append(high_price.strip())
                data.append(low_price.strip())

                self.calc_data.append(data.copy())

            print(len(self.calc_data))

            if sPrevNext=="2":
                self.day_kiwoom_db(code=code,sPrevNext=sPrevNext)
            else:
                self.calculator_event_loop.exit()

    # 종목 코드를 반환
    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")
        print("코스닥 갯수",len(code_list))

        for idx,code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)",self.screen_calculation_stock)
            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(TR_REQ_TIME_INTERVAL)

        self.dynamicCall("SetInputValue(QString,QString)","종목코드",code)
        self.dynamicCall("SetInputValue(QString,QString)","수정주가구분","1")
        if date!=None:
            self.dynamicCall("SetInputValue(QString,QString)","기준일자",date)
        self.dynamicCall("CommRqData(QString,QString,int,QString)","주식일봉차트조회","opt10081",sPrevNext,self.screen_calculation_stock)

        self.calculator_event_loop.exec_()


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