ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [파이썬] Creon Plus 를 이용해서 KOSPI200 종목 시세 가져오기
    개발/파이썬 2017.10.15 15:44

    목적: 

    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



    댓글 0

Designed by black7375.