PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 알고리즘 구축하기 (2) - 조건검색식 사용하기 ②

  • -

이번 게시글에서는 영웅문 상에서 제작한 조건검색식을 기반으로, 조건검색식에 의해 집계된 종목 데이터를 조회하는 기능을 구현할 계획이다.

 

 

조건검색식 설정하기

키움증권 사에서 제공하는 홈 트레이딩 시스템(HTS, Home Trading System) 내에서 화면번호 [0151]로 분류되는 조건검색식 내에서는 여러 가지 조건들을 설정해서 당일당일 해당 조건에 부합하는 종목을 선별해낼 수 있다. 거래량부터 거래대금, 이동평균선의 배열, 전일 대비 거래량 증가율 등등 키움증권에서 제공하는 조건 안에서는 어떤 조건이든 사용할 수 있다. 다만 이 게시글 내에서 확인하고자 하는 바는 조건검색식 상에서 집계된 종목의 데이터를 얻어오는 것이니 자세한 조건식 설정 방법은 차치하고, 아래와 같이 간단한 조건 검색식만 만들어서 기능을 확인해보도록 하자.

  • A : [골든 크로스] 10일 이동평균선이 20일 이동평균선을
  • D : [골든 크로스] 5일 이동평균선이 10일 이동평균선을
  • B : 거래량 10,000,000주 이상
  • C : 거래대금 500억 이상

조건식을 자세히 보면   (A or D) and B and C  라고 적혀있는데, 이는 A와 D 둘 중에 하나의 조건이 들어맞는 동시에 B와 C가 충족되는 종목을 찾으라는 의미이다.   or  과   and  등의 조건은   (A or D) and B and C  우측에 있는   ▼ ! () (X) X  를 클릭해서 변경할 수 있으며, 맨 오른쪽의 물음표를 누르면 이용방법에 대한 설명을 확인할 수 있다. 이 조건 하에 검색된 종목은   까뮤이앤씨[013700]  와   세종메디칼[258830]  두 종목이다. 이제 이 데이터를 가지고 조건검색식을 조회했을 때 두 종목의 종목 코드를 제대로 가져오는지 한 번 살펴보도록 하자.

 

 


728x90

 

 


조건검색식 조회 ① : GetConditionLoad()

  GetConditionLoad()  는 단순하게 로그인된 계정에 등록되어 있는 조건검색식 데이터를 불러오는 절차에 해당하며 이 함수는   OnReceiveConditionVer  라는 이벤트를 발생시키게 된다. 이벤트 이름만 봐도 알 수 있듯이, 조건검색식의 버전 처리에 해당하는 함수라고 봐도 무방하다. 
※ Line : 2~3

## GetConditionLoad 함수 실행하기
def __getconditionload(self):
	self.kiwoom.dynamicCall("GetConditionLoad()")

 

 

조건검색식 조회 ② : OnReceiveConditionVer()

이제   GetConditionVer()   함수를 실행했다면   OnReceiveConditionVer   이벤트가 발생하게 되는데, 그 이벤트를 처리해주면 된다. 이벤트의 처리 방법은 이전에 처리했던   OnReceiveTrData   OnReceiveRealData   등과 동일한 메커니즘을 가진다.

다만   OnReceiveConditionVer   이벤트에서는 반드시 주의해야 하는 점이 있다면   GetConditionNameList()   함수를 반드시   OnReceiveConditionVer   내에서 처리해주어야 한다는 것이다. 아래의   SendCondition()  에서도 살펴보겠지만, 특정 조건검색식에 의해 집계된 종목 데이터를 얻어오기 위해서는 조건검색식명과 인덱스 번호를 전달해줘야 하는데, 여기서 필요한 조건검색식명과 인덱스 번호를   GetConditionNameList()   함수를 통해 얻어오게 된다.
※ 여기서 조건검색식별 인덱스 번호는 경험상 특정 조건에 의해 부여되는 것 같긴 한데, 정확한 기준은 알 수 없다.
※ Line : 23, 28, 29

class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        self.setrealreg = _df.setrealreg()
        print(self.setrealreg)

        """데이터 요청 구간"""
        self.pushButton.clicked.connect(self.request_opt10016)
        self.pushButton_2.clicked.connect(self.request_opt10080)
        self.pushButton_3.clicked.connect(self._pushbtn_3)
        self.pushButton_4.clicked.connect(self._pushbtn_4)

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)        ## 데이터 조회 요청 처리 함수
        self.kiwoom.OnEventConnect.connect(self.process_login)          ## 로그인 반환값 처리 함수
        self.kiwoom.OnReceiveRealData.connect(self.receive_realdata)    ## 실시간 데이터 수신 처리 함수
        self.kiwoom.OnReceiveConditionVer.connect(self.OnReceiveConditionVer)


    ## OnRecieveConditionVer 처리하기
    ## GetConditionNameList() 함수 실행하기
    def OnReceiveConditionVer(self):
        condition_list = self.kiwoom.dynamicCall("GetConditionNameList()").split(";")

    ## GetConditionLoad 함수 실행하기
    def __getconditionload(self):
        self.kiwoom.dynamicCall("GetConditionLoad()")

 

그렇다면 여기서,   OnReceiveConditionVer   이벤트의 발생으로 실행되는   def OnReceiveConditionVer()   함수 내에서   GetConditionNameList()   함수를 통해 조건검색식 별 조건검색식명과 인덱스 번호를   condition_list   변수에 입력했으니 해당 변수를 가공해서 조건검색식 별 이름과 인덱스를 정리한 데이터를 생성해주어야 한다. 

  GetConditionNameList()   함수를 통해 condition_list 변수에 조건검색식 관련 데이터를 입력했는데, 그 데이터 형식은 ['인덱스번호^조건검색식명' , '인덱스번호^조건검색식명', '인덱스번호^조건검색식명', '']과 같이 생성된다. 즉 데이터가 리스트 형태로 전달되기 때문에 인덱싱을 통해 각각의 데이터에 접근할 수 있도록 하였으며 각각의 데이터는 조건검색식별로 부여된 인덱스번호와 조건검색식의 이름이 특수문자 ^를 기준으로 좌우에 위치해있다는 것이다.

따라서 우리는 for 문을 통해   condition_list   내에 입력된 데이터를 각각 인덱스 번호와 조건검색식 명으로 구분하여 데이터를 가공해주어야 한다. 일단 2번 줄에서   self.condition_list   변수를 생성해서 조건검색식과 관련된 데이터를 입력할 수 있도록 한 후에, for문 내에서   self.condition_list  에 데이터를 입력하도록 하면 되는 것이다.
※ Line : 6, 35~41

class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정
        self.condition_list = {'index':[], 'name':[]}

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        self.setrealreg = _df.setrealreg()
        print(self.setrealreg)

        """데이터 요청 구간"""
        self.pushButton.clicked.connect(self.request_opt10016)
        self.pushButton_2.clicked.connect(self.request_opt10080)
        self.pushButton_3.clicked.connect(self._pushbtn_3)
        self.pushButton_4.clicked.connect(self._pushbtn_4)

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)        ## 데이터 조회 요청 처리 함수
        self.kiwoom.OnEventConnect.connect(self.process_login)          ## 로그인 반환값 처리 함수
        self.kiwoom.OnReceiveRealData.connect(self.receive_realdata)    ## 실시간 데이터 수신 처리 함수
        self.kiwoom.OnReceiveConditionVer.connect(self.OnReceiveConditionVer)


    """조건검색식 저장"""
    def __getconditionload(self):
        self.kiwoom.dynamicCall("GetConditionLoad()")

    """OnReceiveConditionVer 이벤트 처리"""
    def OnReceiveConditionVer(self):
        condition_list = self.kiwoom.dynamicCall("GetConditionNameList()").split(";")

        for data in temporary_condition_list:
            try:
                a = data.split("^")
                self.condition_list['index'].append(str(a[0]))
                self.condition_list['name'].append(str(a[1]))
            except IndexError:
                pass

위와 같은 절차를 거친 후   self.condition_list  변수를 확인해보면 {'index':['인덱스번호', '인덱스번호', '인덱스번호', 'name':['조건검색식명', '조건검색식명', '조건검색식명']}과 같은 형태로 출력될 것이다. 이제 우리는 이 인덱스번호와 조건검색식명을 아래의   SendCondition()   함수의 인자로 전달함으로써 해당 조건검색식이 찾은 종목코드들을 조회할 수 있다.

Line 36Line 40 부분에서 try: except: 문을 통해 IndexError의 예외 처리를 해주고 있는데, 이 오류를 처리하는 이유는 조건검색식 데이터를 조회할 경우 맨 마지막 조건검색식의 이름이 빈 칸으로 반환되기 때문이다. 다시 말해, 'name': ['조건검색식명', '조건검색식명', '조건검색식명', '']으로 반환되기 때문에, 맨 마지막의 ''에 대해 a[0]를 사용했을 때   IndexError  가 발생하는 것이다.

 

 

조건검색식 조회 ③ SendCondition()

  SendCondition()  의 경우에는 화면번호, 조건검색식명, 인덱스번호, 실시간조회여부라는 네 개의 변수를 인자로 전달받으며 결과값으로 조회요청 성공 여부를 반환하며 OnReceiveTrCondition이라는 이벤트를 발생시킨다. 

어렵게 생각할 것 없이, 아래와 같이 이해하면 된다.


SendCondition('0156', '조건검색식명', '인덱스번호', '0')

① 결과값이 1인 경우 조회요청 성공, 아니면 실패
② OnReceiveTrCondition 이벤트 발생


 

여기서 조건검색식명과 인덱스 번호는 앞서   self.condition_list   변수 내에 입력해두었으니 본인이 원하는 조건검색식명과 인덱스 번호를 전달해주면 된다.

12번째 줄을 보면 결과 데이터는   OnReceiveTrCondition   또는   OnReceiveRealCondition  으로 반환된다는 내용이 있는데,   SendCondition()   함수의 맨 마지막 자리에 있는 인자에 0을 입력하면 단순 조회이기 때문에 OnReceiveTrCondition 이벤트가 발생하지만 1을 입력하게 되면 실시간 조회이기 때문에 OnReceiveRealCondition 이벤트가 발생하게 된다. 이 두 개의 이벤트 역시 앞서 처리했던   OnReceiveConditionVer  와 같이,   OnReceiveTrCondition  과   OnReceiveRealCondition  역시 이벤트 처리를 해주어야 한다.

또한 화면 번호를 의미하는 파라미터인 scrno와 관련하여, 조건검색식을 조회하는 화면 번호는 [0156] 하나 뿐이니 별도의 파라미터로 설정하지 않고 함수 내부적으로 scrno 자리에 "0156"을 입력해서 전달해줄 수도 있다.
※ Line : 1~9

    """SendCondition 함수 실행"""
    def _sendcondition(self, scrno, con_name, con_index, int):
        result = self.kiwoom.dynamicCall("SendCondition(QString, QString, int, int)", scrno, con_name, con_index, int)
        ## 요청 이후의 결과 데이터는 OnReceiveTrCondition 또는 OnReceiveRealCondition으로 반환받음
    
        if result == 1:
            print("[조건검색] 조회요청 성공")
        if result != 1:
            print("[조건검색] 조회요청 실패")

 

 

조건검색식 조회 ④ OnReceiveTrCondition

앞서 언급했듯 이 역시 하나의 이벤트에 해당하므로 이벤트로 처리해주어야 한다. 그리고 OnRecieveTrCondition 이벤트의 경우에는 화면번호와 종목코드, 조건검색식명, 인덱스 번호 등의 데이터를 전달해주게 되는데 우리는 여기서 앞에 있는 종목코드 리스트만 사용하면 된다. 
※ Line : 26, 27, 59~61


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정
        self.condition_list = {'index':[], 'name':[]}

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        self.setrealreg = _df.setrealreg()
        print(self.setrealreg)

        """데이터 요청 구간"""
        self.pushButton.clicked.connect(self.request_opt10016)
        self.pushButton_2.clicked.connect(self.request_opt10080)
        self.pushButton_3.clicked.connect(self._pushbtn_3)
        self.pushButton_4.clicked.connect(self._pushbtn_4)

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)        ## 데이터 조회 요청 처리 함수
        self.kiwoom.OnEventConnect.connect(self.process_login)          ## 로그인 반환값 처리 함수
        self.kiwoom.OnReceiveRealData.connect(self.receive_realdata)    ## 실시간 데이터 수신 처리 함수
        self.kiwoom.OnReceiveConditionVer.connect(self.OnReceiveConditionVer)
        self.kiwoom.OnReceiveTrCondition.connect(self.OnReceiveTrCondition)
        self.kiwoom.OnReceiveRealCondition.connect(self.OnReceiveRealCondition)


    """조건검색식 저장"""
    def __getconditionload(self):
        self.kiwoom.dynamicCall("GetConditionLoad()")


    """OnReceiveConditionVer 이벤트 처리"""
    def OnReceiveConditionVer(self):
        condition_list = self.kiwoom.dynamicCall("GetConditionNameList()").split(";")

        for data in temporary_condition_list:
            try:
                a = data.split("^")
                self.condition_list['index'].append(str(a[0]))
                self.condition_list['name'].append(str(a[1]))
            except IndexError:
                pass

    """SendCondition 함수 실행"""
    def _sendcondition(self, scr_no, con_name, con_index, int):
        result = self.kiwoom.dynamicCall("SendCondition(QString, QString, int, int)", scrno, con_name, con_index, int)
        ## 요청 이후의 결과 데이터는 OnReceiveTrCondition 또는 OnReceiveRealCondition으로 반환받음
    
        if result == 1:
            print("[조건검색] 조회요청 성공")
        if result != 1:
            print("[조건검색] 조회요청 실패")


    """OnReceiveTrCondition 이벤트 처리"""
    def OnReceiveTrCondition(self, scrno, codelist, con_name, con_index, next):
        ## codelist 안에 해당 조건검색식이 찾아낸 종목코드가 담겨 있다.
        print(codelist)

 

해당 부분의 결과값을 확인하면 아래와 같다. 앞서 확인했던 까뮤이앤씨세종메디칼의 종목코드에 해당하는 데이터들이다. 

>>> ['013700;258830;']

 

 

그렇다면 이제 이 데이터를 우리가 사용하고자 하는 변수에 입력해주면 된다. 여기서는   self.code_list   라는 변수 안에 종목코드 데이터가 담긴   codelist   변수를 입력해주었다.
※ Line : 3, 4

# OnRecieveTrCondition 이벤트 처리하기
def OnReceiveTrCondition(scrno, codelist, con_name, con_index, next):
	self.code_list = []
	self.code_list.append(codelist)

 

 

 


728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.