대신증권 CYBOS PLUS 프로그램 구현 (21) - 실시간 데이터 수신받기 ①
프로그램 구현 목표
- Subscribe/Publish 통신 방식 알아보기
- 실시간 데이터 요청 함수 제작하기
- 실시간 데이터 요청할 GUI 생성하기
예전에 작성했던 게시글에서 대신증권 CYBOS PLUS의 통신 방식에 대해 알아보았는데, 그 게시글에서는 대신증권의 통신 방식은 ① 요청/응답(Request/Reply, RQ/RP) 방식과 ② 구독/생산(Subscribe/Publish, SB/PB) 방식의 두 가지로 구분된다고 설명했었다. 이 중 첫 번째 통신 방식은 여태까지 구현했던 코드들의 동작 방식에서 살펴볼 수 있었듯이, `SetInputValue()` 함수를 통해 서버로 특정 데이터를 전달한 후 `Request` 또는 `BlockRequest`를 통해 데이터를 요청하는 방식이었다. 그렇다면 두 번째 방식은 어떠한 방식으로 서버와 통신할 수 있을까 ?
Subscribe/Publish 통신 방식 알아보기
구독/생산 형태의 통신 방식도 요청/응답 형태의 통신 방식과 공통점이 있는데, 바로 `SetInputValue()` 함수를 사용한다는 것이다. 다만 차이점이 있다면 Request나 BlockRequest가 아닌 `Subscribe()`를 사용한다는 것이다. 더 나아가 구독/생산 형태의 통신 방식은 한 번 구독(Subscribe)을 하게 되면 데이터를 계속해서 수신받게 되기 때문에 데이터 수신을 멈추어주는 절차가 필요한데, 이 기능은 `Unsubscribe()`가 담당하고 있다. 다시 말해, `Subscribe()` 함수를 통해 특정 종목에 대한 데이터를 수신 요청을 하게 되면 `Unsubscribe()` 함수를 통해 데이터 수신을 중지하기 전까지는 계속해서 데이터를 전달받는다는 것이다. 이러한 통신 방식은 실시간 데이터를 요청받거나, 실시간 계좌 내역(잔고 변동이 발생했을 때 처리)을 확인하거나 하는 등과 같이 계속해서 서버로부터 데이터를 전달받아야 하는 경우에 사용하게 된다.
실시간 데이터 요청 함수 제작하기
일단 실시간 데이터를 수신하는 기능은 DsCbo1의 `StockCur` 함수가 담당하고 있으며 이 함수의 인자로 전달해주어야 하는 값은 오직 단 하나, 종목코드이다. 즉, 종목코드만 서버로 전달해주면 그 종목에 대한 실시간 데이터를 계속해서 전달받을 수 있다는 것이다. 이제 우리가 여태까지 클래스를 생성했던 규칙과 마찬가지로, DsCbo1.py 파일에 `StockCur` 클래스와 `_StockCur` 함수를 생성해주도록 하자. 그 후에는 대신증권의 COM에 대한 인스턴스와 이벤트 핸들러 인스턴스를 생성해주어야 하는데, 이는 바로 아래에 있는 `StockMst` 클래스에서와 마찬가지로 COM에 대한 인스턴스는 "DsCbo1.StockCur"에 대해 `Dispatch`를 사용하여 인스턴스를 생성해주면 되고 이를 이벤트 핸들러의 인자로 전달해주면 된다.
※ Line: 4 ~ 10
## DsCbo1.py ##
import win32com.client
class StockCur:
def __init__(self):
self.stockcur = win32com.client.Dispatch("DsCbo1.StockCur") ## COM 연결
self.handler = win32com.client.WithEvents(self.stockcur, event_handler)
def _StockCur(self, item_code):
pass
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}")
이제 이 함수를 활용하여 서버에 '우리가 실시간 데이터를 수신받고자 하는 종목코드' 데이터를 전달해주고 서버로부터 회신받은 '실시간 데이터'를 확인해보도록 하자. 먼저 서버에 종목코드 데이터를 전달해주어야 하는데, 이때 사용할 함수는 이전에도 많이 사용했던 함수인 `SetInputValue(Type, Value)`이다. `Type` 값에는 0이 고정으로 들어가고, `Value`에는 종목코드(A005930과 같은 형식)가 들어가게 된다. 일단 먼저 서버에 종목코드를 전달하고 이벤트 처리기와 연결하는 기능까지만 구현해보자.
※ Line: 9 ~ 11
## DsCbo1.py ##
import win32com.client
class StockCur:
def __init__(self):
self.stockcur = win32com.client.Dispatch("DsCbo1.StockCur") ## COM 연결
self.handler = win32com.client.WithEvents(self.stockcur, event_handler)
def _StockCur(self, item_code):
self.stockcur.SetInputValue(0, item_code)
self.handler.set_instance(self.stockcur, "StockCur")
이제 종목코드를 서버에 입력해주었으니 실시간 데이터를 회신받아야 하는데, 서버에 데이터를 요청하는 기능은 `Subscribe()` 함수가 담당하고 있다. 아래의 코드를 확인해보자.
※ Line: 12
## DsCbo1.py ##
import win32com.client
class StockCur:
def __init__(self):
self.stockcur = win32com.client.Dispatch("DsCbo1.StockCur") ## COM 연결
self.handler = win32com.client.WithEvents(self.stockcur, event_handler)
def _StockCur(self, item_code):
self.stockcur.SetInputValue(0, item_code)
self.handler.set_instance(self.stockcur, "StockCur")
self.stockcur.Subscribe()
print(f"실시간 데이터를 수신합니다. ({item_code})")
이제 서버로 "나 item_code 종목에 대한 실시간 데이터좀 줘."라고 요청해두는 부분까지는 구현했으니, 이제 서버에서 전달해주는 결과 데이터를 확인해보도록 하자. 이전부터 게시글을 계속 봐왔다면 알겠지만, 대신증권 Open API는 서버로부터 회신받을 데이터를 크게 `GetHeaderValue()`와 `GetDataValue()`라는 두 가지 함수를 통해 획득할 수 있다. 다만 실시간 데이터의 경우에는 서버로부터 회신받는 데이터의 형태가 차트 데이터와 같이 한 번에 수많은 데이터를 회신받는 것이 아니기 때문에, `GetHeaderValue()` 함수만을 사용하여 서버로부터 회신받은 데이터를 확인할 수 있다.
이 함수 역시 앞서 살펴본 `SetInputValue()` 함수와 마찬가지로 특정 데이터를 전달해주어야 하는데, 전달해주어야 하는 데이터(`Type`)별로 그에 대응하는 데이터 종류는 아래와 같다.
자세한 내용은 추후에 실시간 데이터가 발생하는 것을 보며 확인하도록 하고, 일단은 모든 타입의 데이터를 수신받은 것으로 코드를 구현해보자. 수신 데이터를 처리하는 방법은 이벤트 처리기를 통해 구현하면 된다. 이전에도 특정 종목의 현재가 데이터를 반환할 때 이벤트 처리기를 통해 `OnReceived()` 이벤트가 발생했을 때 관련 데이터를 가져오도록 구현해두었던 부분이 있는데, 이번에도 마찬가지로 이 이벤트가 발생했을 때 관련 데이터를 수신받도록 만들어주면 된다. 유의해야 할 점이 있다면, 데이터 형태가 long인 것들은 int로, char인 것들은 str로 바꾸어주어야 한다는 것이다. 위의 목록 중 데이터 타입이 char에 해당하는 `Type`은 14, 19, 20, 22, 26에 해당한다. 여기서 char 데이터 타입을 str로 바꾸어주는 기능은 `chr` 함수가 수행한다. 14, 19, 20, 22, 26에 해당하는 데이터인 체결 상태(체결가 방식), 예상체결가 구분 신호, 장 구분 신호, 대비 부호, 체결 상태(호가 방식)에 대해 `chr`을 적용해주자.
[참고] 데이터테입이 long, longlong, char C언어에서 사용되는 데이터의 타입으로 파이썬 언어로 넘어오게 되면 각각 int, str 형태로 변경해주어야 한다. 하지만 long과 longlong은 구태여 int로 형 변환을 해주지 않아도 되지만, char는 반드시 str로 변경해주어야 한다.
※ Line: 22 ~ 50
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}")
if self.object == "StockCur":
print("Raised Received Event")
item_code = self.disp.GetHeaderValue(0) # 종목코드(str)
item_name = self.disp.GetHeaderValue(1) # 종목명(str)
dif = self.disp.GetHeaderValue(2) # 전일대비(int)
# time = self.disp.GetHeaderValue(3) # 체결시간(HHMM)(int) ## 시간이 HHMM 까지만 나옴
open = self.disp.GetHeaderValue(4) # 시가(int)
high = self.disp.GetHeaderValue(5) # 고가(int)
low = self.disp.GetHeaderValue(6) # 저가(int)
sell_call = self.disp.GetHeaderValue(7) # 매도호가(int)
buy_call = self.disp.GetHeaderValue(8) # 매수호가(int)
acc_vol = self.disp.GetHeaderValue(9) # 누적체결량(int)
acc_tvol = self.disp.GetHeaderValue(10) # 누적거래대금(int)
close = self.disp.GetHeaderValue(13) # 현재가 또는 예상체결가(int)
test_14 = chr(self.disp.GetHeaderValue(14)) # 체결 상태(체결가 방식)
acc_sell_vol = self.disp.GetHeaderValue(15) # 누적 매도체결량
acc_buy_vol = self.disp.GetHeaderValue(16) # 누적 매수체결량
sign_quan = self.disp.GetHeaderValue(17) # 체결량(int)
time = self.disp.GetHeaderValue(18) # 시간(HHMMSS) (int)
flag_expect = chr(self.disp.GetHeaderValue(19)) # 예상체결가 구분 신호
flag_tradegubun = chr(self.disp.GetHeaderValue(20)) # 장 구분 신호
pnm = chr(self.disp.GetHeaderValue(22)) # 대비 부호
test_26 = chr(self.disp.GetHeaderValue(26)) # 체결 상태(호가 방식)
print(f"[{item_code}:{item_name}] 대비부호:{pnm}, 현재가:{close}(전일대비:{dif}), 시:{open}, 고:{high}, 저:{low}, 종:{close}")
print(f" ({time}) 체결량:{sign_quan} 체결상태(체결가):{test_14}, 체결상태(호가):{test_26}")
print(f" 누적 매수량:{acc_buy_vol}, 누적 매도량:{acc_sell_vol}")
print(f" 매도호가:{sell_call}, 매수호가:{buy_call}, 누적 체결량:{acc_vol}, 누적거래대금:{acc_tvol}")
print(f" 장 구분 신호:{flag_tradegubun}, 예상체결가 구분 신호:{flag_expect}")
실시간 데이터 요청할 GUI 생성하기
이제 실시간 데이터를 수신받기 위해서는 종목코드 데이터를 서버에 전달해주어야 하는데, 이 기능을 담당할 GUI를 Qt Designer 프로그램을 사용하여 구현해주도록 하자.
이제 다시 Boss.py 파일로 돌아와서, 실시간 종목으로 서버에 데이터를 요청할 함수(`def _RealReceive()`)를 생성해주도록 하자. 이 함수는 GUI에서 생성했던 `pushButton_5`가 클릭되면 실행되도록 하고, 실행되었을 때 `lineEdit_13`에 입력되어 있는 종목코드를 가지고 와서 앞서 제작했던 실시간 데이터 요청 함수(DsCbo1.StockCur()._StockCur())로 전달해주면 된다.
※ Line: 44 ~ 48
## Boss.py ##
import pandas as pd
import win32com.client
from pywinauto import application
import con_mysql
import etc
from COM import CpSysDib
from COM import CpUtil
from COM import CpTrade
from COM import DsCbo1
import time
import datetime
from threading import Thread
## GUI ##
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from gui import qt_gui
main_ui = uic.loadUiType("main.ui")[0]
class cybos(QMainWindow, main_ui):
def __init__(self):
super().__init__()
self.setupUi(self)
self._open_cybosplus() ## 자동 로그인 함수
self._load_account() ## 계좌 조회 함수
self._load_DB() ## 데이터베이스 조회 함수
self.saved_point = con_mysql.manage_db()._read_table('saved_point', 'saved_point')
print(self.saved_point)
# self._load_savepoint() ## saved_point 테이블 생성 함수
now_date = datetime.datetime.now().strftime(f"%Y%m%d")
print(now_date)
self.the_latest_date = etc._return_latestdate()
print(f"현시점 최신 거래일:{self.the_latest_date}")
## GUI 관련 이벤트 처리 ##
self.pushButton.clicked.connect(self._GetStockListByMarket)
self.pushButton_2.clicked.connect(self._day_range)
self.pushButton_3.clicked.connect(self._len_chart)
self.comboBox_2.currentIndexChanged.connect(lambda: qt_gui.etc(self).comboBox_2_CIC())
self.pushButton_4.clicked.connect(self._save_all_chart)
self.pushButton_5.clicked.connect(self._RealReceive)
def _RealReceive(self):
item_code = self.lineEdit_13.text()
DsCbo1.StockCur()._StockCur(item_code)
▶ 실행 결과 확인하기(프로그램을 실행시켜서 종목코드에 'A005930'을 입력한 후 실시간 조회 버튼을 클릭하면 됨)
'AUTO TRADE > [대신증권] CYBOS PLUS' 카테고리의 다른 글
대신증권 CYBOS PLUS 프로그램 구현 (23) - 실시간 데이터 수신받기 ③ (2) | 2024.10.01 |
---|---|
대신증권 CYBOS PLUS 프로그램 구현 (22) - 실시간 데이터 수신받기 ② (1) | 2024.09.30 |
대신증권 CYBOS PLUS 프로그램 구현 (20) - 전종목 차트 데이터 저장하기 ③ (0) | 2024.09.23 |
대신증권 CYBOS PLUS 프로그램 구현 (19) - 전종목 차트 데이터 저장하기 ② (0) | 2024.09.21 |
대신증권 CYBOS PLUS 프로그램 구현 (18) - 전종목 차트 데이터 저장하기 ① (7) | 2024.09.18 |
소중한 공감 감사합니다