AUTO TRADE/[대신증권] CYBOS PLUS

대신증권 CYBOS PLUS 프로그램 구현 (4) - 이벤트 처리 함수 보완하기

프로그램 구현 목표

  • 로그인 함수 이벤트 처리기 생성하기
  • 이벤트 처리 함수 보완하기

지난 게시글에서 GUI와 파이썬 코드를 연결하는 기능을 구현하였기 때문에 이번 게시글에서부터는 여러 모듈을 활용하여 다양한 기능을 사용하는 코드를 구현해보고자 했으나 이벤트 처리에 있어서 다소 보완해야 할 부분들이 있어서 코드 구현을 잠깐 멈추고 이벤트 처리기를 보완하는 코드를 구현해보고자 한다. 이전에 `win32com` 모듈에서 제공하는 `WithEvents` 함수를 사용하여 이벤트를 처리하는 방법에 대해 살펴보았는데, 동일한 방식에 따라 로그인 함수에 대한 이벤트 처리기도 구현해보고 그 안에서 보완해야 할 부분에 대해서도 함께 구축하도록 하자.

 

로그인 함수 이벤트 처리기 생성하기

우리가 Boss.py 파일 내에서 제작한 로그인 함수는 기본적으로 `class login:`이라는 이름으로 제작되어 있다. 이 클래스는 어디까지나 대신증권의 Cybos Plus에 로그인할 아이디와 비밀번호를 전달하고 로그인을 시도하도록 하는 기능만 수행할 뿐, 그 어떠한 기능도 수행하지 않는다. 다시 말해, 로그인 여부를 확인하는 함수는 `class cybos(QMainWindow, main_ui):` 내부에 있는 함수인 `def _open_cybosplus(self):`가 수행한다는 것이다. 그런데 이 함수는 로그인 여부를 확인할 때에는 정작 CpUtil.py 파일 내에 있는 `class CpCybos:`라는 이름의 클래스 내부에 있는 `def _IsConnect(self):` 함수를 사용한다. 우리가 이전에 DsCbo1.py 파일 내에 있는 `StockMst` 모듈의 이벤트 처리기를 DsCbo1.py 파일 내부에 생성해주었듯이, 로그인과 관련된 함수가 있는 CpUtil.py 파일 내에 있는 `CpCybos` 모듈의 이벤트 처리기는 CpUtil.py 파일 내부에 생성해주어야 한다. 그 구조는 이전에 생성했던 것과 동일하다.

아래와 같이 코드를 구현한 후에 프로그램을 실행시켜 로그인을 시도하고 그 결과를 확인해보도록 하자.
※ Line: 33~41

## CpUtil.py ##
import win32com.client

class CpCybos:
    def __init__(self):
        self.cybos = win32com.client.Dispatch("CpUtil.CpCybos")           ## COM 연결
        self.handlers = win32com.client.WithEvents(self.cybos, event_handler_CpUtil)
        self.handlers.set_instance(self.cybos)

    def _IsConnect(self):
        value = self.cybos.IsConnect
        if value == 1:
            print(f"[통신결과:{value}] 서버와의 연결에 성공했습니다.")
            return True
        elif value == 0:
            print(f"[통신결과:{value}] 서버와의 연결에 실패했습니다.")
            return False

    def _ServerType(self):
        """
        0: 연결 끊김
        1: CybosPlus 서버
        2:HTS 보통 서버(1번 제외)
        """
        value = self.cybos.ServerType
        if value == 0:
            print(f"[서버종류:{value}] 서버와의 연결이 끊겼습니다.")
        elif value == 1:
            print(f"[서버종류:{value}] CybosPlus 서버와의 연결에 성공했습니다.")
        elif value == 2:
            print(f"[서버종류:{value}] HTS 보통 서버와의 연결에 성공했습니다.")

class event_handler_CpUtil:
    """서버와의 접속이 종료된 케이스를 확인할 수 없는 상태"""
    def set_instance(self, disp):
        self.disp = disp

    def OnDisConnect(self, disp):
        print(f"{disp}")
        print("Raised OnDisConnet Event")

▶ 실행 결과 확인하기

더보기
더보기

[통신결과:1] 서버와의 연결에 성공했습니다.
로그인되어 있습니다.
self.client:<win32com.gen_py.CpDib 1.0 Type Library.IDib instance at 0x151140816>
Raised Received Event
[A005930] 삼성전자
  전일종가:78300, 현재가:77700 
  시가:77700, 고가:78400, 저가:77500

 

 

이벤트 처리 함수 보완하기

위의 실행 결과를 보고 보완해야 할 부분이 떠올랐다면 벌써 프로그램 구조를 훤히 꿰뚫고 있다고 보아도 무방할 것 같다. 현재 구조를 보면 `def set_instance(self, client):` 부분에서 사전에 전달받은 COM 구문의 내용만 확인하고 있는데, 실질적으로 우리는 그 내용만 보고 어떤 COM인지 확인할 수 없기 때문에 `def set_instance(self, client):` 함수에서 다른 인자를 추가적으로 받아서 확인해줘야 한다. 일단 추가적으로 수신받을 인자의 이름을 `object`라고 하고 함수를 아래와 같이 수정하도록 하자.
※ Line: 4, 6, 7

## CpUtil.py ##
class event_handler_CpUtil:
    """서버와의 접속이 종료된 케이스를 확인할 수 없는 상태"""
    def set_instance(self, disp, object):
        self.disp = disp
        self.object = object
        print(f"self.object:{self.object}")

    def OnDisConnect(self, disp):
        print(f"{disp}")
        print("Raised OnDisConnet Event")

그리고 다시 `class CpCybos:`로 돌아와서, 이전에 `self.handlers.set_instance(self.cybos)`로만 작성했던 구문에서 해당 함수의 인자로 전달해줄 `object`를 전달해주어야 하는데, 이 `object` 변수가 갖는 의미는 무엇일까? 바로 인스턴스로 생성해두었던 COM 모듈의 이름이라고 보면 된다. 여기서 `self.object`를 하나의 인자로 또 받아내는 이유는 바로 그 인스턴스가 생성시키는 이벤트가 어떠한 인스턴스로부터 발생했는지를 구분해내는 데에 그 목적이 있다고 보면 된다. 현재 작성하고 있는 CpUtil.py 파일 내부에서 사용하고 있는 `CpUtil.CpCybos` 모듈은 `OnDisConnect`라는 이벤트만 발생시킬 수 있겠지만, 다른 모듈로 확장시켜서 본다면 하나의 모듈이 하나의 이벤트만 발생시키지 않을 수 있다.

예를 들어, CpUtil.py 파일 내부에서는 `CpUtil.CpCybos` 모듈 외에도 `CpUtil.CpStockCode`나 `CpUtil.CpCodeMgr` 등과 같은 여러 가지 모듈을 사용하고 있는데, 가령 이 세 가지 모듈이 모두 다 `OnReceived`라는 이벤트를 생성시킨다고 가정해보도록 하자. 그렇다면 이 때 우리는 각기 다른 방식으로 데이터를 수신받아야 하는데, 현재 코드 구조 상에서는 이벤트가 발생했을 때 그 이벤트를 발생시킨 모듈이 어떠한 모듈인지 확인할 수 없다는 맹점이 있다. 따라서 이벤트를 발생시킨 모듈이 어떠한 모듈인지를 확인하는 기능을 `object`라는 변수가 수행하는 것이다.

이제 이벤트 처리기에 COM에 대한 인스턴스를 전달하는 코드 라인에서 `object`를 전달해주도록 하자. 가장 먼저 보완할 클래스는 `class CpCybos` 내부의 로그인 함수이다. 아래의 코드와 실행 결과를 확인해보자. 실행 결과를 보면 우리가 Line: 8에서 전달해준 "CpCybos"가 정상적으로 출력되고 있다는 것을 확인할 수 있다.
※ Line: 8

## CpUtil.py ##
import win32com.client

class CpCybos:
    def __init__(self):
        self.cybos = win32com.client.Dispatch("CpUtil.CpCybos")           ## COM 연결
        self.handlers = win32com.client.WithEvents(self.cybos, event_handler_CpUtil)
        self.handlers.set_instance(self.cybos, "CpCybos")

    def _IsConnect(self):
        value = self.cybos.IsConnect
        if value == 1:
            print(f"[통신결과:{value}] 서버와의 연결에 성공했습니다.")
            return True
        elif value == 0:
            print(f"[통신결과:{value}] 서버와의 연결에 실패했습니다.")
            return False

    def _ServerType(self):
        """
        0: 연결 끊김
        1: CybosPlus 서버
        2:HTS 보통 서버(1번 제외)
        """
        value = self.cybos.ServerType
        if value == 0:
            print(f"[서버종류:{value}] 서버와의 연결이 끊겼습니다.")
        elif value == 1:
            print(f"[서버종류:{value}] CybosPlus 서버와의 연결에 성공했습니다.")
        elif value == 2:
            print(f"[서버종류:{value}] HTS 보통 서버와의 연결에 성공했습니다.")

class event_handler_CpUtil:
    """서버와의 접속이 종료된 케이스를 확인할 수 없는 상태"""
    def set_instance(self, disp, object):
        self.disp = disp
        self.object = object
        print(f"self.object:{self.object}")

    def OnDisConnect(self, disp):
        print(f"{disp}")
        print("Raised OnDisConnet Event")

▶ 실행 결과 확인하기

더보기
더보기

self.object:CpCybos
[통신결과:1] 서버와의 연결에 성공했습니다.
로그인되어 있습니다.
self.client:<win32com.gen_py.CpDib 1.0 Type Library.IDib instance at 0x139934064>
Raised Received Event
[A005930] 삼성전자
  전일종가:78300, 현재가:77700 
  시가:77700, 고가:78400, 저가:77500

 

 


반응형
728x90

 

 

이제 마지막으로 `def OnDisConnect(self):` 함수 내부에서 `self.object` 변수를 통해 OnDisConnect라는 이벤트를 발생시킨 COM 인스턴스가 CpUtil.CpCybos인 경우에만 해당 이벤트를 실행시키도록 수정해주자.
※ Line: 9

class event_handler_CpUtil:
    """서버와의 접속이 종료된 케이스를 확인할 수 없는 상태"""
    def set_instance(self, disp, object):
        self.disp = disp
        self.object = object
        print(f"self.object:{self.object}")

    def OnDisConnect(self, disp):
        if self.object == "CpCybos":
            print(f"{disp}")
            print("Raised OnDisConnet Event")

 

이제 다시 이전에 작성했던 DsCbo1.py 파일 내부에 있는 `class event_handler` 내부의 코드도 이와 동일한 논리 구조에 따라 동작하도록 해당 클래스의 `def set_instance()` 함수가 `object`라는 인자로 추가로 받도록 하고 `def OnReceived` 함수 내부에서는 `self.object`라는 변수를 가지고 이벤트를 발생시킨 COM 인스턴스를 구분하도록 하여 이벤트를 처리할 수 있도록 수정해주도록 하자.

## 기존 코드 ##
## DsCbo1.py ##
import win32com.client

class StockMst:
    def __init__(self):
        self.stockmst = win32com.client.Dispatch("DsCbo1.StockMst")         ## COM 연결
        self.handler = win32com.client.WithEvents(self.stockmst, event_handler)

    def _StockMst(self, item_code):
        """주식종목현재가 반환"""
        self.stockmst.SetInputValue(0, item_code)
        self.handler.set_instance(self.stockmst)
        self.stockmst.BlockRequest()

class event_handler:
    def set_instance(self, disp):
        self.disp = disp
        print(f"self.client:{self.disp}")

    def OnReceived(self):
        print("Raised Received Event")
        item_code = self.disp.GetHeaderValue(0)
        item_name = self.disp.GetHeaderValue(1)
        y_close = self.disp.GetHeaderValue(10)
        now = self.disp.GetHeaderValue(11)
        open = self.disp.GetHeaderValue(13)
        high = self.disp.GetHeaderValue(14)
        low = self.disp.GetHeaderValue(15)
        print(f"[{item_code}] {item_name}")
        print(f"  전일종가:{y_close}, 현재가:{now} ")
        print(f"  시가:{open}, 고가:{high}, 저가:{low}")
## 수정 후 코드 ##
## DsCbo1.py ##
import win32com.client

class StockMst:
    def __init__(self):
        self.stockmst = win32com.client.Dispatch("DsCbo1.StockMst")         ## COM 연결
        self.handler = win32com.client.WithEvents(self.stockmst, event_handler)

    def _StockMst(self, item_code):
        """주식종목현재가 반환"""
        self.stockmst.SetInputValue(0, item_code)
        self.handler.set_instance(self.stockmst, "StockMst")
        self.stockmst.BlockRequest()

class event_handler:
    def set_instance(self, disp, object):
        self.disp = disp
        self.object = object
        print(f"self.client:{self.disp}")
        print(f"self.object:{self.object}")

    def OnReceived(self):
        if self.object == "StockMst":
            print("Raised Received Event")
            item_code = self.disp.GetHeaderValue(0)
            item_name = self.disp.GetHeaderValue(1)
            y_close = self.disp.GetHeaderValue(10)
            now = self.disp.GetHeaderValue(11)
            open = self.disp.GetHeaderValue(13)
            high = self.disp.GetHeaderValue(14)
            low = self.disp.GetHeaderValue(15)
            print(f"[{item_code}] {item_name}")
            print(f"  전일종가:{y_close}, 현재가:{now} ")
            print(f"  시가:{open}, 고가:{high}, 저가:{low}")

▶ 실행 결과 확인하기

더보기
더보기

[통신결과:0] 서버와의 연결에 실패했습니다.
[통신결과:0] 서버와의 연결에 실패했습니다.
로그인되어 있지 않습니다. 로그인을 시도합니다.
[통신결과:0] 서버와의 연결에 실패했습니다.

[통신결과:1] 서버와의 연결에 성공했습니다.
[통신결과:1] 서버와의 연결에 성공했습니다.
로그인되었습니다. 반복문에서 나갑니다.
self.client:<win32com.gen_py.CpDib 1.0 Type Library.IDib instance at 0x146946352>
self.object:StockMst
Raised Received Event
[A005930] 삼성전자
  전일종가:78300, 현재가:77700 
  시가:77700, 고가:78400, 저가:77500

 

 


728x90
반응형
Contents

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

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