[파이썬] Creon Plus 를 이용해서 KOSPI200 종목 시세 가져오기

2017. 10. 15. 15:44개발/파이썬

목적: 

CREON PLUS API를 이용해서 KOSPI200에 소속된 200 종목의 현재가 정보를 가져온다

가져온 데이터를 Pandas DataFrame 에 저장하고, 엑셀로도 내 보낸다. 


예제를 위해 공부가 필요한(or 필요했던) 항목들

  • CREON PLUS API (증권 시세/주문/계좌 정보 가져오기 위해 필요)
  • 판다스(PANDAS), DATAFRAME


개인적으로는 파이썬의 Pandas/DataFrame 을 좀더 공부하고 싶어서 만든 예제입니다 

아직 실시간 부분은 처리하지 못했는데 요건 담번에 추가 예정입니다 

실시간 시세 받아 업데이트도 됩니다.


엑셀로 저장한 데이터 


전체 코드 (실시간은 아직 미 구현 실시간도 구현됨)

import sys
from PyQt5.QtWidgets import *
import win32com.client
from pandas import Series, DataFrame
import pandas as pd
import locale
import os
import time
 
locale.setlocale(locale.LC_ALL, '')
# cp object
g_objCodeMgr = win32com.client.Dispatch('CpUtil.CpCodeMgr')
g_objCpStatus = win32com.client.Dispatch('CpUtil.CpCybos')
g_objCpTrade = win32com.client.Dispatch('CpTrade.CpTdUtil')
 
gExcelFile = 'd:\\dev\\market_data.xlsx'
 
# CpEvent: 실시간 이벤트 수신 클래스
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 == 'stockcur':
            pbTime = time.time()
            #curData = {}
            code = self.client.GetHeaderValue(0)
            #curData = self.caller.marketDF.loc[code]
 
            self.caller.marketDF.set_value(code, 'time', self.client.GetHeaderValue(18))  # 초
            self.caller.marketDF.set_value(code, 'name',self.client.GetHeaderValue(1) )
            self.caller.marketDF.set_value(code, 'open',self.client.GetHeaderValue(4))
            self.caller.marketDF.set_value(code, 'high', self.client.GetHeaderValue(5))
            self.caller.marketDF.set_value(code, 'low',self.client.GetHeaderValue(6))
            self.caller.marketDF.set_value(code, 'offer', self.client.GetHeaderValue(7))
            self.caller.marketDF.set_value(code, 'bid', self.client.GetHeaderValue(8))
            exFlag  = self.client.GetHeaderValue(19)  # ord('1') - 예상, ord('2') 장중
            if (exFlag  == ord('2')):
                self.caller.marketDF.set_value(code, 'cur',self.client.GetHeaderValue(13))  # 현재가
                self.caller.marketDF.set_value(code, 'diff', self.client.GetHeaderValue(2))  # 대비
                self.caller.marketDF.set_value(code, 'vol', self.client.GetHeaderValue(9))  # 거래량
                lastday = self.caller.marketDF.get_value(code, 'lastprice')
                diff = self.caller.marketDF.get_value(code, 'diff')
                if lastday:
                    diffp = (diff / lastday) * 100
                    self.caller.marketDF.set_value(code, 'diffp',diffp)
 
            # for debug
            #curData = self.caller.marketDF.loc[code]
            #print(curData)
 
 
class CpPublish:
    def __init__(self, name, serviceID):
        self.name = name
        self.obj = win32com.client.Dispatch(serviceID)
        self.bIsSB = False
 
    def Subscribe(self, var, caller):
        if self.bIsSB:
            self.Unsubscribe()
 
        if (len(var) > 0):
            self.obj.SetInputValue(0, var)
 
        handler = win32com.client.WithEvents(self.obj, CpEvent)
        handler.set_params(self.obj, self.name, caller)
        self.obj.Subscribe()
        self.bIsSB = True
 
    def Unsubscribe(self):
        if self.bIsSB:
            self.obj.Unsubscribe()
        self.bIsSB = False
 
# CpPBStockCur: 실시간 현재가 요청 클래스
class CpPBStockCur(CpPublish):
    def __init__(self):
        super().__init__('stockcur', 'DsCbo1.StockCur')
 
 
# CpMarketEye : 복수종목 현재가 통신 서비스
class CpMarketEye:
    def Request(self, codes, caller):
        # 연결 여부 체크
        objCpCybos = win32com.client.Dispatch('CpUtil.CpCybos')
        bConnect = objCpCybos.IsConnect
        if (bConnect == 0):
            print('PLUS가 정상적으로 연결되지 않음. ')
            return False
 
        # 관심종목 객체 구하기
        objRq = win32com.client.Dispatch('CpSysDib.MarketEye')
        # 필드
        # 0 코드, 1 시간 2:대비부호(char) 3:전일대비 - 주의) 반드시대비부호(2)와같이요청을하여야함
        # 4:현재가 5:시가 6:고가  7:저가 8:매도호가 9:매수호가 10:거래량 23:전일종가
        # 28:예상체결가 29:예상체결가대비(long) - 주의) 반드시예샹체결가대비부호(30)와같이요청을하여야함
        # 30:예상체결가대비부호 31:예상체결수량
        rqField = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 23, 28, 29, 30, 31]  # 요청 필드
        objRq.SetInputValue(0, rqField)  # 요청 필드
        objRq.SetInputValue(1, codes)  # 종목코드 or 종목코드 리스트
        objRq.BlockRequest()
 
        # 현재가 통신 및 통신 에러 처리
        rqStatus = objRq.GetDibStatus()
        rqRet = objRq.GetDibMsg1()
        print('통신상태', rqStatus, rqRet)
        if rqStatus != 0:
            return False
 
        cnt = objRq.GetHeaderValue(2)
 
        caller.marketDF = None
        caller.marketDF = pd.DataFrame(columns=('code', 'name','time', 'cur', 'diff',
                        'diffp', 'vol', 'offer','bid','open','high', 'low','lastprice'))
 
 
        for i in range(cnt):
            item = {}
            item['code'] = objRq.GetDataValue(0, i)  # 코드
            item['name'] = g_objCodeMgr.CodeToName(item['code'])
            item['time'] = objRq.GetDataValue(1, i)  # 시간
            item['diff'] = objRq.GetDataValue(3, i)  # 전일대비
            item['cur'] = objRq.GetDataValue(4, i)  # 현재가
            item['open'] = objRq.GetDataValue(5, i)  # 시가
            item['high'] = objRq.GetDataValue(6, i)  # 고가
            item['low'] = objRq.GetDataValue(7, i)  # 저가
            item['offer'] = objRq.GetDataValue(8, i)  # 매도호가
            item['bid'] = objRq.GetDataValue(9, i)  # 매수호가
            item['vol'] = objRq.GetDataValue(10, i)  # 거래량
            item['lastprice'] = objRq.GetDataValue(11, i)  # 전일종가
            #item['excur'] = objRq.GetDataValue(12, i)  # 예상체결가
            #item['exdiff'] = objRq.GetDataValue(13, i)  # 예상대비
            #item['exvol'] = objRq.GetDataValue(15, i)  # 예상체결수량
 
 
            if item['lastprice'] != 0 :
                item['diffp'] = (item['diff'] / item['lastprice']) * 100
                #item['exdiffp'] = (item['exdiff'] / item['lastprice']) * 100
            else:
                item['diffp'] = 0
                #item['exdiffp'] = 0
 
            caller.marketDF.loc[len(caller.marketDF)] = item
 
        # data frmae 의  기본 인덱스(0,1,2,3~ ) --> 'code' 로 변경
        caller.marketDF =  caller.marketDF.set_index('code')
        # 인덱스 이름 제거
        caller.marketDF.index.name = None
        print(caller.marketDF)
        return True
 
 
class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('PLUS API TEST')
        self.setGeometry(300, 300, 300, 240)
        self.isSB = False
        self.objCur = []
 
        self.marketDF = DataFrame()
 
        btnStart = QPushButton('요청 시작', self)
        btnStart.move(20, 20)
        btnStart.clicked.connect(self.btnStart_clicked)
 
        btnExcel = QPushButton('Excel 내보내기', self)
        btnExcel.move(20, 70)
        btnExcel.clicked.connect(self.btnExcel_clicked)
 
        btnPrint = QPushButton('DF Print', self)
        btnPrint.move(20, 120)
        btnPrint.clicked.connect(self.btnPrint_clicked)
 
        btnExit = QPushButton('종료', self)
        btnExit.move(20, 190)
        btnExit.clicked.connect(self.btnExit_clicked)
 
    def StopSubscribe(self):
        if self.isSB:
            cnt = len(self.objCur)
            for i in range(cnt):
                self.objCur[i].Unsubscribe()
            print(cnt, '종목 실시간 해지되었음')
        self.isSB = False
 
        self.objCur = []
 
    def btnStart_clicked(self):
        # 요청 필드 배열 - 종목코드, 시간, 대비부호 대비, 현재가, 거래량, 종목명
        codes = g_objCodeMgr.GetGroupCodeList(180)
        objMarkeyeye = CpMarketEye()
        if (objMarkeyeye.Request(codes,self) == False):
            exit()
 
        cnt = len(codes)
        for i in range(cnt):
            self.objCur.append(CpPBStockCur())
            self.objCur[i].Subscribe(codes[i], self)
 
        print('================-')
        print(cnt, '종목 실시간 현재가 요청 시작')
        self.isSB = True
 
    def btnExcel_clicked(self):
        print(len(self.marketDF.index))
        # Create a Pandas Excel writer using XlsxWriter as the engine.
        writer = pd.ExcelWriter(gExcelFile, engine='xlsxwriter')
        # Convert the dataframe to an XlsxWriter Excel object.
        self.marketDF.to_excel(writer, sheet_name='Sheet1')
        # Close the Pandas Excel writer and output the Excel file.
        writer.save()
        os.startfile(gExcelFile)
        return
 
    def btnPrint_clicked(self):
        print(self.marketDF)
 
    def btnExit_clicked(self):
        self.StopSubscribe()
        exit()
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()


DataFrame 생성

컬럼만 먼저 생성함. 

       caller.marketDF = pd.DataFrame(columns=('code', 'name','time', 'cur', 'diff',
                        'diffp', 'vol', 'offer','bid','open','high', 'low','lastprice'))



DataFrame 에 행 추가(딕셔너리로 key:value 식으로 만든 행)

loc[index] 식으로 행 추가 가능 

item 의 key 값은 data frame 의 기본 컬럼과 개수가 동일 해야 함.

            caller.marketDF.loc[len(caller.marketDF)] = item




DataFrame 의 기본 인덱스를 특정 컬럼으로 변경하기

기본적으로 0, 1, 2, 3, 과 같은 인덱스가 붙는데 입력된 특정 컬럼으로 변경하려면 df.set_index() 함수를 사용하면 된다. 
df.set_index() 를 호출하면 인덱스 이름이 한줄 추가되기 때문에 이를 없애는 코드도 아래와 같이 필요하다 

       # data frmae 의  기본 인덱스(0,1,2,3~ ) --> 'code' 로 변경
        caller.marketDF =  caller.marketDF.set_index('code')
        # 인덱스 이름 제거
        caller.marketDF.index.name = None


DataFrame 업데이트 하기 

DataFrame의 업데이트는 엑셀의 행/열 식으로 업데이트 가능합니다 

dataframe.set_value('행', '열', value)

            self.caller.marketDF.set_value(code, 'time', self.client.GetHeaderValue(18))  # 초
            self.caller.marketDF.set_value(code, 'name',self.client.GetHeaderValue(1) )
            self.caller.marketDF.set_value(code, 'open',self.client.GetHeaderValue(4))
            self.caller.marketDF.set_value(code, 'high', self.client.GetHeaderValue(5))
            self.caller.marketDF.set_value(code, 'low',self.client.GetHeaderValue(6))
            self.caller.marketDF.set_value(code, 'offer', self.client.GetHeaderValue(7))
            self.caller.marketDF.set_value(code, 'bid', self.client.GetHeaderValue(8))
            exFlag  = self.client.GetHeaderValue(19)  # ord('1') - 예상, ord('2') 장중
            if (exFlag  == ord('2')):
                self.caller.marketDF.set_value(code, 'cur',self.client.GetHeaderValue(13))  # 현재가
                self.caller.marketDF.set_value(code, 'diff', self.client.GetHeaderValue(2))  # 대비
                self.caller.marketDF.set_value(code, 'vol', self.client.GetHeaderValue(9))  # 거래량
                lastday = self.caller.marketDF.get_value(code, 'lastprice')
                diff = self.caller.marketDF.get_value(code, 'diff')
                if lastday:
                    diffp = (diff / lastday) * 100
                    self.caller.marketDF.set_value(code, 'diffp',diffp)




DataFrame을 엑셀로 내보내기

데이터 프레임으로 만들면 df.to_excel() 한줄로 엑셀 내보내기가 가능하다 

    def btnExcel_clicked(self):
        print(len(self.marketDF.index))
        # Create a Pandas Excel writer using XlsxWriter as the engine.
        writer = pd.ExcelWriter(gExcelFile, engine='xlsxwriter')
        # Convert the dataframe to an XlsxWriter Excel object.
        self.marketDF.to_excel(writer, sheet_name='Sheet1')
        print(2)
        # Close the Pandas Excel writer and output the Excel file.
        writer.save()
        os.startfile(gExcelFile)
        return


Creon Plus 마켓아이 서비스

마켓아이 서비스를 이용하면 종목에 대한 160여가지 정보를 한번에 구할 수 있다.(최대 200 종목) 

서비스에 대한 자세한 설명은 아래 링크에서 확인 가능~

http://money2.creontrade.com/e5/mboard/ptype_basic/HTS_Plus_Helper/DW_Basic_Read_Page.aspx?boardseq=284&seq=131&page=1&searchString=marketeye&p=8841&v=8643&m=9505

요청 필드와 종목리스트를 세팅해서 obj.BlockRequest() 호출하면 배열 방식으로 정보를 내려 준다. 

       # 관심종목 객체 구하기
        objRq = win32com.client.Dispatch('CpSysDib.MarketEye')
        # 필드
        # 0 코드, 1 시간 2:대비부호(char) 3:전일대비 - 주의) 반드시대비부호(2)와같이요청을하여야함
        # 4:현재가 5:시가 6:고가  7:저가 8:매도호가 9:매수호가 10:거래량 23:전일종가
        # 28:예상체결가 29:예상체결가대비(long) - 주의) 반드시예샹체결가대비부호(30)와같이요청을하여야함
        # 30:예상체결가대비부호 31:예상체결수량
        rqField = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 23, 28, 29, 30, 31]  # 요청 필드
        objRq.SetInputValue(0, rqField)  # 요청 필드
        objRq.SetInputValue(1, codes)  # 종목코드 or 종목코드 리스트
        objRq.BlockRequest()