AUTO TRADE/[대신증권] CYBOS PLUS

대신증권 CYBOS PLUS 프로그램 구현 (23) - 실시간 데이터 수신받기 ③

프로그램 구현 목표

  • 요청 개수 자동 초기화 시점 획득하기: LimitRequestRemainTime
  • 타입별 요청 개수 자동 초기화 시점 획득하기: GetLimitRemainTime
  • 타입별 조회 제한이 걸리기까지 남은 요청 횟수: GetLimitRemainCount
  • 실시간 등록 종목의 잔여 횟수 GUI에 표기하기

대신증권 Open API는 키움증권에서 제공하는 것과 동일하게, 조회 제한이 걸려 있다. 다만 차이점이 있다면 키움증권의 경우에는 조회 제한이 발생하면 프로그램을 멈추고 다시 실행시켜야 하지만 대신증권의 경우에는 서버 내부적으로 조회 제한 데이터를 확인하여 제한에 걸리면 데이터 조회를 멈추었다가 다시 진행시키도록 하고 있다는 것이다. 그렇다면 얼만큼의 시간이 지나야 다시 데이터를 요청할 수 있는 것일까 ? 그리고 그런 정보는 어떻게 얻어올 수 있을까 ? 사실 대부분의 정보는 CpUtil의 CpCybos를 통해 제공되고 있다.

 

요청 개수 자동 초기화 시점 획득하기: LimitRequestRemainTime

가장 먼저 구현할 기능은 조회 제한 횟수가 초기화되는 시간을 반환하는 기능이다. 대신증권 Open API의 경우에는 일정 시간 내에 데이터를 요청할 수 있는 횟수가 제한되어 있는데, 이 때 이전에 데이터 요청을 몇 번 했든지 간에 상관없이 일정한 주기마다 제한 횟수를 초기화한다. 이 때 제한 횟수를 초기화하는 주기를 반환해주는 `LimitRequestRemainTime` 함수를 통해 잔여 시간 데이터를 얻을 수 있다. 

이 함수의 경우에는 직접 호출해서 반환되는 데이터를 보면 알 수 있겠지만 밀리초의 단위로 반환되는 만큼 이 데이터를 초 단위로 바꾸어주면 한 눈에 들어오는 데이터로 만들어 줄 수 있다. 밀리초는 초를 1,000으로 나눈 값으로, 1초는 1,000밀리초와 같다. 그렇기 때문에 밀리초 단위로 반환되는 데이터를 대상으로 1,000으로 나누어주면 단위가 초인 데이터를 얻어올 수 있다.

## CpUtil.py ##
    def _LimitRequestRemainTime(self):
        """
        요청 개수를 재계산하기까지 남은 시간을 반환(시간 단위: milisecond)
        """
        value = round((self.cybos.LimitRequestRemainTime)/1000, 0)
        return value

 

타입별 요청 개수 자동 초기화 시점 획득하기: GetLimitRemainTime

다음으로 구현해 볼 함수는 앞서 살펴본 함수와 동일한 로직으로 동작하는 함수이지만 보다 자세한 정보를 전달해준다는 점에서 다소 차이점이 있다. 이 함수는 크게 주문이나 계좌와 관련된 요청에 대한 초기화 시점과 시세 조회와 관련된 초기화 시점이라는 두 가지 데이터를 반환해준다. 또한 시간과 관련된 데이터를 반환해주는 데에 있어서 그 데이터의 단위는 밀리초로 앞서 살펴봤던 함수와 동일하므로 이 함수의 반환값에 대해서도 1,000으로 나누어서 그 결과값을 초 단위의 데이터로 반환받도록 하자. 

    def _GetLimitRemainTime(self, Type):
        """
        Type에 대한 제한을 해제하기까지 남은 시간을 반환
        LT_TRADE_REQUEST = 0, 주문 및 계좌 관련 요청(단위: milisecond)
        LT_NONTRADE_REQUEST = 1, 시세 관련 요청(단위: milisecond)
        """
        value = round((self.cybos.GetLimitRemainTime(Type))/1000, 0)
        return value

그렇다면 이 함수는 앞서 살펴봤던 `LimitRequestRemainTime` 함수와 어떠한 차이점과 공통점이 있을까 ? 일단 이 함수의 경우에는 ① 주문 및 계좌와 관련된 요청에 대한 요청 횟수 제한이 초기화 하는 시간과 ② 시세와 관련된 요청에 대한 요청 횟수 제한이 초기화되는 시간이라는 두 가지 데이터를 획득할 수 있다고 했는데, 여기서 두 번째 데이터가 바로 앞서 살펴본 함수와 동일한 개념의 데이터이다. 다만 앞서 살펴본 함수는 조회 분야를 불문하고 모든 것에 대한 조회 제한이 초기화되는 시점을 의미하고 이번에 살펴본 함수는 조회 분야에 따라 조회 제한이 초기화되는 시점을 의미한다는 점에서 조금 더 디테일한 초기화 시점을 확인할 수 있다는 점에서 차이점이 있다.

이 내용은 개인적인 사견인데, 보다 더 자세한 프로그램을 구현하기 위해서는 첫 번째 함수보다는 `GetLimitRemainTime` 함수를 더 적극적으로 활용하는 것이 좋을 것 같다고 생각된다. 왜냐하면 주문이나 계좌와 관련된 요청의 경우에는 시세와 관련된 요청의 경우보다 더 짧은 초기화 시간을 갖고 있기 때문이다. 따라서 주문과 관련된 요청 횟수 제한이 걸렸다가 시간이 지나서 풀렸지만(주문은 접수할 수 있지만) 이 때 우리가  첫 번째 함수만을 사용하게 된다면 여전히 요청 횟수 제한이 초기화되기를 기다리는 것밖에 할 수 있는 게 없다. 즉, 주문의 경우에는 주문 초기화 시점에 따르고 그 외의 경우에는 그 외의 초기화 시점에 따라 새로운 데이터를 요청하는 것이 자원을 보다 더 효율적으로 사용할 수 있게 된다는 것이다. 

 

타입별로 조회 제한이 걸리기까지 남은 요청 횟수: GetLimitRemainCount

앞서 살펴본 두 가지 함수는 모두 조회 제한이 걸린 후 그 제한이 풀리기까지 남아있는 시간을 반환해주는 함수였다면, 이 함수는 그 조회 제한이 걸리기까지 더 요청할 수 있는 잔여 요청 횟수를 반환해주는 함수이다. 예를 들어, 10번을 요청한 이후에는 15초 동안 기다려야 한다고 하고 한 번 데이터를 요청했다고 가정해보자. 여기서 조회 제한이 걸리기까지 추가적으로 요청할 수 있는 잔여 요청 횟수는 9회가 되고 여기서 9회를 더 요청하게 되면 15초라는 대기 시간을 거쳐야 요청 횟수 제한이 다시 10회로 돌아올 것인데, 여기서 9회라는 데이터를 가져오는 함수가 바로 `GetLimitRemainCount`다.

이 함수는 지난 게시글에서 실시간 데이터를 수신받기 위해 서버에 데이터를 요청할 때 이미 구현해놨던 함수이기도 하니, 여기서는 간단하게 살펴보고 넘어가도록 하자.

    def _GetLimitRemainCount(self, Type):
        """
        Type에 대한 조회 제한이 이루어지기까지 남은 요청 개수를 반환
        LT_TRADE_REQUEST = 0, 주문 및 계좌 관련 RQ요청
        LT_NONTRADE_REQUEST = 1, 시세 관련 RQ요청
        LT_SUBSCRIBE = 2, 시세 관련 구독 건수
        """
        value = self.cybos.GetLimitRemainCount(Type)
        return value

 


반응형
728x90

 

실시간 등록 종목의 잔여 횟수 GUI에 표기하기

GUI 파일을 열어서 아래와 같이 잔여 요청 횟수를 확인할 부분과 요청 횟수 제한이 풀릴 수 있는 시간을 확인할 부분을 만들어주도록 하자. 이제 이 부분에 앞서 만들었던 함수로부터 데이터를 수신받아 표시하도록 하는 기능을 구현할 것이다.

본인의 경우 상단의 잔여 요청 횟수 하단에 있는 주문 RQ 부분의 객체 이름은 `lineEdit_12`이고 시세 RQ는 `lineEdit_11`, 시세 구독은 `lineEdit_8`의 객체 이름을 갖고 있으며 그 아래에 있는 제한 해제 시간 란의 주문 RQ는 `lineEdit_9`, 시세 RQ는 `lineEdit_10`의 객체 이름을 갖고 있다.(각자의 객체 이름이 다른 경우에는 각자의 객체 이름을 사용하여 데이터를 확인하면 된다.)

이제 이 데이터를 확인하는 방법에 대해 고민해보아야 하는데, 이전 게시글에서 ① 실시간 데이터를 조회할 때마다 잔여 요청 횟수 데이터를 불러오는 함수를 호출하여 결과 데이터를 불러오는 방법도 있긴 하지만 제한 해제 시간같은 경우에는 ② 수시로 데이터를 회신받으면서 데이터 조회 요청 제한이 초기화되는 동시에 이후의 데이터를 처리할 수 있도록 하는 것이 더 효율적인 작동 방식일 것이다. 여기서 수시로 데이터를 확인한다는 것은 단순하게 `while` 문과 `time.sleep()` 함수를 통해 간편하게 구현할 수 있는데, GUI 프로그램의 경우에는 기본적으로 하나의 쓰레드(Thread) 내에서 동작하기 때문에 특정 함수가 계속해서 실행되고 있으면(=`while` 문이 작동하고 있으면) 다른 GUI를 사용할 수 없는 문제점이 발생하게 된다. 일단은 쓰레드 개념을 활용하여 데이터를 처리하는 것은 나중에 알아보기로 하고, 일단 수시로 데이터를 확인할 수 있는 기능을 먼저 구현해보도록 하자.

## Boss.py ##
    def _check_thread(self):
        while True:
            # self.lineEdit_7.setText(str(CpUtil.CpCybos()._LimitRequestRemainTime()))
            self.lineEdit_12.setText(str(CpUtil.CpCybos()._GetLimitRemainCount(0)))
            self.lineEdit_11.setText(str(CpUtil.CpCybos()._GetLimitRemainCount(1)))
            self.lineEdit_8.setText(str(CpUtil.CpCybos()._GetLimitRemainCount(2)))
            self.lineEdit_9.setText(str(CpUtil.CpCybos()._GetLimitRemainTime(0)))
            self.lineEdit_10.setText(str(CpUtil.CpCybos()._GetLimitRemainTime(1)))
            time.sleep(1)

그 후에는 초기화 함수에서 프로그램이 실행됨과 동시에 이 함수를 호출하고 1초마다 계속해서 데이터를 획득해오도록 구현해보자. 하지만 정작 초기화 함수에서 이 함수를 실행시키도록 구현하면 프로그램은 실행되더라도 GUI가 표기되지 않는다거나 하는 등의 오류가 발생할 것이고, 별도의 버튼을 생성해서 이 함수를 실행시키도록 구현하더라도 정작 이 함수를 호출하면 GUI는 응답 없음으로 바뀐 채 아무런 동작도 하지 않을 것이다. 이는 모두 GUI는 하나의 쓰레드에서 동작한다는 점에서 기인한 현상(오류는 아님)들이다.

그렇다면 별도의 쓰레드를 구현하여 이 함수가 동작하도록 코드를 만들어주어야 하는데, 이 때 사용하는 것이 `threading` 라이브러리의 `Thread` 클래스다. 이 라이브러리는 파이썬 내부에서 기본적으로 제공해주고 있는 라이브러리이기 때문에 바로 불러와서 사용하면 되고, 그 사용 방법도 간단하다. 라이브러리를 사용할 때, `target`이라는 인자로 함수를 전달해준 후에 `start()` 함수를 사용하여 이 함수를 실행시키기만 하면 된다.

## Boss.py ##
from threading import Thread

class cybos(QMainWindow, main_ui):
    def __init__(self):
        ( 생략 )
        th = Thread(target=self._check_thread)
        th.start()

이제 이 함수를 실행할 코드를 초기화 함수(`def __init__(self):`) 내부에 구현해주었으므로 프로그램이 실행되는 즉시 관련 데이터를 조회하는 함수가 실행될 것이다. 

▶ 실행 결과 확인하기 ① 시세 구독 잔여 개수는 정상적으로 계산되고 있는가

더보기
실시간 등록 버튼을 누를 때마다 시세 구독 건수가 1건씩 감소하고 있다.

▶ 실행 결과 확인하기 ② 시세 RQ는 정상적으로 감소하고 초기화되는가

더보기
차트를 조회할 때마다 시세 RQ가 1씩 감소하다가, 하단의 제한 해제 시간이 다 경과하자 시세 RQ가 다시 60으로 초기화되는 모습

 

 


728x90
반응형
Contents

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

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