Showing
13 changed files
with
412 additions
and
1 deletions
proj/data_collector.py
0 → 100644
proj/library/.idea/.gitignore
0 → 100644
proj/library/.idea/library.iml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<module type="PYTHON_MODULE" version="4"> | ||
3 | + <component name="NewModuleRootManager"> | ||
4 | + <content url="file://$MODULE_DIR$" /> | ||
5 | + <orderEntry type="inheritedJdk" /> | ||
6 | + <orderEntry type="sourceFolder" forTests="false" /> | ||
7 | + </component> | ||
8 | + <component name="TestRunnerService"> | ||
9 | + <option name="PROJECT_TEST_RUNNER" value="pytest" /> | ||
10 | + </component> | ||
11 | +</module> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
proj/library/.idea/misc.xml
0 → 100644
proj/library/.idea/modules.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project version="4"> | ||
3 | + <component name="ProjectModuleManager"> | ||
4 | + <modules> | ||
5 | + <module fileurl="file://$PROJECT_DIR$/.idea/library.iml" filepath="$PROJECT_DIR$/.idea/library.iml" /> | ||
6 | + </modules> | ||
7 | + </component> | ||
8 | +</project> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
proj/library/.idea/vcs.xml
0 → 100644
No preview for this file type
No preview for this file type
proj/library/collector_api.py
0 → 100644
... | @@ -2,6 +2,12 @@ | ... | @@ -2,6 +2,12 @@ |
2 | db_id="user" | 2 | db_id="user" |
3 | db_pw="password" | 3 | db_pw="password" |
4 | db_port="3306" | 4 | db_port="3306" |
5 | +db_ip="localhost" | ||
5 | 6 | ||
6 | # 모의투자 계좌번호 | 7 | # 모의투자 계좌번호 |
7 | -account_no="8147766711" | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
8 | +test_account_no="8147766711" | ||
9 | + | ||
10 | +# 시뮬레이션을 돌리기 시작할 날짜 | ||
11 | +start_buying='20190101' | ||
12 | +simul_num1=1 | ||
13 | +simul_name1="AutoBot"+str(simul_num1)+"_Test" | ... | ... |
proj/library/error_code.py
0 → 100644
1 | +def errors(err_code): | ||
2 | + err_dict={0:('OP_ERR_NONE','정상처리'), | ||
3 | + -10:('OP_ERR_FAIL','실패'), | ||
4 | + -100:('OP_ERR_LOGIN','사용자정보교환실패'), | ||
5 | + -101:('OP_ERR_CONNECT','서버접속실패'), | ||
6 | + -102:('OP_ERR_VERSION','버전처리실패'), | ||
7 | + -103:('OP_ERR_FAIRWALL','개인방화벽실패'), | ||
8 | + -104:('OP_ERR_MEMORY','메모리보호실패'), | ||
9 | + -105:('OP_ERR_INPUT','함수입력값오류'), | ||
10 | + -106:('OP_ERR_SOCKET_CLOSED','통신연결종료'), | ||
11 | + -200:('OP_ERR_SISE_OVERFLOW','시세조회과부화'), | ||
12 | + -201:('OP_ERR_RQ_STRUCT_FAIL','전문작성초기화실패'), | ||
13 | + -202:('OP_ERR_RQ_STRING_FAIL','전문작성입력값오류'), | ||
14 | + -203:('OP_ERR_NO_DATA','데이터없음'), | ||
15 | + -204:('OP_ERR_OVER_MAX_DATA','조회가능한종목수초과'), | ||
16 | + -205:('OP_ERR_DATA_RCV_FAIL','데이터수신실패'), | ||
17 | + -206:('OP_ERR_OVER_MAX_FID','조회가능한FID수초과'), | ||
18 | + -207:('OP_ERR_REAL_CANCEL','실시간해제오류'), | ||
19 | + -300:('OP_ERR_ORD_WRONG_INPUT','입력값오류'), | ||
20 | + -301:('OP_ERR_ORD_WRONG_ACCNO','계좌비밀번호없음'), | ||
21 | + -302:('OP_ERR_OTHER_ACC_USE','타인계좌사용오류'), | ||
22 | + -303:('OP_ERR_MIS_2BILL_EXC','주문가격이20억원을초과'), | ||
23 | + -304:('OP_ERR_MIS_5BILL_EXC','주문가격이50억원을초과'), | ||
24 | + -305:('OP_ERR_MIS_1PER_EXC','주문수량이총발행주수의1 % 초과오류'), | ||
25 | + -306:('OP_ERR_MIS_3PER_EXC','주문수량이총발행주수의3 % 초과오류'), | ||
26 | + -307:('OP_ERR_SEND_FAIL','주문전송실패'), | ||
27 | + -308:('OP_ERR_ORD_OVERFLOW','주문전송과부화'), | ||
28 | + -309:('OP_ERR_MIS_300CNT_EXC','주문수량300계약초과'), | ||
29 | + -310:('OP_ERR_MIS_500CNT_EXC','주문수량500계약초과'), | ||
30 | + -340:('OP_ERR_ORD_WRONG_ACCINFO','계좌정보없음'), | ||
31 | + -500:('OP_ERR_ORD_SYMCODE_EMPTY','종목코드없음') | ||
32 | + } | ||
33 | + | ||
34 | + result=err_dict[err_code] | ||
35 | + return result | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
proj/library/open_api.py
0 → 100644
1 | +from PyQt5.QtWidgets import * | ||
2 | +from PyQt5.QAxContainer import * | ||
3 | +from PyQt5.QtCore import * | ||
4 | +from PyQt5.QtTest import * | ||
5 | +from pandas import DataFrame | ||
6 | +import sys | ||
7 | +import pymysql | ||
8 | +from sqlalchemy import * | ||
9 | + | ||
10 | +import config | ||
11 | +import error_code | ||
12 | + | ||
13 | +TR_REQ_TIME_INTERVAL = 3600 | ||
14 | + | ||
15 | +class OpenApi(QAxWidget): | ||
16 | + def __init__(self): | ||
17 | + super().__init__() | ||
18 | + | ||
19 | + # event_loop list | ||
20 | + self.tr_event_loop=QEventLoop() | ||
21 | + self.login_event_loop=QEventLoop() | ||
22 | + | ||
23 | + # variable list | ||
24 | + self.account_no=None # 계좌번호 | ||
25 | + self.ohlcv = None # 거래정보(시가,종가,고가,저가,거래량) | ||
26 | + self.remained_data=None # 다음페이지 존재여부 | ||
27 | + | ||
28 | + # screen number list | ||
29 | + self.screen_data_req="0101" # 거래 데이터 | ||
30 | + | ||
31 | + self.create_instance() | ||
32 | + self._signal_slots() | ||
33 | + self.comm_connect() # 로그인 | ||
34 | + self.get_account_info() # 계좌정보 가져오기 | ||
35 | + self.variable_setting() | ||
36 | + | ||
37 | + def variable_setting(self): | ||
38 | + print("============variable setting function===============") | ||
39 | + # 모의투자 | ||
40 | + if self.account_no==config.test_account_no: | ||
41 | + print("test investment!!!!!!") | ||
42 | + self.simul_num=config.simul_num1 | ||
43 | + self.db_name_setting(config.simul_name1) | ||
44 | + | ||
45 | + | ||
46 | + # AutoBot Database 생성 | ||
47 | + def create_database(self,cursor): | ||
48 | + print("create database") | ||
49 | + query="create database {}" | ||
50 | + cursor.execute(query.format(self.db_name)) | ||
51 | + | ||
52 | + # AutoBot Database가 존재하는지 여부를 확인 | ||
53 | + def is_BotTable_exist(self,cursor): | ||
54 | + query="select 1 from information_schema.schemata where schema_name='{}'" | ||
55 | + print(query.format(self.db_name)) | ||
56 | + if (cursor.execute(query.format(self.db_name))): | ||
57 | + print("%s 데이터베이스가 존재합니다.",self.db_name) | ||
58 | + return True | ||
59 | + else: | ||
60 | + return False | ||
61 | + | ||
62 | + def db_name_setting(self,db_name): | ||
63 | + self.db_name=db_name | ||
64 | + print("db_name : %s"%self.db_name) | ||
65 | + conn = pymysql.connect( | ||
66 | + host=config.db_ip, | ||
67 | + port=int(config.db_port), | ||
68 | + user=config.db_id, | ||
69 | + password=config.db_pw, | ||
70 | + charset='utf8' | ||
71 | + ) | ||
72 | + cursor=conn.cursor() | ||
73 | + if not self.is_BotTable_exist(cursor): | ||
74 | + self.create_database(cursor) | ||
75 | + self.engine_bot=create_engine( | ||
76 | + "mysql+pymysql://" + config.db_id + ":" + config.db_pw + "@" + config.db_ip + ":" + config.db_port + "/" + | ||
77 | + db_name, encoding='utf-8') | ||
78 | + self.db_setting(cursor) | ||
79 | + | ||
80 | + def db_setting(self,cursor): | ||
81 | + # 생성해야 할 데이터베이스 리스트 | ||
82 | + to_create=['stock_info','daily_info','minute_info'] | ||
83 | + query="select schema_name from information_schema.schemata" | ||
84 | + cursor.execute(query) | ||
85 | + result=cursor.fetchall() | ||
86 | + create_db="CREATE DATABASE {}" | ||
87 | + # 생성되어 있는 데이터베이스 리스트 | ||
88 | + created=list() | ||
89 | + for item in result: | ||
90 | + created.append(item[0]) | ||
91 | + # 생성하야 할 데이터베이스가 존재하지 않는 경우 새로 생성 | ||
92 | + for db in to_create: | ||
93 | + if db not in created: | ||
94 | + print(db,"데이터베이스가 존재하지 않습니다. 데이터베이스를 새로 생성합니다.") | ||
95 | + create_query=create_db.format(db) | ||
96 | + cursor.execute(create_query) | ||
97 | + print(db,"데이터베이스 생성 완료!") | ||
98 | + | ||
99 | + # 주식일봉차트조회요청 | ||
100 | + def _opt10081(self,sRQName,sTrCode): | ||
101 | + QTest.qWait(TR_REQ_TIME_INTERVAL) | ||
102 | + | ||
103 | + cnt=self._get_repeat_cnt(sTrCode,sRQName) | ||
104 | + | ||
105 | + for i in range(cnt): | ||
106 | + date=self._get_comm_data(sTrCode,sRQName,i,"일자") | ||
107 | + open=self._get_comm_data(sTrCode,sRQName,i,"시가") | ||
108 | + high=self._get_comm_data(sTrCode,sRQName,i,"고가") | ||
109 | + low=self._get_comm_data(sTrCode,sRQName,i,"저가") | ||
110 | + close=self._get_comm_data(sTrCode,sRQName,i,"현재가") | ||
111 | + volume=self._get_comm_data(sTrCode,sRQName,i,"거래량") | ||
112 | + | ||
113 | + self.ohlcv['date'].append(date) | ||
114 | + self.ohlcv['open'].append(int(open)) | ||
115 | + self.ohlcv['high'].append(int(high)) | ||
116 | + self.ohlcv['low'].append(int(low)) | ||
117 | + self.ohlcv['close'].append(int(close)) | ||
118 | + self.ohlcv['volumn'].append(int(volume)) | ||
119 | + | ||
120 | + # TR 요청을 처리하는 slot | ||
121 | + # param sScrNo: 스크린번호 | ||
122 | + # sRQName: 요청했을 때 지은 이름 | ||
123 | + # sTrCode: 요청 id, tr코드 | ||
124 | + # sRecordName: 레코드 이름 | ||
125 | + # sPrevNext: 다음 페이지가 있는지 여부. "2" : 다음페이지 존재, "0" or "" : 다음페이지 없음 | ||
126 | + def _receive_tr_data(self,sScrNo,sRQName,sTrCode,sRecordName,sPrevNext): | ||
127 | + if sPrevNext=='2': | ||
128 | + self.remained_data=True | ||
129 | + else: | ||
130 | + self.remained_data=False | ||
131 | + | ||
132 | + if sRQName=="opt10081": | ||
133 | + print("==============주식일봉차트조회요청================") | ||
134 | + self._opt10081(sRQName,sTrCode) | ||
135 | + elif sRQName=="opw0001_req": | ||
136 | + return | ||
137 | + elif sRQName=="opw00018_req": | ||
138 | + return | ||
139 | + elif sRQName=="opt10074_req": | ||
140 | + return | ||
141 | + elif sRQName=="opw00015_req": | ||
142 | + return | ||
143 | + elif sRQName=="opt10076_req": | ||
144 | + return | ||
145 | + elif sRQName=="opt10073_req": | ||
146 | + return | ||
147 | + | ||
148 | + try: | ||
149 | + self.tr_event_loop.exit() | ||
150 | + except AttributeError: | ||
151 | + pass | ||
152 | + | ||
153 | + # 특정 종목의 일자별 거래 데이터 조회 함수 | ||
154 | + # param : code - 종목코드 | ||
155 | + # start - 기준일자 | ||
156 | + # return : df - 특정종목의 일자별 거래 데이터 목록 | ||
157 | + def get_total_data(self,code,start): | ||
158 | + self.ohlcv = {'date': [], 'open': [], 'high': [], 'low': [], 'close': [], 'volume': []} | ||
159 | + | ||
160 | + self._set_input_value("종목코드",code) | ||
161 | + self._set_input_value("기준일자",start) | ||
162 | + self._set_input_value("수정주가구분",1) | ||
163 | + self._comm_rq_data("opt10081_req","opt10081",0,self.screen_data_req) | ||
164 | + | ||
165 | + while self.remained_data: | ||
166 | + self._set_input_value("종목코드",code) | ||
167 | + self._set_input_value("기준일자",start) | ||
168 | + self._set_input_value("수정주가구분",1) | ||
169 | + self._comm_rq_data("opt10081_req","opt10081",2,self.screen_data_req) | ||
170 | + | ||
171 | + # 데이터가 없거나, date=''일 경우 empty list를 반환 | ||
172 | + if len(self.ohlcv)==0: | ||
173 | + return [] | ||
174 | + if self.ohlcv['date']=='': | ||
175 | + return [] | ||
176 | + | ||
177 | + # 데이터를 DataFrame형태로 저장 및 반환 | ||
178 | + df=DataFrame(self.ohlcv,columns=['open','high','low','close','volume'],index=self.ohlcv['date']) | ||
179 | + | ||
180 | + return df | ||
181 | + | ||
182 | + # 특정 종목의 특정 날짜의 시가,종가,고가,저가,거래량 중 특정한 데이터를 반환하는 함수 | ||
183 | + # param : code - 종목코드 | ||
184 | + # : date - 조회날짜 | ||
185 | + # : option - open(시가)/close(종가)/high(고가)/low(저가)/volume(거래량) | ||
186 | + # return : 조회한 데이터에 해당하는 값 | ||
187 | + # 값이 없거나, option이 오류일 경우 return False | ||
188 | + def get_oneday_option_data(self,code,date,option): | ||
189 | + self.ohlcv = {'date': [], 'open': [], 'high': [], 'low': [], 'close': [], 'volume': []} | ||
190 | + | ||
191 | + self._set_input_value("종목코드",code) | ||
192 | + self._set_input_value("기준일자",date) | ||
193 | + self._set_input_value("수정주가구분",1) | ||
194 | + self._comm_rq_data("opt10081_req","opt10081",0,self.screen_data_req) | ||
195 | + | ||
196 | + if self.ohlcv['date']=="": | ||
197 | + return False | ||
198 | + | ||
199 | + df = DataFrame(self.ohlcv, columns=['open', 'high', 'low', 'close', 'volume'], index=self.ohlcv['date']) | ||
200 | + if option == 'open': | ||
201 | + return df.iloc[0, 0] | ||
202 | + elif option == 'high': | ||
203 | + return df.iloc[0, 1] | ||
204 | + elif option == 'low': | ||
205 | + return df.iloc[0, 2] | ||
206 | + elif option == 'close': | ||
207 | + return df.iloc[0, 3] | ||
208 | + elif option == 'volume': | ||
209 | + return df.iloc[0, 4] | ||
210 | + else: | ||
211 | + return False | ||
212 | + | ||
213 | + # 사용자의 계좌정보 저장 및 출력 | ||
214 | + def get_account_info(self): | ||
215 | + account=self.get_login_info("ACCNO") | ||
216 | + self.account_no=account.split(";")[0] | ||
217 | + print("======== 계좌번호 : ",self.account_no,"========") | ||
218 | + | ||
219 | + # 원하는 사용자 정보 반환 | ||
220 | + # param : tag - ACCNO - 보유계좌리스트 | ||
221 | + # ACCOUNT_CNT - 보유계좌 수 | ||
222 | + # USER_ID - 사용자 ID | ||
223 | + # USER_NAME - 사용자 이름 | ||
224 | + # KEY_BSECGB - 키보드 보안 해제 여부 (0 : 정상, 1: 해지) | ||
225 | + # FIREW_SECGB - 방화벽 설정여부 (0 : 미설정, 1: 설정, 2 : 해지) | ||
226 | + # GetServerGubun - 접속서버 구분 (1 : 모의투자, 나머지 : 실서버) | ||
227 | + # return : tag를 통해 요청한 정보(ret) 반환 | ||
228 | + def get_login_info(self,tag): | ||
229 | + try: | ||
230 | + ret=self.dynamicCall("GetLoginInfo(QString)",tag) | ||
231 | + return ret | ||
232 | + except Exception as e: | ||
233 | + print(e) | ||
234 | + sys.exit() | ||
235 | + | ||
236 | + # 키움 api를 사용하기 위한 ocx controller 저장 | ||
237 | + def create_instance(self): | ||
238 | + try: | ||
239 | + self.setControl("KHOPENAPI.KHOpenAPICtrl.1") | ||
240 | + except Exception as e: | ||
241 | + print(e) | ||
242 | + sys.exit() | ||
243 | + | ||
244 | + # event처리를 위한 slot | ||
245 | + def _signal_slots(self): | ||
246 | + try: | ||
247 | + self.OnEventConnect.connect(self._login_slot) | ||
248 | + self.OnReceiveTrData.connect(self._receive_tr_data) | ||
249 | + except Exception as e: | ||
250 | + print(e) | ||
251 | + sys.exit() | ||
252 | + | ||
253 | + # 수동 로그인설정인 경우 로그인창을 출력해서 로그인을 시도 | ||
254 | + # 자동로그인 설정인 경우 로그인창 출력없이 로그인을 시도 | ||
255 | + def comm_connect(self): | ||
256 | + try: | ||
257 | + self.dynamicCall("CommConnect()") | ||
258 | + self.login_event_loop.exec_() | ||
259 | + except Exception as e: | ||
260 | + print(e) | ||
261 | + sys.exit() | ||
262 | + | ||
263 | + # 로그인 이벤트 처리 slot | ||
264 | + # param : code - 로그인 성공 시 0 | ||
265 | + # 실패 시 에러코드 출력 | ||
266 | + def _login_slot(self,code): | ||
267 | + try: | ||
268 | + result=error_code.errors(code) | ||
269 | + if code==0: | ||
270 | + print("Connected",result[1]) | ||
271 | + else: | ||
272 | + print("Failed to connect",result[1]) | ||
273 | + self.login_event_loop.exit() | ||
274 | + except Exception as e: | ||
275 | + print(e) | ||
276 | + sys.exit() | ||
277 | + | ||
278 | + # 조회요청시 TR의 Input값을 지정하는 함수 | ||
279 | + # param : sId - TR에 명시된 Input이름 | ||
280 | + # svalue - Input이름으로 지정한 값 | ||
281 | + def _set_input_value(self,sId,sValue): | ||
282 | + try: | ||
283 | + self.dynamicCall("SetInputValue(QString, QString)", sId, sValue) | ||
284 | + except Exception as e: | ||
285 | + print(e) | ||
286 | + sys.exit() | ||
287 | + | ||
288 | + # 조회요청함수 | ||
289 | + # param : sRQName - 사용자 구분명 | ||
290 | + # sTrCode - 조회하려는 TR이름 | ||
291 | + # nPrevNext - 연속조회여부 | ||
292 | + # sScreenNo - 화면번호 | ||
293 | + def _comm_rq_data(self,sRQName,sTrData,nPrevNext,sScrNo): | ||
294 | + self.dynamicCall("CommRqData(QString, QString, int, QString", sRQName, sTrData, nPrevNext, sScrNo) | ||
295 | + self.tr_event_loop.exec_() | ||
296 | + | ||
297 | + # OnReceiveTRData()이벤트가 호출될때 조회데이터를 얻어오는 함수 | ||
298 | + # param : sTrCode - TR 이름 | ||
299 | + # sRecordName - 레코드이름 | ||
300 | + # nIndex - TR반복부 | ||
301 | + # sItemName - TR에서 얻어오려는 출력항목이름 | ||
302 | + def _get_comm_data(self,sTrCode,sRecordName,nIndex,sItemName): | ||
303 | + ret = self.dynamicCall("GetCommData(QString, QString, int, QString", sTrCode, sRecordName, nIndex, sItemName) | ||
304 | + return ret.strip() | ||
305 | + | ||
306 | + # 조회수신한 멀티데이터의 갯수(반복)을 얻는다 | ||
307 | + # param : sTrCode - tr이름 | ||
308 | + # sRecordName - 레코드 이름 | ||
309 | + def _get_repeat_cnt(self,sTrCode,sRecordName): | ||
310 | + try: | ||
311 | + ret=self.dynamicCall("GetRepeatCnt(QString, QString)",sTrCode,sRecordName) | ||
312 | + return ret | ||
313 | + except Exception as e: | ||
314 | + print(e) | ||
315 | + sys.exit() | ||
316 | + | ||
317 | +if __name__ == "__main__": | ||
318 | + app = QApplication(sys.argv) | ||
319 | + api=OpenApi() | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment