We will find a way. We always have.

고려대학교에서 인공지능과 금융공학을 연구하고 있는 어느 대학원생의 블로그입니다.

금융(Finance)/시스템 트레이딩(System Trading)

[예제] 대신증권API를 이용한 트레이딩 시스템 - 데이터 요청 방법 2가지 BlockRequest 와 Request 방식 비교하기

MinsukSung 2020. 9. 22. 21:48

안녕하세요 성민석입니다.

진행하는 튜토리얼의 모든 코드는 대신증권 사이보스플러스 자료실에서 제공하는 걸 기반으로 만들었습니다.

그리고 여기에 사용된 모든 코드는 저의 GitHub에서 확인하실 수 있습니다.


지난 시간에는 대신증권API를 통해서 예수금 가져오기에 대해서 알아보았습니다. 이 때 제가 대신증권API를 통해서 데이터를 받아오는 방법이 사뭇 다르다는 걸 깨닫고 대신증권에서 제공하는 튜토리얼을 한번 공부하고자 합니다. 

 

 

[개발] 대신증권API를 이용한 트레이딩 시스템 - 예수금 가져오기

안녕하세요 성민석입니다. 진행하는 튜토리얼의 모든 코드는 대신증권 사이보스플러스 자료실에서 제공하는 걸 기반으로 만들었습니다. 그리고 여기에 사용된 모든 코드는 저의 GitHub에서 확인

minsuksung-ai.tistory.com

 

대신증권API를 통해서 데이터를 가져올 수 있는 방식은 크게 2가지라고 합니다.

 

  1. BlockRequest 방식 - 가장 간단하게 데이터 요청해서 수신 가능
  2. Request 방식 - 함수 호출 후 Received 이벤트로 수신 받기
일반적인 데이터 요청에는 BlockRequest  방식이 가장 간단합니다. 다만, BlockRequest  함수 내에서도 동일 하게 메시지 펌핑을 하고 있어 해당 통신이 마치기 전에 실시간 시세를 수신 받거나 다른 이벤트에 의해 재귀 호출 되는 문제가 있을 경우 함수 호출이 실패할 수 있습니다. 복잡한 실시간 시세 수신 중에 통신을 해야 하는 경우에는 Request 방식을 이용해야 합니다. 

출처: 대신증권 자료실

 

대신증권 자료실에 있는 말을 조금 더 쉽게 풀어보면 저번 시간에 예수금 가져왔을 때 확인할 수 있었듯이, BlockRequest()로 데이터를 가져올 수 있었습니다. 근데 이렇게 한꺼번에 (그래서 Block이란 표현을 사용하는듯 합니다.) 내가 필요한 모든 정보가 담겨있는 데이터와 달리 실시간 데이터는 계속해서 데이터를 받아야 합니다. 여전히 감이 안 오실 수 있으니 예제를 한번 살펴보죠.

 

예제를 그대로 복사해서 붙여넣었는데 왜 에러가 나는거지?

 

일단 에러부터 해결해야합니다.

AttributeError: module 'win32event' has no attribute 'createEvent' 라는 에러가 뜨는데 생각보다 오랜 시간을 삽질하고 나서야 게시판의 댓글 도움으로 해결할 수 있었습니다. 저기 c가 C로 바뀌어야한다는 것입니다. 혹시 이걸 변경해도 동작이 안되시는 분들은 라이브러리 설치를 해보시길 바랍니다.

pip install pywin32
pip install pypiwin32

저의 시간을 벌게 해준 고마우신 분

 

예제를 돌리면 아래와 같은 결과가 나옵니다. 한번 코드를 차근차근 뜯어보죠.

 

예제를 돌려본 결과

 


BlockRequest 방식에 대하여

여기서 보면 DsCbo1.StockMst API를 이용하여 객체를 생성합니다. 그런 후에는 SetInputValue 함수를 통해서 파라미터를 설정하고 BlockRequest() 함수를 통해서 데이터를 가져옵니다. 이렇게 가져온 데이터는 objStockMst의 GetHeaderValue를 통해서 가져올 수 있습니다.

# 1. BlockRequest
print('#####################################')
objStockMst = win32com.client.Dispatch("DsCbo1.StockMst")
code = 'A005930'
objStockMst.SetInputValue(0, code)
objStockMst.BlockRequest()
print('BlockRequest 로 수신 받은 데이터')
item = {}
item['종목명'] = g_objCodeMgr.CodeToName(code)
item['현재가'] = objStockMst.GetHeaderValue(11)  # 종가
item['대비'] = objStockMst.GetHeaderValue(12)  # 전일대비
print(item)

여기까진 예수금 데이터를 가져왔을 때처럼 동일하게 데이터를 가져올 수 있는 방식이었습니다. 근데 우리가 아직 생소한 Request 방식에 대해서 알아봅시다. 

 


Request 방식에 대하여

일단 CpCurReply라는 클래스와 MessagePump라는 함수가 무엇인지 모르겠습니다. 하지만 코드로만 알 수 있는 것은 일단 CpCurReply 클래스를 통해서 객체를 생성한 후 Subscribe 함수를 호출합니다. 그러고 앞선 예제와 같이 objStockMst 객체를 통해서 이번엔 Request 함수를 호출하게 됩니다. (앞에서는 BlockRequest 함수를 호출했음을 기억해주세요.) 그런 뒤에 MessagePump 함수가 호출되고 이후는 앞의 예제와 동일하게 진행됩니다.

 

# 2. Request ==> 메시지 펌프 ==>  OnReceived 이벤트 수신
print('#####################################')
objReply = CpCurReply(objStockMst)
objReply.Subscribe()

code = 'A005930'
objStockMst.SetInputValue(0, code)
objStockMst.Request()
MessagePump(10000)
item = {}
item['종목명'] = g_objCodeMgr.CodeToName(code)
item['현재가'] = objStockMst.GetHeaderValue(11)  # 종가
item['대비'] = objStockMst.GetHeaderValue(12)  # 전일대비
print(item)

일단 Request 방식을 이해하기 위해서 CpEvent 클래스와 CpCurReply 클래스와 마지막으로 MessagePump 함수를 하나씩 살펴봅시다. 저도 코딩을 잘하진 않아서 조금 헷갈리네요.

 

CpEvent 클래스

CpEvent 클래스는 실시간 통신을 하기 위한 객체입니다. 이후 CpCurReply 클래스의 Subscribe 메소드에서 사용될 예정입니다. 근데 OnReceived 메소드는 어디서 사용되는지 아직까지 확인할 수 없습니다.

class CpEvent:
    def set_params(self, client, name, caller):
        self.client = client  # CP 실시간 통신 object
        self.name = name  # 서비스가 다른 이벤트를 구분하기 위한 이름
        self.caller = caller  # callback 을 위해 보관

    def OnReceived(self):
        # 실시간 처리 - 현재가 주문 체결
        if self.name == 'stockmst':
            print('recieved')
            win32event.SetEvent(StopEvent)
            return

 

CpCurReply 클래스

CpCurReply 클래스는 Subscribe 메소드를 통해서 이벤트가 발생하면 핸들링해주는 클래스입니다. 

 

class CpCurReply:
    def __init__(self, objEvent):
        self.name = "stockmst"
        self.obj = objEvent

    def Subscribe(self):
        handler = win32com.client.WithEvents(self.obj, CpEvent)
        handler.set_params(self.obj, self.name, None)

 

MessagePump 함수

MessagePump 함수를 통해서 OnReceived 이벤트를 수신할 수 있게 됩니다.

 

def MessagePump(timeout):
    waitables = [StopEvent]
    while 1:
        rc = win32event.MsgWaitForMultipleObjects(
            waitables,
            0,  # Wait for all = false, so it waits for anyone
            timeout,  # (or win32event.INFINITE)
            win32event.QS_ALLEVENTS)  # Accepts all input

        if rc == win32event.WAIT_OBJECT_0:
            # Our first event listed, the StopEvent, was triggered, so we must exit
            print('stop event')
            break

        elif rc == win32event.WAIT_OBJECT_0 + len(waitables):
            # A windows message is waiting - take care of it. (Don't ask me
            # why a WAIT_OBJECT_MSG isn't defined < WAIT_OBJECT_0...!).
            # This message-serving MUST be done for COM, DDE, and other
            # Windowsy things to work properly!
            print('pump')
            if pythoncom.PumpWaitingMessages():
                break  # we received a wm_quit message
        elif rc == win32event.WAIT_TIMEOUT:
            print('timeout')
            return
            pass
        else:
            print('exception')
            raise RuntimeError("unexpected win32wait return value")

 


 

 저는 해당 예제를 보면서 이해하려고 했는데, 여전히 감이 안 잡히네요. 그래서 여기저기 찾아보다가 대신증권 비공식 블로그에서 퍼온 내용은 아래와 같습니다. 이해하시는데 도움이 되시길 바랍니다.


CybosPlus Communication
대신증권의 통신은 Request/Reply ( RQ/RP ) 방식과 Subscribe/Publish (SB/PB) 방식으로 나눠집니다. CybosPlus의 각 통신 오브젝트는 이 두 가지 통신 모델 중 한가지만 지원합니다.
 
1. RQ/RP 와 SB/PB 비교

[ 비동기식 (asynchronous) ]
입력 데이터를 채워넣고 통신을 요청(Request or Subscribe) 하면 함수가 바로 반환됩니다. 그 후 서버로부터 데이터가 수신되면 Received 이벤트가 발생하게 됩니다

 

RQ/RP : 현시점의 데이타 1회 통신 요청

 

SB/PB : 실시간 데이타 수신 요청 변경 시에만 이벤트가 발생합니다. 요청시점의 데이타를 얻기위해서는 먼저 RQ/RP 오브젝트로 구현한 이후에 사용하세요. 복수종목을 실시간으로 수신받으려면 1,2 항목을 반복하면 됩니다.

 

 

2. RQ/RP의 동기식 통신 지원

[ 동기식 (synchronous) ]

입력 데이터를 채워넣고 BlockRequest 메소드를 호출하면, 서버로 부터 응답이 완료   까지 대기상태를 유지한다. 데이터를 정상적으로 수신한 후에야 함수가 리턴된다. 30초 동안 서버로 부터 요청한 데이타를 수신하지  하면 타임아웃으로 처리된다. BlockRequest 함수의 리턴값으로 통신결과 상태를 확인할  있다.

 

RQ/RP의 동기식 통신 지원

 

3. RQ/RP의 연속 데이타 통신

데이타 수신시에는 효율성을 고려하여 데이타의 적정 Size가 있습니다. 모든 데이타를 한번의 요청으로 얻는 것이 아니라, 여러번 요청으로 데이터를 얻을  있습니다. 예를 들면, CYBOS의 화면 7024,7026 처럼 시간대별, 일자별의 데이타의 양이 많습니다. 이런 경우 화면 우측 상단에 "다음" 버튼이 존재합니다. "다음" 버튼이 활성화 되어 있다는 것은 현재 수신된 데이타 이후로 데이타가 존재한다는 의미입니다. CybosPlus에서 CYBOS 화면의 "다음" 버튼이 활성화  상태와 같은 의미로는 각 오브젝트에 공통 속성인 Continue가 True인 상태입니다. 아래 그림과 같이 CybosPlus에서는 데이타를 수신 받고나서 Continue 속성을 체크합니다. Continue가 Ture인 것은 연속데이타가 있다는 의미이므로, 그 상태에서 통신을 요청하면(BlockRequest 또는 Request) 연속데이타를 얻을  있습니다.

 

( 다음 그림은 동기식(BlockRequest)으로 설명한 것입니다. 비동기(Request)로도 연속 데이타 통신 구현이 가능합니다)

 

동기식(BlockRequest)으로 설명한 그림

 

 

CybosPlus Communication

설명 주식 종목에 대해 일자별 주가 데이터 (최고 10년치 데이터)를 최근의 날로부터 그 일정 시점의 날까지 시가,고가,저가,종가,등락률,외인 비중 등등격

cybosplus.github.io


생각보다 자세하고 쉽게 설명되어 있습니다. 간단하게 정리하자면 BlockRequest 방식은 요청할 때만 1번 데이터를 주는 것이고, Request 방식은 Subscribe 함수를 통해서 이벤트가 발생할 때마다 수시로 데이터를 수신할 수 있는 방식이라고 정리할 수 있습니다. 사실 동기식과 비동기식의 차이는 아직까지 정확하게 이해를 하지 못해서 내용을 잘 못 전달할 수 있으므로 생략하겠습니다. (추후 이해가 더 쌓이면 다시 오겠습니다...)

 

원문이 궁금하신 분들은 링크 걸어두었습니다. 직접 가서 확인해보시길 바랍니다.


전체코드는 아래를 확인해보시길 바랍니다.

 

import pythoncom
from PyQt5.QtWidgets import *
import win32com.client
 
import win32event
 
g_objCodeMgr = win32com.client.Dispatch('CpUtil.CpCodeMgr')
 
StopEvent = win32event.CreateEvent(None, 0, 0, None)
 
class CpEvent:
    def set_params(self, client, name, caller):
        self.client = client  # CP 실시간 통신 object
        self.name = name  # 서비스가 다른 이벤트를 구분하기 위한 이름
        self.caller = caller  # callback 을 위해 보관
 
    def OnReceived(self):
        # 실시간 처리 - 현재가 주문 체결
        if self.name == 'stockmst':
            print('recieved')
            win32event.SetEvent(StopEvent)
            return
 
 
class CpCurReply:
    def __init__(self, objEvent):
        self.name = "stockmst"
        self.obj = objEvent
 
    def Subscribe(self):
        handler = win32com.client.WithEvents(self.obj, CpEvent)
        handler.set_params(self.obj, self.name, None)
 
 
def MessagePump(timeout):
    waitables = [StopEvent]
    while 1:
        rc = win32event.MsgWaitForMultipleObjects(
            waitables,
            0,  # Wait for all = false, so it waits for anyone
            timeout, #(or win32event.INFINITE)
            win32event.QS_ALLEVENTS)  # Accepts all input
 
        if rc == win32event.WAIT_OBJECT_0:
            # Our first event listed, the StopEvent, was triggered, so we must exit
            print('stop event')
            break
 
        elif rc == win32event.WAIT_OBJECT_0 + len(waitables):
            # A windows message is waiting - take care of it. (Don't ask me
            # why a WAIT_OBJECT_MSG isn't defined < WAIT_OBJECT_0...!).
            # This message-serving MUST be done for COM, DDE, and other
            # Windowsy things to work properly!
            print('pump')
            if pythoncom.PumpWaitingMessages():
                break  # we received a wm_quit message
        elif rc == win32event.WAIT_TIMEOUT:
            print('timeout')
            return
            pass
        else:
            print('exception')
            raise RuntimeError("unexpected win32wait return value")
 
 
code = 'A005930'
 
##############################################################
#1. BlockRequest
print('#####################################')
objStockMst = win32com.client.Dispatch("DsCbo1.StockMst")
objStockMst.SetInputValue(0, code)
objStockMst.BlockRequest()
print('BlockRequest 로 수신 받은 데이터')
item = {}
item['종목명']= g_objCodeMgr.CodeToName(code)
item['현재가'] = objStockMst.GetHeaderValue(11)  # 종가
item['대비'] =  objStockMst.GetHeaderValue(12)  # 전일대비
print(item)
 
print('')
##############################################################
# 2. Request ==> 메시지 펌프 ==>  OnReceived 이벤트 수신
print('#####################################')
objReply = CpCurReply(objStockMst)
objReply.Subscribe()
 
code = 'A005930'
objStockMst.SetInputValue(0, code)
objStockMst.Request()
MessagePump(10000)
item = {}
item['종목명']= g_objCodeMgr.CodeToName(code)
item['현재가'] = objStockMst.GetHeaderValue(11)  # 종가
item['대비'] =  objStockMst.GetHeaderValue(12)  # 전일대비
print(item)

 


참고자료

 

사이보스플러스자료실 - 대신증권

첨부파일을 PDF뷰어로 확인 하실수 있으며, PDF뷰어 미설치 고객께서는 우측 다운로드를 통해 설치 후 이용 가능 합니다. PDF뷰어 다운로드

money2.daishin.com

 

 

mssung94/daishin-trading-system

대신증권API를 활용하여 간단하게 만들어보는 트레이딩 시스템. Contribute to mssung94/daishin-trading-system development by creating an account on GitHub.

github.com