컴퓨터/Python

python pycharm program gargen

풍경소리^^ 2022. 11. 4. 13:38

__init__.py ====================

from ui.ui import *

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

        Ui_class()

if __name__ == "__main__":
    Main()

ui>ui.py ====================

from kiwoom.kiwoom import *

from PyQt5.QtWidgets import *
import sys

class Ui_class():
    def __init__(self):
        print("Ui_class 입니다.")

        self.app = QApplication(sys.argv) # ui 초기화 ['파이썬파일경로','추가할옵션','추가할옵션']

        self.kiwoom = Kiwoom()

        self.app.exec_() # 이벤트루프 실행된 상태에서는 다음 코드 실행 안됨

kiwoom>kiwoom.py ====================

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        ########################

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

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw0001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

kiwoom>kiwoom.py ====================42강 TR요청 4편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        ########################

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

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

kiwoom>kiwoom.py ====================43강 TR요청 5편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        ########################

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

        ####### 이벤트루프 모음
        self.detail_acount_info_event_loop = None
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()


    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()

kiwoom>kiwoom.py ====================44강 TR요청 6편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = None
        self.detail_acount_info_event_loop2 = None
        ########################

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

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print("계좌평가 잔고내역 요청")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, "2000")
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        self.detail_acount_info_event_loop2 = QEventLoop()
        self.detail_acount_info_event_loop2.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            self.detail_acount_info_event_loop2.exit()

kiwoom>kiwoom.py ====================45강 TR요청 7편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = None
        self.detail_acount_info_event_loop2 = None
        ########################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 변수 모음
        self.account_stock_dict = {}
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print("계좌평가 잔고내역 요청")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, "2000")
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        self.detail_acount_info_event_loop2 = QEventLoop()
        self.detail_acount_info_event_loop2.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, cnt, "종목번호")
                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, "매매가능수량")

                code_nm = code_nm.strip()
                stock_quantity = int(stock_quantity.strip())
                buy_price = int(buy_price.strip())
                learn_rate = float(learn_rate.strip)
                currrent_price = int(current_price.strip())
                total_chegual_price = int(total_chegual_price.strip())

            self.detail_acount_info_event_loop2.exit()

kiwoom>kiwoom.py ====================46강 TR요청 8편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = None
        self.detail_acount_info_event_loop_2 = QEventLoop()
        ########################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 변수 모음
        self.account_stock_dict = {}
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", "2000")
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, "2000")
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop_2.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop_2.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

kiwoom>kiwoom.py ====================47강 TR요청 9편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()

kiwoom>kiwoom.py ====================49강 TR요청 11편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        self.calculator_fnc() # 종목 분석용, 임시용으로 실행

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        elif "주식일봉차트조회" == sRQName:
            print("일봉데이터 요청")

    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

kiwoom>kiwoom.py ====================52강 TR요청 14편 ~ 53강 TR요청 15편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        self.calculator_fnc() # 종목 분석용, 임시용으로 실행

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"일봉데이터 요청 {code}")

            rows = self.dynamicCall("GetRepeatCnt(QString, QString")
            print(rows)

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


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

 

kiwoom>kiwoom.py ====================54강 TR요청 16편 ~ 55강 TR요청 17편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        #######################

        ####### 종목 분석용
        self.calcul_data = []
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        self.calculator_fnc() # 종목 분석용, 임시용으로 실행

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"일봉데이터 요청 {code}")

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

            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],

            # 한 번 조회하면 600일치까지 일봉데이터를 받을 수 있다.
            # KOA studio - TR 목록 - opt10081 주식일봉차트조회요청
            for i in range(cnt): # [0,...,599]
                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())
                data.append("")
                
                self.calcul_data.append(data.copy())
            
            # print(self.calcul_data)
            # print(len(self.calcul_data))

            if sPrevNext == "2":
                self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
            else:
                # 조건에 부합하는 종목 추출
                print(f"총 일수{len(self.calcul_data)}")

                pass_success = False
                # 120일 이평선을 그릴 만큼의 데이터가 있는지 체크
                if self.calcul_data == None or len(self.calcul_data) < 120:
                    pass_success = False

                else:
                    # 120일 이상이 되면
                    total_price = 0
                    for value in self.calcul_data[:120]: # [오늘, 하루전,...,119일전]
                        total_price += int(value[1]) # 현재가
                    
                    moving_average_price = total_price / 120 # 120일 이평선 오늘 평균가격

                    # 오늘자의 저가 7가 120일 이평선 오늘 평균가격보다 낮아야 된다 # 오늘자의 고가 6
                    if int(self.calcul_data[0][7]) <= moving_average_price and moving_average_price <= int(self.calcul_data[0][6]):
                        print("오늘 주가 120이평선에 걸쳐있는 것 확인")


                self.calculator_event_loop.exit()


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

kiwoom>kiwoom.py ====================56강 TR요청 18편

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        #######################

        ####### 종목 분석 용
        self.calcul_data = []
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        self.calculator_fnc() # 종목 분석용, 임시용으로 실행

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"{code} 일봉데이터 요청")

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

            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],

            # 한 번 조회하면 600일치까지 일봉데이터를 받을 수 있다.
            # KOA studio - TR 목록 - opt10081 주식일봉차트조회요청
            for i in range(cnt): # [0,...,599]
                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())
                data.append("")
                
                self.calcul_data.append(data.copy())
            
            # print(self.calcul_data)
            # print(len(self.calcul_data))

            if sPrevNext == "2": # 과거 데이터가 더 있다면
                self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
            else:
                # 조건에 부합하는 종목 추출
                print(f"총 일수 {len(self.calcul_data)}")

                pass_success = False
                # 120일 이평선을 그릴 만큼의 데이터가 있는지 체크
                if self.calcul_data == None or len(self.calcul_data) < 120:
                    pass_success = False

                else:
                    # 120일 이상이 되면
                    total_price = 0
                    for value in self.calcul_data[:120]: # [오늘, 하루전,...,119일전]
                        total_price += int(value[1]) # 현재가(종가)
                    
                    moving_average_price = total_price / 120 # 120일 이평선 오늘 평균가격
                    # moving_average_price = int(total_price / 120) # 120일 이평선 오늘 평균가격

                    # 오늘자의 저가 7가 120일 이평선 오늘 평균가격보다 낮아야 된다 # 오늘자의 고가 6
                    # 오늘자 주가가 120일 이평선에 걸쳐있는지 확인
                    bottom_stock_price = False
                    check_price = None
                    if int(self.calcul_data[0][7]) <= moving_average_price and moving_average_price <= int(self.calcul_data[0][6]):
                        print("오늘 주가 120이평선에 걸쳐있는 것 확인")
                        bottom_stock_price = True
                        check_price = int(self.calcul_data[0][6]) # 오늘의 고가
                    
                    # 과거 일봉들이 120일 이평선보다 밑에 있는지 확인
                    # 확인하다가 일봉이 120일 이평선보다 위에 있으면 계산 진행
                    prev_price = None # 과거의 일봉 저가
                    if bottom_stock_price == True:
                        moving_average_price_prev = 0 # 과거
                        price_top_moving = False

                        idx = 1
                        while True:
                            if len(self.calcul_data[idx:]) < 120: # 120일치가 있는지 계속 확인
                                print("120일치가 없음!")
                                break

                            total_price = 0
                            for value in self.calcul_data[idx:120+idx]:
                                total_price += int(value[1])
                            moving_average_price_prev = total_price / 120

                            # 고가가 이평선 위에 있을 경우
                            if moving_average_price_prev <= int(self.calcul_data[idx][6]) and idx <=20: # 이평선아래 있는 구간이 20일은 되게
                                print("20일 동안 주가가 120일 이평선과 같거나 위에 있으면 조건 통과 못함")
                                price_top_moving = False
                                break
                            elif int(self.calcul_data[idx][7]) > moving_average_price_prev and idx > 20: # 7 저가가 이평선보다 위에 있고 20일보다 많으면
                                print("120일 이평선 위에 있는 일봉 확인됨")
                                price_top_moving = True
                                prev_price = int(self.calcul_data[idx][7]) # 이 때의 저가 가격 확인
                                break

                            idx += 1
                        
                        # 해당 부분 이평선이 가장 최근 일자의 이평선 가격보다 낮은지 확인
                        if price_top_moving == True:
                            if moving_average_price > moving_average_price_prev and check_price > prev_price:
                                print("포착된 이평선의 가격이 오늘자(최근일자) 이평선 가격보다 낮은 것 확인됨")
                                print("포착된 부분의 일봉 저가가 오늘자 일봉의 고가보다 낮은지 확인됨")
                                pass_success = True

                if pass_success == True:
                    print("조건부 통과됨")

                    code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)

                    f = open("files/condition_stock.txt", "a", encoding="utf8")
                    f.write(f"{code}\t{code_nm}\t{str(self.calcul_data[0][1])}\n") # 1 현재가
                    f.close()

                elif pass_success == False:
                    print("조건부 통과 못함")
                
                self.calcul_data.clear()
                self.calculator_event_loop.exit()


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

코스닥 갯수 1604
1 / 1604 : KOSDAQ Stock Code: 900110 is updating...
일봉데이터 요청 900110
데이터 일수 600
QEventLoop::exec: instance 0x10e6c00 has already called exec()
일봉데이터 요청 900110
데이터 일수 600
QEventLoop::exec: instance 0x10e6c00 has already called exec()
일봉데이터 요청 900110
데이터 일수 600
QEventLoop::exec: instance 0x10e6c00 has already called exec()
일봉데이터 요청 900110
데이터 일수 600
QEventLoop::exec: instance 0x10e6c00 has already called exec()
일봉데이터 요청 900110
데이터 일수 600
QEventLoop::exec: instance 0x10e6c00 has already called exec()
일봉데이터 요청 900110
데이터 일수 98
총 일수 3098
조건부 통과 못함

kiwoom>kiwoom.py ====================58강 파일 종목 읽기, 종목별 스크린번호 할당하기

import os

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


class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        self.screen_real_stock = "5000" # 종목별로 할당할 스크린 번호
        self.screen_meme_stock = "6000" # 종목별 할당할 주문용 스크린 번호
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        self.portfolio_stock_dict = {}
        #######################

        ####### 종목 분석 용
        self.calcul_data = []
        #######################

        self.get_ocx_instance()
        self.event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        # self.calculator_fnc() # 종목 분석용, 임시용으로 실행
        self.read_code() # 저장된 종목들 불러오기
        self.screen_number_setting() # 스크린 번호 할당

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"{code} 일봉데이터 요청")

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

            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],

            # 한 번 조회하면 600일치까지 일봉데이터를 받을 수 있다.
            # KOA studio - TR 목록 - opt10081 주식일봉차트조회요청
            for i in range(cnt): # [0,...,599]
                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())
                data.append("")
                
                self.calcul_data.append(data.copy())
            
            # print(self.calcul_data)
            # print(len(self.calcul_data))

            if sPrevNext == "2": # 과거 데이터가 더 있다면
                self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
            else:
                # 조건에 부합하는 종목 추출
                print(f"총 일수 {len(self.calcul_data)}")

                pass_success = False
                # 120일 이평선을 그릴 만큼의 데이터가 있는지 체크
                if self.calcul_data == None or len(self.calcul_data) < 120:
                    pass_success = False

                else:
                    # 120일 이상이 되면
                    total_price = 0
                    for value in self.calcul_data[:120]: # [오늘, 하루전,...,119일전]
                        total_price += int(value[1]) # 현재가(종가)
                    
                    moving_average_price = total_price / 120 # 120일 이평선 오늘 평균가격
                    # moving_average_price = int(total_price / 120) # 120일 이평선 오늘 평균가격

                    # 오늘자의 저가 7가 120일 이평선 오늘 평균가격보다 낮아야 된다 # 오늘자의 고가 6
                    # 오늘자 주가가 120일 이평선에 걸쳐있는지 확인
                    bottom_stock_price = False
                    check_price = None
                    if int(self.calcul_data[0][7]) <= moving_average_price and moving_average_price <= int(self.calcul_data[0][6]):
                        print("오늘 주가 120이평선에 걸쳐있는 것 확인")
                        bottom_stock_price = True
                        check_price = int(self.calcul_data[0][6]) # 오늘의 고가
                    
                    # 과거 일봉들이 120일 이평선보다 밑에 있는지 확인
                    # 확인하다가 일봉이 120일 이평선보다 위에 있으면 계산 진행
                    prev_price = None # 과거의 일봉 저가
                    if bottom_stock_price == True:
                        moving_average_price_prev = 0 # 과거
                        price_top_moving = False

                        idx = 1
                        while True:
                            if len(self.calcul_data[idx:]) < 120: # 120일치가 있는지 계속 확인
                                print("120일치가 없음!")
                                break

                            total_price = 0
                            for value in self.calcul_data[idx:120+idx]:
                                total_price += int(value[1])
                            moving_average_price_prev = total_price / 120

                            # 고가가 이평선 위에 있을 경우
                            if moving_average_price_prev <= int(self.calcul_data[idx][6]) and idx <=20: # 이평선아래 있는 구간이 20일은 되게
                                print("20일 동안 주가가 120일 이평선과 같거나 위에 있으면 조건 통과 못함")
                                price_top_moving = False
                                break
                            elif int(self.calcul_data[idx][7]) > moving_average_price_prev and idx > 20: # 7 저가가 이평선보다 위에 있고 20일보다 많으면
                                print("120일 이평선 위에 있는 일봉 확인됨")
                                price_top_moving = True
                                prev_price = int(self.calcul_data[idx][7]) # 이 때의 저가 가격 확인
                                break

                            idx += 1
                        
                        # 해당 부분 이평선이 가장 최근 일자의 이평선 가격보다 낮은지 확인
                        if price_top_moving == True:
                            if moving_average_price > moving_average_price_prev and check_price > prev_price:
                                print("포착된 이평선의 가격이 오늘자(최근일자) 이평선 가격보다 낮은 것 확인됨")
                                print("포착된 부분의 일봉 저가가 오늘자 일봉의 고가보다 낮은지 확인됨")
                                pass_success = True

                if pass_success == True:
                    print("조건부 통과됨")

                    code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)

                    f = open("files/condition_stock.txt", "a", encoding="utf8")
                    f.write(f"{code}\t{code_nm}\t{str(self.calcul_data[0][1])}\n") # 1 현재가
                    f.close()

                elif pass_success == False:
                    print("조건부 통과 못함")
                
                self.calcul_data.clear()
                self.calculator_event_loop.exit()


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

    # 매수법칙 계산 들어가면 됨
    def read_code(self):
        if os.path.exists("files/condition_stock.txt"): # 존재하냐
            f = open("files/condition_stock.txt", "r", encoding="utf8")
            lines = f.readlines()
            for line in lines:
                if line != "":
                    ls = line.split("\t") # ["230923", "종목명", "현재가\n"]
                    stock_code = ls[0]
                    stock_name = ls[1]
                    stock_price = int(ls[2].split("\n")[0])
                    stock_price = abs(stock_price)

                    self.portfolio_stock_dict.update({stock_code:{"종목명":stock_name, "현재가":stock_price}}) # 딕셔너리 업데이트
                    # {"2090923":{"종목명":"삼성","현재가":"2000"}, "091092":{"종목명":"LG"...}}
            f.close()
            print(self.portfolio_stock_dict)
    
    def screen_number_setting(self):
        # 겹치는 종목 있는지 확인
        screen_overwrite = []
        
        # 계좌평가잔고내역에 있는 종목들
        for code in self.account_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 미체결에 있는 종목들
        for order_number in self.not_account_stock_dict.keys():
            code = self.not_account_stock_dict[order_number]['종목코드']

            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 포트폴리오에 담겨있는 종목들
        for code in self.portfolio_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 스크린번호 할당
        # 스크린번호 하나에 요청 갯수는 100개까지
        # 스크린번호는 200개까지 생성가능
        cnt = 0
        for code in screen_overwrite:
            temp_screen = int(self.screen_real_stock)
            meme_screen = int(self.screen_real_stock)

            if (cnt % 50) == 0:
                temp_screen += 1 # 스크린번호 하나당 50개까지만 할당하겠다 5000 → 5001
                self.screen_real_stock = str(temp_screen)
            
            if (cnt % 50) == 0:
                meme_screen += 1
                self.screen_meme_stock = str(meme_screen)

            if code in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict[code].update({"스크린번호": str(self.screen_real_stock)})
                self.portfolio_stock_dict[code].update({"주문용스크린번호": str(self.screen_meme_stock)})
            elif code not in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict.update({code: {"스크린번호":str(self.screen_real_stock), "주문용스크린번호":str(self.screen_meme_stock)}})
            cnt += 1
        print(self.portfolio_stock_dict)

kiwoom>kiwoom.py ====================59강 주식 종목 수천개를 빠르게 데이터 받기

import os

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        self.realType = RealType()

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        self.screen_real_stock = "5000" # 종목별로 할당할 스크린 번호
        self.screen_meme_stock = "6000" # 종목별 할당할 주문용 스크린 번호
        self.screen_start_stop_real = "1000" # 
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        self.portfolio_stock_dict = {}
        #######################

        ####### 종목 분석 용
        self.calcul_data = []
        #######################

        self.get_ocx_instance()
        self.event_slots()
        self.real_event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        # self.calculator_fnc() # 종목 분석용, 임시용으로 실행
        self.read_code() # 저장된 종목들 불러오기
        self.screen_number_setting() # 스크린 번호 할당
        # 장 시작 시간이냐 장 끝 시간이냐
        # KOA studio - 실시간 목록 - Real Type: 장시작시간 # FID 번호
        self.dynamicCall("SetRealReg(QString, QString, QString, QString)", self.screen_start_stop_real, "", self.realType.REALTYPE['장시작시간']['장운영구분'], "0")

        for code in self.portfolio_stock_dict.keys():
            screen_num = self.portfolio_stock_dict[code]['스크린번호']
            fids = self.realType.REALTYPE['주식체결']['체결시간'] # 틱발생시 - 체결시간 하나만 꺼내오라고 하면 전체 다 가져옴
            self.dynamicCall("SetRealReg(QString, QString, QString, QString)", screen_num, code, fids, "1") # 최초등록 0, 추가등록 1
            print(f"실시간 등록 코드 : {code}, 스크린번호 : {screen_num}, 번호: {fids}")

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_slot)
    
    def real_event_slots(self):
        # KOA studio - 개발가이드 - 조회와 실시간데이터처리 - 관련함수
        self.OnReceiveRealData.connect(self.realdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"{code} 일봉데이터 요청")

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

            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],

            # 한 번 조회하면 600일치까지 일봉데이터를 받을 수 있다.
            # KOA studio - TR 목록 - opt10081 주식일봉차트조회요청
            for i in range(cnt): # [0,...,599]
                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())
                data.append("")
                
                self.calcul_data.append(data.copy())
            
            # print(self.calcul_data)
            # print(len(self.calcul_data))

            if sPrevNext == "2": # 과거 데이터가 더 있다면
                self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
            else:
                # 조건에 부합하는 종목 추출
                print(f"총 일수 {len(self.calcul_data)}")

                pass_success = False
                # 120일 이평선을 그릴 만큼의 데이터가 있는지 체크
                if self.calcul_data == None or len(self.calcul_data) < 120:
                    pass_success = False

                else:
                    # 120일 이상이 되면
                    total_price = 0
                    for value in self.calcul_data[:120]: # [오늘, 하루전,...,119일전]
                        total_price += int(value[1]) # 현재가(종가)
                    
                    moving_average_price = total_price / 120 # 120일 이평선 오늘 평균가격
                    # moving_average_price = int(total_price / 120) # 120일 이평선 오늘 평균가격

                    # 오늘자의 저가 7가 120일 이평선 오늘 평균가격보다 낮아야 된다 # 오늘자의 고가 6
                    # 오늘자 주가가 120일 이평선에 걸쳐있는지 확인
                    bottom_stock_price = False
                    check_price = None
                    if int(self.calcul_data[0][7]) <= moving_average_price and moving_average_price <= int(self.calcul_data[0][6]):
                        print("오늘 주가 120이평선에 걸쳐있는 것 확인")
                        bottom_stock_price = True
                        check_price = int(self.calcul_data[0][6]) # 오늘의 고가
                    
                    # 과거 일봉들이 120일 이평선보다 밑에 있는지 확인
                    # 확인하다가 일봉이 120일 이평선보다 위에 있으면 계산 진행
                    prev_price = None # 과거의 일봉 저가
                    if bottom_stock_price == True:
                        moving_average_price_prev = 0 # 과거
                        price_top_moving = False

                        idx = 1
                        while True:
                            if len(self.calcul_data[idx:]) < 120: # 120일치가 있는지 계속 확인
                                print("120일치가 없음!")
                                break

                            total_price = 0
                            for value in self.calcul_data[idx:120+idx]:
                                total_price += int(value[1])
                            moving_average_price_prev = total_price / 120

                            # 고가가 이평선 위에 있을 경우
                            if moving_average_price_prev <= int(self.calcul_data[idx][6]) and idx <=20: # 이평선아래 있는 구간이 20일은 되게
                                print("20일 동안 주가가 120일 이평선과 같거나 위에 있으면 조건 통과 못함")
                                price_top_moving = False
                                break
                            elif int(self.calcul_data[idx][7]) > moving_average_price_prev and idx > 20: # 7 저가가 이평선보다 위에 있고 20일보다 많으면
                                print("120일 이평선 위에 있는 일봉 확인됨")
                                price_top_moving = True
                                prev_price = int(self.calcul_data[idx][7]) # 이 때의 저가 가격 확인
                                break

                            idx += 1
                        
                        # 해당 부분 이평선이 가장 최근 일자의 이평선 가격보다 낮은지 확인
                        if price_top_moving == True:
                            if moving_average_price > moving_average_price_prev and check_price > prev_price:
                                print("포착된 이평선의 가격이 오늘자(최근일자) 이평선 가격보다 낮은 것 확인됨")
                                print("포착된 부분의 일봉 저가가 오늘자 일봉의 고가보다 낮은지 확인됨")
                                pass_success = True

                if pass_success == True:
                    print("조건부 통과됨")

                    code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)

                    f = open("files/condition_stock.txt", "a", encoding="utf8")
                    f.write(f"{code}\t{code_nm}\t{str(self.calcul_data[0][1])}\n") # 1 현재가
                    f.close()

                elif pass_success == False:
                    print("조건부 통과 못함")
                
                self.calcul_data.clear()
                self.calculator_event_loop.exit()


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

    # 매수법칙 계산 들어가면 됨
    def read_code(self):
        if os.path.exists("files/condition_stock.txt"): # 존재하냐
            f = open("files/condition_stock.txt", "r", encoding="utf8")
            lines = f.readlines()
            for line in lines:
                if line != "":
                    ls = line.split("\t") # ["230923", "종목명", "현재가\n"]
                    stock_code = ls[0]
                    stock_name = ls[1]
                    stock_price = int(ls[2].split("\n")[0])
                    stock_price = abs(stock_price)

                    self.portfolio_stock_dict.update({stock_code:{"종목명":stock_name, "현재가":stock_price}}) # 딕셔너리 업데이트
                    # {"2090923":{"종목명":"삼성","현재가":"2000"}, "091092":{"종목명":"LG"...}}
            f.close()
            print(self.portfolio_stock_dict)
    
    def screen_number_setting(self):
        # 겹치는 종목 있는지 확인
        screen_overwrite = []
        
        # 계좌평가잔고내역에 있는 종목들
        for code in self.account_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 미체결에 있는 종목들
        for order_number in self.not_account_stock_dict.keys():
            code = self.not_account_stock_dict[order_number]['종목코드']

            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 포트폴리오에 담겨있는 종목들
        for code in self.portfolio_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 스크린번호 할당
        # 스크린번호 하나에 요청 갯수는 100개까지
        # 스크린번호는 200개까지 생성가능
        cnt = 0
        for code in screen_overwrite:
            temp_screen = int(self.screen_real_stock)
            meme_screen = int(self.screen_real_stock)

            if (cnt % 50) == 0:
                temp_screen += 1 # 스크린번호 하나당 50개까지만 할당하겠다 5000 → 5001
                self.screen_real_stock = str(temp_screen)
            
            if (cnt % 50) == 0:
                meme_screen += 1
                self.screen_meme_stock = str(meme_screen)

            if code in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict[code].update({"스크린번호": str(self.screen_real_stock)})
                self.portfolio_stock_dict[code].update({"주문용스크린번호": str(self.screen_meme_stock)})

            elif code not in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict.update({code: {"스크린번호":str(self.screen_real_stock), "주문용스크린번호":str(self.screen_meme_stock)}})
            cnt += 1
        print(self.portfolio_stock_dict)

    def realdata_slot(self, sCode, sRealType, sRealData):
        # print(sCode)
        if sRealType == "장시작시간": # KOA studio - 실시간목록 - Real Type:장시작시간
            fid = self.realType.REALTYPE[sRealType]['장운영구분']
            # KOA studio - 개발가이드 - 조회와 실시간데이터처리 - 관련함수
            value = self.dynamicCall("GetCommRealData(QString, int)", sCode, fid)

            if value == '0':
                print("장 시작 전")
            elif value == '3':
                print("장 시작")
            elif value == '2':
                print("장 종료, 동시호가로 넘어감")
            elif value == '4':
                print("3시30분 장 종료")
            elif sRealType == "주식체결":
                print(sCode)

kiwoom>kiwoom.py ====================60강 틱봉마다 데이터 업데이트

import os

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

class Kiwoom(QAxWidget):
    def __init__(self):
        super().__init__() # 부모의 초기값을 그대로 쓰겠다

        print("Kiwoom 클래스 입니다.")

        self.realType = RealType()

        ####### eventloop 모음
        self.login_event_loop = None
        self.detail_acount_info_event_loop = QEventLoop()
        # self.detail_acount_info_event_loop_2 = QEventLoop()
        self.calculator_event_loop = QEventLoop()
        ########################

        ####### 스크린번호 모음
        self.screen_my_info = "2000"
        self.sereen_calculation_stock = "4000"
        self.screen_real_stock = "5000" # 종목별로 할당할 스크린 번호
        self.screen_meme_stock = "6000" # 종목별 할당할 주문용 스크린 번호
        self.screen_start_stop_real = "1000" # 
        #######################

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

        ####### 계좌 관련 변수
        self.use_money = 0
        self.use_money_percent = 0.5
        #######################

        ####### 딕션너리 모음
        self.account_stock_dict = {}
        self.not_account_stock_dict = {}
        self.portfolio_stock_dict = {}
        #######################

        ####### 종목 분석 용
        self.calcul_data = []
        #######################

        self.get_ocx_instance()
        self.event_slots()
        self.real_event_slots()

        self.signal_login_commConnect()
        self.get_account_info() # 계좌번호 받아오기
        self.detail_account_info() # 예수금 가져오는 부분
        self.detail_account_mystock() # 계좌평가 잔고내역 요청
        self.not_concluded_account() # 미체결 요청

        # 실시간
        # self.calculator_fnc() # 종목 분석용, 임시용으로 실행
        self.read_code() # 저장된 종목들 불러오기
        self.screen_number_setting() # 스크린 번호 할당
        # 장 시작 시간이냐 장 끝 시간이냐
        # KOA studio - 실시간 목록 - Real Type: 장시작시간 # FID 번호
        self.dynamicCall("SetRealReg(QString, QString, QString, QString)", self.screen_start_stop_real, "", self.realType.REALTYPE['장시작시간']['장운영구분'], "0")

        for code in self.portfolio_stock_dict.keys():
            screen_num = self.portfolio_stock_dict[code]['스크린번호']
            fids = self.realType.REALTYPE['주식체결']['체결시간'] # 틱발생시 - 체결시간 하나만 꺼내오라고 하면 전체 다 가져옴
            self.dynamicCall("SetRealReg(QString, QString, QString, QString)", screen_num, code, fids, "1") # 최초등록 0, 추가등록 1
            print(f"실시간 등록 코드 : {code}, 스크린번호 : {screen_num}, 번호: {fids}")

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

    def event_slots(self):
        self.OnEventConnect.connect(self.login_slot) # 로그인용 이벤트
        # TR용 이벤트 - KOA 스튜디오 - 개발가이드 - 조회와실시간데이터처리 - 관련함수
        self.OnReceiveTrData.connect(self.trdata_slot)
    
    def real_event_slots(self):
        # KOA studio - 개발가이드 - 조회와 실시간데이터처리 - 관련함수
        self.OnReceiveRealData.connect(self.realdata_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") # 계좌번호 확인해보자
        # account_list = self.dynamicCall("GetLoginInfo(String)", "USER_ID") # 계좌번호 확인해보자

        self.account_num = account_list.split(';')[0]
        print(f"나의 보유 계좌번호 {self.account_num}")

    def detail_account_info(self): # 예수금 가져오는 부분 ######## signal로 요청하고
        print("예수금 요청하는 부분")
        # KOA 스튜디오 - TR목록 - 예수금상세 - 다음 버튼
        # SetInputValue 인적사항 작성해서 signal로 정보 요청 CommRqData
        # - 이벤트 OnReceiveTrData에 의해 slot 결과값 GetCommData 가져옴
        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        self.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000")
        self.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
        self.dynamicCall("SetInputValue(String, String)", "조회구분", "2") # 일반조회
        # 예수금 상세 조회 요청 CommRqData
        # ("내가 지은 요청이름", "TR번호", "preNext", "화면번호") - 스크린번호 - 그룹 - 항목100개로 200개그룹까지 가능
        self.dynamicCall("CommRqData(String, String, int, String)", "예수금상세현황요청", "opw00001", "0", self.screen_my_info)
        # 참고 설명
        # ("DisconnectRealData(QString)","2000") 스크린번호 취소 가능 - 안에 있는 내용물들 다 날라감
        # ("SetRealRemove(QString, QString)","2001","종목101") 스크린번호 안에 있는 종목 하나 취소가능 - 지정한 종목만 날라감

        # 데이터 처리하는 동안 다른 작업을 할 수 있게 하는 것 - 이벤트루프
        # 예수금 요청을 하는 동안에 결과 값을 다 받고
        # 이벤트루프 실행하는 것을 직접 종료해 주지 않으면
        # 이 다음 코드가 실행이 안된다.
        # 이 사이에서 이벤트루프를 실행해 주어야 된다
        self.detail_acount_info_event_loop = QEventLoop()
        self.detail_acount_info_event_loop.exec_()

    def detail_account_mystock(self, sPrevNext="0"):
        print(f"계좌평가 잔고내역 요청하기 연속조회 {sPrevNext}")

        self.dynamicCall("SetInputValue(String, String)", "계좌번호", self.account_num)
        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)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.screen_my_info)
        # KOA studio - TR 목록 - 계좌평가 잔고내역 - 영웅문0391
        
        self.detail_acount_info_event_loop.exec_()

    def not_concluded_account(self, sPrevNext="0"): # 미체결
        print("미체결 요청")

        self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
        # self.dynamicCall("SetInputValue(QString, QString)", "전체종목구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "매매구분", "0")
        self.dynamicCall("SetInputValue(QString, QString)", "체결구분", "1") # 1 미체결
        self.dynamicCall("CommRqData(String, String, int, String)", "실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)

        self.detail_acount_info_event_loop.exec_()

    def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext): # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - OnReceiveTrData
        '''
        TR 요청을 받는 구역이다! 슬롯이다!
        :param sScrNo: 스크린번호
        :param sRQName: 내가 요청했을 때 지은 이름
        :param sTrCode: 요청id, TR코드
        :param sRecordName: 사용 안함
        :param sPrevNext: 다음 페이지가 있는지
        :return:
        '''
        # 다음 페이지 없으면 - sPrevNext : "0", "" 으로 나온다
        ######## TR로 받아서

        if sRQName == "예수금상세현황요청":
            # KOA 스튜디오 - 개발가이드 - 조회와 실시간데이터처리 - GetCommData ######## 해당 함수로 꺼내왔다GetCommData
            deposit = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "예수금")
            print(f"예수금 {int(deposit)}")

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

            # 데이터 다 받았으니까
            self.detail_acount_info_event_loop.exit()
        
        elif sRQName == "계좌평가잔고내역요청":
            # 총매입금액
            total_buy_money = self.dynamicCall("GetCommData(String, String, int, String", sTrCode, sRQName, 0, "총매입금액")
            # string형태로 나온다
            total_buy_money_result = int(total_buy_money)

            print(f"총매입금액 {total_buy_money_result}")

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

            print(f"총수익률(%) {total_profit_loss_rate_result}")

            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
            cnt = 0
            for i in range(rows):
                # KOA studio - TR목록 - 계좌평가잔고내역
                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()
                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())

                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})
                # {"02039":{"종목명":"어디", "보유수량":1}}
                cnt += 1
            print(f"계좌에 가지고 있는 종목 {self.account_stock_dict} ")
            print(f"계좌에 보유 종목 카운트 {cnt} ")

            # 다음 페이지 있으면 - 계좌평가잔고내역 시그널을 또 보내줘야 된다
            if sPrevNext == "2":
                self.detail_account_mystock(sPrevNext="2")
            else:
                self.detail_acount_info_event_loop.exit() # 더 이상 조회할게 없으니까 연결을 끊어준거다

        elif sRQName == "실시간미체결요청":
            # KOA studio - 개발가이드 - 조회와실시간데이터처리 - GETRepeatCnt
            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_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 = code.strip()
                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.strip().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})
                self.not_account_stock_dict[order_no].update({"체결량": ok_quantity})

                print(f"미체결 종목 : {self.not_account_stock_dict[order_no]}")
            
            self.detail_acount_info_event_loop.exit()
        
        if sRQName == "주식일봉차트조회":
            code = self.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()
            print(f"{code} 일봉데이터 요청")

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

            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''],

            # 한 번 조회하면 600일치까지 일봉데이터를 받을 수 있다.
            # KOA studio - TR 목록 - opt10081 주식일봉차트조회요청
            for i in range(cnt): # [0,...,599]
                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())
                data.append("")
                
                self.calcul_data.append(data.copy())
            
            # print(self.calcul_data)
            # print(len(self.calcul_data))

            if sPrevNext == "2": # 과거 데이터가 더 있다면
                self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
            else:
                # 조건에 부합하는 종목 추출
                print(f"총 일수 {len(self.calcul_data)}")

                pass_success = False
                # 120일 이평선을 그릴 만큼의 데이터가 있는지 체크
                if self.calcul_data == None or len(self.calcul_data) < 120:
                    pass_success = False

                else:
                    # 120일 이상이 되면
                    total_price = 0
                    for value in self.calcul_data[:120]: # [오늘, 하루전,...,119일전]
                        total_price += int(value[1]) # 현재가(종가)
                    
                    moving_average_price = total_price / 120 # 120일 이평선 오늘 평균가격
                    # moving_average_price = int(total_price / 120) # 120일 이평선 오늘 평균가격

                    # 오늘자의 저가 7가 120일 이평선 오늘 평균가격보다 낮아야 된다 # 오늘자의 고가 6
                    # 오늘자 주가가 120일 이평선에 걸쳐있는지 확인
                    bottom_stock_price = False
                    check_price = None
                    if int(self.calcul_data[0][7]) <= moving_average_price and moving_average_price <= int(self.calcul_data[0][6]):
                        print("오늘 주가 120이평선에 걸쳐있는 것 확인")
                        bottom_stock_price = True
                        check_price = int(self.calcul_data[0][6]) # 오늘의 고가
                    
                    # 과거 일봉들이 120일 이평선보다 밑에 있는지 확인
                    # 확인하다가 일봉이 120일 이평선보다 위에 있으면 계산 진행
                    prev_price = None # 과거의 일봉 저가
                    if bottom_stock_price == True:
                        moving_average_price_prev = 0 # 과거
                        price_top_moving = False

                        idx = 1
                        while True:
                            if len(self.calcul_data[idx:]) < 120: # 120일치가 있는지 계속 확인
                                print("120일치가 없음!")
                                break

                            total_price = 0
                            for value in self.calcul_data[idx:120+idx]:
                                total_price += int(value[1])
                            moving_average_price_prev = total_price / 120

                            # 고가가 이평선 위에 있을 경우
                            if moving_average_price_prev <= int(self.calcul_data[idx][6]) and idx <=20: # 이평선아래 있는 구간이 20일은 되게
                                print("20일 동안 주가가 120일 이평선과 같거나 위에 있으면 조건 통과 못함")
                                price_top_moving = False
                                break
                            elif int(self.calcul_data[idx][7]) > moving_average_price_prev and idx > 20: # 7 저가가 이평선보다 위에 있고 20일보다 많으면
                                print("120일 이평선 위에 있는 일봉 확인됨")
                                price_top_moving = True
                                prev_price = int(self.calcul_data[idx][7]) # 이 때의 저가 가격 확인
                                break

                            idx += 1
                        
                        # 해당 부분 이평선이 가장 최근 일자의 이평선 가격보다 낮은지 확인
                        if price_top_moving == True:
                            if moving_average_price > moving_average_price_prev and check_price > prev_price:
                                print("포착된 이평선의 가격이 오늘자(최근일자) 이평선 가격보다 낮은 것 확인됨")
                                print("포착된 부분의 일봉 저가가 오늘자 일봉의 고가보다 낮은지 확인됨")
                                pass_success = True

                if pass_success == True:
                    print("조건부 통과됨")

                    code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)

                    f = open("files/condition_stock.txt", "a", encoding="utf8")
                    f.write(f"{code}\t{code_nm}\t{str(self.calcul_data[0][1])}\n") # 1 현재가
                    f.close()

                elif pass_success == False:
                    print("조건부 통과 못함")
                
                self.calcul_data.clear()
                self.calculator_event_loop.exit()


    # KOA studio - 개발가이드 - 기타함수 - 종목정보관련함수 - GetCodeListByMarket
    def get_code_list_by_market(self, market_code):
        '''
        종목 코드들 반환
        :param market_code:
        :return:
        '''
        code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
        # "109012;2394823;293481;092834;" → ["11912", "@349023", ""]
        code_list = code_list.split(";")[:-1]
        return code_list

    def calculator_fnc(self):
        '''
        종목 분석 실행용 함수
        :return:
        '''
        code_list = self.get_code_list_by_market("10")
        print(f"코스닥 갯수 {len(code_list)}")

        for idx, code in enumerate(code_list):
            self.dynamicCall("DisconnectRealData(QString)", self.sereen_calculation_stock)
            print(f"{idx+1} / {len(code_list)} : KOSDAQ Stock Code: {code} is updating...")
            self.day_kiwoom_db(code=code)

    def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
        # 시간 텀을 주자
        QTest.qWait(3600) # 3초 딜레이
        self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
        self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")

        if date != None:
            self.dynamicCall("SetInpuValue(QString, QString)", "기준일자", date)
        self.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", sPrevNext, self.sereen_calculation_stock)

        self.calculator_event_loop.exec_()

    # 매수법칙 계산 들어가면 됨
    def read_code(self):
        if os.path.exists("files/condition_stock.txt"): # 존재하냐
            f = open("files/condition_stock.txt", "r", encoding="utf8")
            lines = f.readlines()
            for line in lines:
                if line != "":
                    ls = line.split("\t") # ["230923", "종목명", "현재가\n"]
                    stock_code = ls[0]
                    stock_name = ls[1]
                    stock_price = int(ls[2].split("\n")[0])
                    stock_price = abs(stock_price)

                    self.portfolio_stock_dict.update({stock_code:{"종목명":stock_name, "현재가":stock_price}}) # 딕셔너리 업데이트
                    # {"2090923":{"종목명":"삼성","현재가":"2000"}, "091092":{"종목명":"LG"...}}
            f.close()
            print(self.portfolio_stock_dict)
    
    def screen_number_setting(self):
        # 겹치는 종목 있는지 확인
        screen_overwrite = []
        
        # 계좌평가잔고내역에 있는 종목들
        for code in self.account_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 미체결에 있는 종목들
        for order_number in self.not_account_stock_dict.keys():
            code = self.not_account_stock_dict[order_number]['종목코드']

            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 포트폴리오에 담겨있는 종목들
        for code in self.portfolio_stock_dict.keys():
            if code not in screen_overwrite:
                screen_overwrite.append(code)

        # 스크린번호 할당
        # 스크린번호 하나에 요청 갯수는 100개까지
        # 스크린번호는 200개까지 생성가능
        cnt = 0
        for code in screen_overwrite:
            temp_screen = int(self.screen_real_stock)
            meme_screen = int(self.screen_real_stock)

            if (cnt % 50) == 0:
                temp_screen += 1 # 스크린번호 하나당 50개까지만 할당하겠다 5000 → 5001
                self.screen_real_stock = str(temp_screen)
            
            if (cnt % 50) == 0:
                meme_screen += 1
                self.screen_meme_stock = str(meme_screen)

            if code in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict[code].update({"스크린번호": str(self.screen_real_stock)})
                self.portfolio_stock_dict[code].update({"주문용스크린번호": str(self.screen_meme_stock)})

            elif code not in self.portfolio_stock_dict.keys():
                self.portfolio_stock_dict.update({code: {"스크린번호":str(self.screen_real_stock), "주문용스크린번호":str(self.screen_meme_stock)}})
            cnt += 1
        print(self.portfolio_stock_dict)

    def realdata_slot(self, sCode, sRealType, sRealData):
        # print(sCode)
        if sRealType == "장시작시간": # KOA studio - 실시간목록 - Real Type:장시작시간
            fid = self.realType.REALTYPE[sRealType]['장운영구분']
            # KOA studio - 개발가이드 - 조회와 실시간데이터처리 - 관련함수
            value = self.dynamicCall("GetCommRealData(QString, int)", sCode, fid)

            if value == '0':
                print("장 시작 전")
            elif value == '3':
                print("장 시작")
            elif value == '2':
                print("장 종료, 동시호가로 넘어감")
            elif value == '4':
                print("3시30분 장 종료")
        elif sRealType == "주식체결":
            a = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["체결시간"]) # HHMMSS
            b = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["현재가"]) # +(-) 2500
            b = abs(int(b))
            c = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["전일대비"]) # +(-) 50
            c = abs(int(c))
            d = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["등락율"]) # +(-) 12.98
            d = float(d)
            e = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["(최우선)매도호가"]) # +(-) 50
            e = abs(int(e))
            f = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["(최우선)매수호가"]) # +(-) 50
            f = abs(int(f))
            g = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["거래량"]) # +(-) 50
            g = abs(int(g))
            h = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["누적거래량"]) # +(-) 50
            h = abs(int(h))
            i = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["고가"]) # +(-) 50
            i = abs(int(i))
            j = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["시가"]) # +(-) 50
            j = abs(int(j))
            k = self.dynamicCall("GetCommRealData(QString, int)", sCode, self.realType.REALTYPE[sRealType]["저가"]) # +(-) 50
            k = abs(int(k))

            if sCode not in self.portfolio_stock_dict:
                self.portfolio_stock_dict.update({sCode:{}})

            self.portfolio_stock_dict[sCode].update({"체결시간": a})
            self.portfolio_stock_dict[sCode].update({"현재가": b})
            self.portfolio_stock_dict[sCode].update({"전일대비": c})
            self.portfolio_stock_dict[sCode].update({"등락율": d})
            self.portfolio_stock_dict[sCode].update({"(최우선)매도호가": e})
            self.portfolio_stock_dict[sCode].update({"(최우선)매수호가": f})
            self.portfolio_stock_dict[sCode].update({"거래량": g})
            self.portfolio_stock_dict[sCode].update({"누적거래량": h})
            self.portfolio_stock_dict[sCode].update({"고가": i})
            self.portfolio_stock_dict[sCode].update({"시가": j})
            self.portfolio_stock_dict[sCode].update({"저가": k})

            print(self.portfolio_stock_dict[sCode])

kiwoom>kiwoom.py ====================

kiwoom>kiwoom.py ====================

kiwoom>kiwoom.py ====================

kiwoom>kiwoom.py ====================