목적:
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 종목)
서비스에 대한 자세한 설명은 아래 링크에서 확인 가능~
요청 필드와 종목리스트를 세팅해서 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()
'개발 > 파이썬' 카테고리의 다른 글
[파이썬] 파일 옮기기 (0) | 2017.12.30 |
---|---|
[파이썬] 폴더 크기 구하기 (0) | 2017.12.30 |
[파이썬] 리스트/딕셔너리 정렬하기 (0) | 2017.12.11 |
[파이썬] 안드로이드 프로젝트에서 자바 클래스 레퍼런스 카운트 구하기 (0) | 2017.10.14 |
[파이썬] 폴더 명 일괄 변경 예제 (0) | 2017.10.11 |
[파이썬] 주어진 폴더 파일/크기 비교 예제 (0) | 2017.10.11 |
댓글