사진찍는 프로그래머

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

개발/파이썬

목적: 

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

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


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

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


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

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

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


엑셀로 저장한 데이터 


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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 = [0123456789102328293031]  # 요청 필드
        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(300300300240)
        self.isSB = False
        self.objCur = []
 
        self.marketDF = DataFrame()
 
        btnStart = QPushButton('요청 시작', self)
        btnStart.move(2020)
        btnStart.clicked.connect(self.btnStart_clicked)
 
        btnExcel = QPushButton('Excel 내보내기', self)
        btnExcel.move(2070)
        btnExcel.clicked.connect(self.btnExcel_clicked)
 
        btnPrint = QPushButton('DF Print', self)
        btnPrint.move(20120)
        btnPrint.clicked.connect(self.btnPrint_clicked)
 
        btnExit = QPushButton('종료', self)
        btnExit.move(20190)
        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_()
cs


DataFrame 생성

컬럼만 먼저 생성함. 

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


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

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

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

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


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

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

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


DataFrame 업데이트 하기 

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    
            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)
cs



DataFrame을 엑셀로 내보내기

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

1
2
3
4
5
6
7
8
9
10
11
    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
cs


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() 호출하면 배열 방식으로 정보를 내려 준다. 

1
2
3
4
5
6
7
8
9
10
11
        # 관심종목 객체 구하기
        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 = [0123456789102328293031]  # 요청 필드
        objRq.SetInputValue(0, rqField)  # 요청 필드
        objRq.SetInputValue(1, codes)  # 종목코드 or 종목코드 리스트
        objRq.BlockRequest()
cs



저작자 표시
신고