├── .gitignore
├── .idea
├── PyTrader.iml
├── encodings.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── README.md
├── buy_list.txt
├── convert_data.py
├── logging.conf
├── pykiwoom
├── __init__.py
├── kiwoom
│ └── __init__.py
└── wrapper
│ └── __init__.py
├── pymon.py
├── pytrader.png
├── pytrader.py
├── pytrader.ui
├── save_data.py
├── sell_list.txt
├── test.py
├── test_kiwoom.py
├── update_version.py
└── webreader.py
/.gitignore:
--------------------------------------------------------------------------------
1 | account.txt
2 | stock.db
3 | kiwoom.log
4 | *.pyc
5 | .idea
6 |
--------------------------------------------------------------------------------
/.idea/PyTrader.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
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 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 | 1487475077022
419 |
420 |
421 | 1487475077022
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 키움 Open API + 를 이용한 시스템트레이딩
2 |
3 | ## 개발환경
4 | - Anaconda3-4.3.0.1 32bit (Python 3.6, PyQt5.6, pywinauto, pandas)
5 | - Windows 7/10
6 |
7 | ## 로그인설정
8 | - account.txt에 로그인정보 입력
9 | - 사용자id
10 | - 로그인 pw
11 | - 공인인증서 pw
12 |
13 | ## 사용법
14 | - 장 개시 전 매수할 종목을 buy_list.txt에 매도할 종목을 sell_list.txt에 기록.
15 | - 장 개시 후 pytrader.py를 실행하면 buy_list.txt에 있는 종목을 매수, sell_list.txt에 있는 종목을 매도.
16 |
17 | 
18 |
19 | ## 딥러닝을 이용한 매수/매도 종목 선정
20 | - [PyMLT](https://github.com/didw/PyMLT)
21 |
22 | ## 참고사이트
23 | - [파이썬을 이용한 시스템 트레이딩(기초편)](https://wikidocs.net/book/110)
24 |
25 |
--------------------------------------------------------------------------------
/buy_list.txt:
--------------------------------------------------------------------------------
1 | 매수;223040;시장가;26;0;매수전;
2 | 매수;237750;시장가;9;0;매수전;
3 | 매수;240810;시장가;84;0;매수전;
4 | 매수;241520;시장가;23;0;매수전;
5 | 매수;001290;시장가;85;0;매수전;
6 | 매수;003010;시장가;26;0;매수전;
7 | 매수;003160;시장가;7;0;매수전;
8 | 매수;005387;시장가;72;0;매수전;
9 | 매수;007630;시장가;17;0;매수전;
10 | 매수;008060;시장가;92;0;매수전;
11 | 매수;008500;시장가;256;0;매수전;
12 | 매수;010955;시장가;3;0;매수전;
13 | 매수;010960;시장가;129;0;매수전;
14 | 매수;012690;시장가;149;0;매수전;
15 | 매수;012750;시장가;33;0;매수전;
16 | 매수;025000;시장가;1016;0;매수전;
17 | 매수;037270;시장가;4;0;매수전;
18 | 매수;090370;시장가;159;0;매수전;
19 | 매수;122630;시장가;25;0;매수전;
20 | 매수;001840;시장가;144;0;매수전;
21 | 매수;016170;시장가;81;0;매수전;
22 | 매수;019540;시장가;68;0;매수전;
23 | 매수;024880;시장가;96;0;매수전;
24 | 매수;038340;시장가;173;0;매수전;
25 | 매수;043370;시장가;110;0;매수전;
26 | 매수;043590;시장가;1442;0;매수전;
27 | 매수;049480;시장가;13;0;매수전;
28 | 매수;052420;시장가;22;0;매수전;
29 | 매수;056000;시장가;67;0;매수전;
30 | 매수;068270;시장가;20;0;매수전;
31 | 매수;078130;시장가;129;0;매수전;
32 | 매수;082920;시장가;70;0;매수전;
33 | 매수;083470;시장가;50;0;매수전;
34 | 매수;109080;시장가;14;0;매수전;
35 | 매수;109740;시장가;125;0;매수전;
36 | 매수;213090;시장가;44;0;매수전;
37 | 매수;214430;시장가;144;0;매수전;
38 |
--------------------------------------------------------------------------------
/convert_data.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import sqlite3
3 | import glob
4 | import h5py
5 |
6 | def convert_sql_to_csv():
7 | con = sqlite3.connect("../data/stock.db")
8 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
9 | code = code_list[0][0]
10 | for code in code_list:
11 | code = code[0]
12 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자').sort_index()
13 | data.to_csv('../data/stocks/%s.csv' % code)
14 |
15 | def convert_sql_to_h5():
16 | con = sqlite3.connect("../data/stock.db")
17 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
18 | code = code_list[0][0]
19 | for code in code_list:
20 | code = code[0]
21 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자').sort_index()
22 | data.to_hdf('../data/h5/%s.h5'%code,'df',mode='w',data_columns=True)
23 |
24 | def read_h5():
25 | code_list = glob.glob('../data/h5/*')
26 | for code in code_list[:10]:
27 | h = h5py.File(code)
28 | print(h)
29 |
30 | if __name__ == '__main__':
31 | read_h5()
32 |
--------------------------------------------------------------------------------
/logging.conf:
--------------------------------------------------------------------------------
1 | [loggers]
2 | keys=root,infoLogger
3 |
4 | [handlers]
5 | keys=simpleHandler,fileHandler
6 |
7 | [formatters]
8 | keys=simpleFormatter
9 |
10 | [logger_root]
11 | level=NOTSET
12 | handlers=
13 |
14 | [logger_infoLogger]
15 | level=DEBUG
16 | handlers=simpleHandler,fileHandler
17 | qualname=Kiwoom
18 | propagate=1
19 |
20 | [handler_simpleHandler]
21 | class=StreamHandler
22 | formatter=simpleFormatter
23 | args=(sys.stdout,)
24 |
25 | [handler_fileHandler]
26 | class=FileHandler
27 | formatter=simpleFormatter
28 | args=('kiwoom.log', 'w')
29 |
30 | [formatter_simpleFormatter]
31 | format=%(asctime)s - %(name)s - %(levelname)-8s - %(message)s
32 | datefmt=
--------------------------------------------------------------------------------
/pykiwoom/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didw/PyTrader/eae75171e3e157ee58012d7af65f8270ce792e74/pykiwoom/__init__.py
--------------------------------------------------------------------------------
/pykiwoom/kiwoom/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Kiwoom 클래스는 OCX를 통해 API 함수를 호출할 수 있도록 구현되어 있습니다.
3 | OCX 사용을 위해 QAxWidget 클래스를 상속받아서 구현하였으며,
4 | 주식(현물) 거래에 필요한 메서드들만 구현하였습니다.
5 |
6 | author: Jongyeol Yang
7 | last edit: 2017. 02. 23
8 | """
9 | import sys
10 | import logging
11 | import logging.config
12 | from PyQt5.QAxContainer import QAxWidget
13 | from PyQt5.QtCore import QEventLoop
14 | from PyQt5.QtWidgets import QApplication
15 | from pandas import DataFrame
16 | import time
17 | import numpy as np
18 | from datetime import datetime
19 |
20 | class Kiwoom(QAxWidget):
21 | def __init__(self):
22 | super().__init__()
23 |
24 | self.setControl("KHOPENAPI.KHOpenAPICtrl.1")
25 |
26 | # Loop 변수
27 | # 비동기 방식으로 동작되는 이벤트를 동기화(순서대로 동작) 시킬 때
28 | self.login_loop = None
29 | self.request_loop = None
30 | self.order_loop = None
31 | self.condition_loop = None
32 |
33 | # 서버구분
34 | self.server_gubun = None
35 |
36 | # 조건식
37 | self.condition = None
38 |
39 | # 에러
40 | self.error = None
41 |
42 | # 주문번호
43 | self.order_no = ""
44 |
45 | # 조회
46 | self.inquiry = 0
47 |
48 | # 서버에서 받은 메시지
49 | self.msg = ""
50 |
51 | # 예수금 d+2
52 | self.data_opw00001 = 0
53 |
54 | # 보유종목 정보
55 | self.data_opw00018 = {'account_evaluation': [], 'stocks': []}
56 |
57 | # 주가상세정보
58 | self.data_opt10081 = [] * 15
59 | self.data_opt10086 = [] * 23
60 |
61 | # signal & slot
62 | self.OnEventConnect.connect(self.event_connect)
63 | self.OnReceiveTrData.connect(self.on_receive_tr_data)
64 | self.OnReceiveChejanData.connect(self.on_receive_chejan_data)
65 | self.OnReceiveRealData.connect(self.receive_real_data)
66 | self.OnReceiveMsg.connect(self.receive_msg)
67 | self.OnReceiveConditionVer.connect(self.receive_condition_ver)
68 | self.OnReceiveTrCondition.connect(self.receive_tr_condition)
69 | self.OnReceiveRealCondition.connect(self.receive_real_condition)
70 |
71 | # 로깅용 설정파일
72 | logging.config.fileConfig('logging.conf')
73 | self.log = logging.getLogger('Kiwoom')
74 |
75 | ###############################################################
76 | # 로깅용 메서드 정의 #
77 | ###############################################################
78 |
79 | def logger(origin):
80 | def wrapper(*args, **kwargs):
81 | args[0].log.debug('{} args - {}, kwargs - {}'.format(origin.__name__, args, kwargs))
82 | return origin(*args, **kwargs)
83 |
84 | return wrapper
85 |
86 | ###############################################################
87 | # 이벤트 정의 #
88 | ###############################################################
89 |
90 | def event_connect(self, return_code):
91 | """
92 | 통신 연결 상태 변경시 이벤트
93 |
94 | return_code 0이면 로그인 성공
95 | 그 외에는 ReturnCode 클래스 참조.
96 |
97 | :param return_code: int
98 | """
99 | try:
100 | if return_code == ReturnCode.OP_ERR_NONE:
101 | if self.get_login_info("GetServerGubun", True):
102 | self.msg += "실서버 연결 성공" + "\r\n\r\n"
103 | else:
104 | self.msg += "모의투자서버 연결 성공" + "\r\n\r\n"
105 | else:
106 | self.msg += "연결 끊김: 원인 - " + ReturnCode.CAUSE[return_code] + "\r\n\r\n"
107 | except Exception as error:
108 | self.log.error('eventConnect {}'.format(error))
109 | finally:
110 | # commConnect() 메서드에 의해 생성된 루프를 종료시킨다.
111 | # 로그인 후, 통신이 끊길 경우를 대비해서 예외처리함.
112 | try:
113 | self.login_loop.exit()
114 | except AttributeError:
115 | pass
116 |
117 | def receive_msg(self, screen_no, request_name, tr_code, msg):
118 | """
119 | 수신 메시지 이벤트
120 |
121 | 서버로 어떤 요청을 했을 때(로그인, 주문, 조회 등), 그 요청에 대한 처리내용을 전달해준다.
122 |
123 | :param screen_no: string - 화면번호(4자리, 사용자 정의, 서버에 조회나 주문을 요청할 때 이 요청을 구별하기 위한 키값)
124 | :param request_name: string - TR 요청명(사용자 정의)
125 | :param tr_code: string
126 | :param msg: string - 서버로 부터의 메시지
127 | """
128 |
129 | if request_name == "서버구분":
130 |
131 | if msg.find('모의투자') < 0:
132 | self.server_gubun = 1
133 |
134 | else:
135 | self.server_gubun = 0
136 |
137 | try:
138 | self.order_loop.exit()
139 | except AttributeError:
140 | pass
141 | finally:
142 | return
143 |
144 | self.msg += request_name + ": " + msg + "\r\n\r\n"
145 |
146 | def on_receive_tr_data(self, screen_no, request_name, tr_code, record_name, inquiry, unused0, unused1, unused2,
147 | unused3):
148 | """
149 | TR 수신 이벤트
150 |
151 | 조회요청 응답을 받거나 조회데이터를 수신했을 때 호출됩니다.
152 | request_name tr_code comm_rq_data()메소드의 매개변수와 매핑되는 값 입니다.
153 | 조회데이터는 이 이벤트 메서드 내부에서 comm_get_data() 메서드를 이용해서 얻을 수 있습니다.
154 |
155 | :param screen_no: string - 화면번호(4자리)
156 | :param request_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 requestName)
157 | :param tr_code: string
158 | :param record_name: string
159 | :param inquiry: string - 조회('0': 남은 데이터 없음, '2': 남은 데이터 있음)
160 | """
161 |
162 | print("on_receive_tr_data 실행: screen_no: %s, request_name: %s, tr_code: %s, record_name: %s, inquiry: %s" % (
163 | screen_no, request_name, tr_code, record_name, inquiry))
164 |
165 | # 주문번호와 주문루프
166 | self.order_no = self.comm_get_data(tr_code, "", request_name, 0, "주문번호")
167 |
168 | try:
169 | self.order_loop.exit()
170 | except AttributeError:
171 | pass
172 |
173 | self.inquiry = inquiry
174 |
175 | if request_name == "관심종목정보요청":
176 | data = self.get_comm_data_ex(tr_code, "관심종목정보")
177 |
178 | """ commGetData
179 | cnt = self.getRepeatCnt(trCode, requestName)
180 |
181 | for i in range(cnt):
182 | data = self.commGetData(trCode, "", requestName, i, "종목명")
183 | print(data)
184 | """
185 |
186 | if request_name == "주식일봉차트조회요청":
187 | data = self.get_comm_data_ex(tr_code, "주식일봉차트조회")
188 | if data is not None:
189 | data = list(map(lambda x: list(map(lambda y: y.replace('+','').replace('--','-'), x)), np.array(data)[:,1:8].tolist()))
190 | data = list(map(lambda x: list(map(lambda y: int(y) if y != '' else 0, x)), data))
191 | self.data_opt10081.extend(data)
192 | date = str(data[0][3])
193 | dt = datetime.strptime(date, "%Y%m%d")
194 | if dt <= self.start_date:
195 | self.inquiry = 0
196 | if inquiry == "0" or self.inquiry == 0:
197 | col_name = ['현재가', '거래량', '거래대금', '일자', '시가', '고가', '저가']
198 | self.data_opt10081 = DataFrame(self.data_opt10081, columns=col_name)
199 |
200 | if request_name == "일별주가요청":
201 | data = self.get_comm_data_ex(tr_code, "일별주가요청")
202 | if data is not None:
203 | data = list(map(lambda x: list(map(lambda y: y.replace('+','').replace('--','-'), x)), data))
204 | data = list(map(lambda x: list(map(lambda y: float(y) if y != '' else 0, x)), data))
205 | self.data_opt10086.extend(data)
206 | date = str(int(data[0][0]))
207 | dt = datetime.strptime(date, "%Y%m%d")
208 | if dt <= self.start_date:
209 | self.inquiry = 0
210 | if inquiry == "0" or self.inquiry == 0:
211 | col_name = ['일자', '시가', '고가', '저가', '종가', '전일비', '등락률', '거래량', '금액(백만)', '신용비', '개인',
212 | '기관', '외인수량', '외국계', '프로그램', '외인비', '체결강도', '외인보유', '외인비중', '외인순매수',
213 | '기관순매수', '개인순매수', '신용잔고율']
214 | self.data_opt10086 = DataFrame(self.data_opt10086, columns=col_name)
215 |
216 | if request_name == "예수금상세현황요청":
217 | estimate_day2_deposit = self.comm_get_data(tr_code, "", request_name, 0, "d+2추정예수금")
218 | estimate_day2_deposit = self.change_format(estimate_day2_deposit)
219 | self.data_opw00001 = estimate_day2_deposit
220 |
221 | if request_name == '계좌평가잔고내역요청':
222 | # 계좌 평가 정보
223 | account_evaluation = []
224 | key_list = ["총매입금액", "총평가금액", "총평가손익금액", "총수익률(%)", "추정예탁자산"]
225 |
226 | for key in key_list:
227 | value = self.comm_get_data(tr_code, "", request_name, 0, key)
228 |
229 | if key.startswith("총수익률"):
230 | value = self.change_format(value, 1)
231 | else:
232 | value = self.change_format(value)
233 | account_evaluation.append(value)
234 | self.data_opw00018['account_evaluation'] = account_evaluation
235 |
236 | # 보유 종목 정보
237 | cnt = self.get_repeat_cnt(tr_code, request_name)
238 | key_list = ["종목명", "보유수량", "매입가", "현재가", "평가손익", "수익률(%)", "종목번호"]
239 | for i in range(cnt):
240 | stock = []
241 | for key in key_list:
242 | value = self.comm_get_data(tr_code, "", request_name, i, key)
243 | if key.startswith("수익률"):
244 | value = self.change_format(value, 2)
245 | elif key != "종목명" and key != "종목번호":
246 | value = self.change_format(value)
247 | stock.append(value)
248 | self.data_opw00018['stocks'].append(stock)
249 | try:
250 | self.request_loop.exit()
251 | except AttributeError:
252 | pass
253 |
254 | def receive_real_data(self, code, real_type, real_data):
255 | """
256 | 실시간 데이터 수신 이벤트
257 |
258 | 실시간 데이터를 수신할 때 마다 호출되며,
259 | set_real_reg() 메서드로 등록한 실시간 데이터도 이 이벤트 메서드에 전달됩니다.
260 | get_comm_real_data() 메서드를 이용해서 실시간 데이터를 얻을 수 있습니다.
261 |
262 | :param code: string - 종목코드
263 | :param real_type: string - 실시간 타입(KOA의 실시간 목록 참조)
264 | :param real_data: string - 실시간 데이터 전문
265 | """
266 |
267 | try:
268 | self.log.debug("[receiveRealData]")
269 | self.log.debug("({})".format(real_type))
270 |
271 | if real_type not in RealType.REALTYPE:
272 | return
273 |
274 | data = []
275 |
276 | if code != "":
277 | data.append(code)
278 | codeOrNot = code
279 | else:
280 | codeOrNot = real_type
281 |
282 | for fid in sorted(RealType.REALTYPE[real_type].keys()):
283 | value = self.get_comm_real_data(codeOrNot, fid)
284 | data.append(value)
285 |
286 | # TODO: DB에 저장
287 | self.log.debug(data)
288 |
289 | except Exception as e:
290 | self.log.error('{}'.format(e))
291 |
292 | def on_receive_chejan_data(self, gubun, item_cnt, fid_list):
293 | print("gubun: ", gubun)
294 | print(self.GetChejanData(9203))
295 | print(self.GetChejanData(302))
296 | print(self.GetChejanData(900))
297 | print(self.GetChejanData(901))
298 |
299 | def get_codelist_by_market(self, market):
300 | func = 'GetCodeListByMarket("%s")' % market
301 | codes = self.dynamicCall(func)
302 | return codes.split(';')
303 |
304 | ###############################################################
305 | # 메서드 정의: 로그인 관련 메서드 #
306 | ###############################################################
307 |
308 | def comm_connect(self):
309 | """
310 | 로그인을 시도합니다.
311 |
312 | 수동 로그인일 경우, 로그인창을 출력해서 로그인을 시도.
313 | 자동 로그인일 경우, 로그인창 출력없이 로그인 시도.
314 | """
315 | self.dynamicCall("CommConnect()")
316 | self.login_loop = QEventLoop()
317 | self.login_loop.exec_()
318 |
319 | def get_connect_state(self):
320 | """
321 | 현재 접속상태를 반환합니다.
322 |
323 | 반환되는 접속상태는 아래와 같습니다.
324 | 0: 미연결, 1: 연결
325 |
326 | :return: int
327 | """
328 | ret = self.dynamicCall("GetConnectState()")
329 | return ret
330 |
331 | def get_login_info(self, tag, is_connect_state=False):
332 | """
333 | 사용자의 tag에 해당하는 정보를 반환한다.
334 |
335 | tag에 올 수 있는 값은 아래와 같다.
336 | ACCOUNT_CNT: 전체 계좌의 개수를 반환한다.
337 | ACCNO: 전체 계좌 목록을 반환한다. 계좌별 구분은 ;(세미콜론) 이다.
338 | USER_ID: 사용자 ID를 반환한다.
339 | USER_NAME: 사용자명을 반환한다.
340 | GetServerGubun: 접속서버 구분을 반환합니다.(0: 모의투자, 그외: 실서버)
341 |
342 | :param tag: string
343 | :param is_connect_state: bool - 접속상태을 확인할 필요가 없는 경우 True로 설정.
344 | :return: string
345 | """
346 | if not is_connect_state:
347 | if not self.get_connect_state():
348 | raise KiwoomConnectError()
349 |
350 | if not isinstance(tag, str):
351 | raise ParameterTypeError()
352 |
353 | if tag not in ['ACCOUNT_CNT', 'ACCNO', 'USER_ID', 'USER_NAME', 'GetServerGubun']:
354 | raise ParameterValueError()
355 |
356 | cmd = 'GetLoginInfo("%s")' % tag
357 | info = self.dynamicCall(cmd)
358 |
359 | if tag == 'GetServerGubun' and info == "":
360 | if self.server_gubun == None:
361 | account_list = self.get_login_info("ACCNO").split(';')
362 | self.send_order("서버구분", "0102", account_list[0], 1, "066570", 0, 0, "05", "")
363 | info = self.server_gubun
364 | return info
365 |
366 | #################################################################
367 | # 메서드 정의: 조회 관련 메서드 #
368 | # 시세조회, 관심종목 조회, 조건검색 등 이들의 합산 조회 횟수가 1초에 5회까지 허용 #
369 | #################################################################
370 |
371 | def set_input_value(self, id, value):
372 | self.dynamicCall("SetInputValue(QString, QString)", id, value)
373 |
374 | def comm_rq_data(self, request_name, tr_code, inquiry, screen_no):
375 | """
376 | 키움서버에 TR 요청을 한다.
377 |
378 | 조회요청메서드이며 빈번하게 조회요청시, 시세과부하 에러값 -200이 리턴된다.
379 |
380 | :param request_name: string - TR 요청명(사용자 정의)
381 | :param tr_code: string
382 | :param inquiry: int - 조회(0: 조회, 2: 남은 데이터 이어서 요청)
383 | :param screen_no: string - 화면번호(4자리)
384 | """
385 |
386 | if not self.get_connect_state():
387 | raise KiwoomConnectError()
388 |
389 | if not (isinstance(request_name, str)
390 | and isinstance(tr_code, str)
391 | and isinstance(inquiry, int)
392 | and isinstance(screen_no, str)):
393 | raise ParameterTypeError()
394 |
395 | return_code = self.dynamicCall("CommRqData(QString, QString, int, QString)", request_name, tr_code, inquiry,
396 | screen_no)
397 |
398 | if return_code != ReturnCode.OP_ERR_NONE:
399 | raise KiwoomProcessingError("comm_rq_data(): " + ReturnCode.CAUSE[return_code])
400 |
401 | # 루프 생성: receive_tr_data() 메서드에서 루프를 종료시킨다.
402 | self.request_loop = QEventLoop()
403 | self.request_loop.exec_()
404 |
405 | def comm_get_data(self, code, real_type, field_name, index, item_name):
406 | """
407 | 데이터 획득 메서드
408 |
409 | receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 조회데이터를 얻어오는 메서드입니다.
410 |
411 | :param code: string
412 | :param real_type: string - TR 요청시 ""(빈문자)로 처리
413 | :param field_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 field_name)
414 | :param index: int
415 | :param item_name: string - 수신 데이터에서 얻고자 하는 값의 키(출력항목이름)
416 | :return: string
417 | """
418 | ret = self.dynamicCall("CommGetData(QString, QString, QString, int, QString)", code, real_type,
419 | field_name, index, item_name)
420 | return ret.strip()
421 |
422 | def get_repeat_cnt(self, tr_code, request_name):
423 | """
424 | 서버로 부터 전달받은 데이터의 갯수를 리턴합니다.(멀티데이터의 갯수)
425 |
426 | receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다.
427 |
428 | 키움 OpenApi+에서는 데이터를 싱글데이터와 멀티데이터로 구분합니다.
429 | 싱글데이터란, 서버로 부터 전달받은 데이터 내에서, 중복되는 키(항목이름)가 하나도 없을 경우.
430 | 예를들면, 데이터가 '종목코드', '종목명', '상장일', '상장주식수' 처럼 키(항목이름)가 중복되지 않는 경우를 말합니다.
431 | 반면 멀티데이터란, 서버로 부터 전달받은 데이터 내에서, 일정 간격으로 키(항목이름)가 반복될 경우를 말합니다.
432 | 예를들면, 10일간의 일봉데이터를 요청할 경우 '종목코드', '일자', '시가', '고가', '저가' 이러한 항목이 10번 반복되는 경우입니다.
433 | 이러한 멀티데이터의 경우 반복 횟수(=데이터의 갯수)만큼, 루프를 돌면서 처리하기 위해 이 메서드를 이용하여 멀티데이터의 갯수를 얻을 수 있습니다.
434 |
435 | :param tr_code: string
436 | :param request_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 request_name)
437 | :return: int
438 | """
439 | ret = self.dynamicCall("GetRepeatCnt(QString, QString)", tr_code, request_name)
440 | return ret
441 |
442 | def get_comm_data_ex(self, tr_code, multi_data_name):
443 | """
444 | 멀티데이터 획득 메서드
445 |
446 | receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다.
447 |
448 | :param tr_code: string
449 | :param multi_data_name: string - KOA에 명시된 멀티데이터명
450 | :return: list - 중첩리스트
451 | """
452 |
453 | if not (isinstance(tr_code, str)
454 | and isinstance(multi_data_name, str)):
455 | raise ParameterTypeError()
456 |
457 | data = self.dynamicCall("GetCommDataEx(QString, QString)", tr_code, multi_data_name)
458 | return data
459 |
460 | def commKwRqData(self, codes, inquiry, codeCount, requestName, screenNo, typeFlag=0):
461 | """
462 | 복수종목조회 메서드(관심종목조회 메서드라고도 함).
463 |
464 | 이 메서드는 setInputValue() 메서드를 이용하여, 사전에 필요한 값을 지정하지 않는다.
465 | 단지, 메서드의 매개변수에서 직접 종목코드를 지정하여 호출하며,
466 | 데이터 수신은 receiveTrData() 이벤트에서 아래 명시한 항목들을 1회 수신하며,
467 | 이후 receiveRealData() 이벤트를 통해 실시간 데이터를 얻을 수 있다.
468 |
469 | 복수종목조회 TR 코드는 OPTKWFID 이며, 요청 성공시 아래 항목들의 정보를 얻을 수 있다.
470 |
471 | 종목코드, 종목명, 현재가, 기준가, 전일대비, 전일대비기호, 등락율, 거래량, 거래대금,
472 | 체결량, 체결강도, 전일거래량대비, 매도호가, 매수호가, 매도1~5차호가, 매수1~5차호가,
473 | 상한가, 하한가, 시가, 고가, 저가, 종가, 체결시간, 예상체결가, 예상체결량, 자본금,
474 | 액면가, 시가총액, 주식수, 호가시간, 일자, 우선매도잔량, 우선매수잔량,우선매도건수,
475 | 우선매수건수, 총매도잔량, 총매수잔량, 총매도건수, 총매수건수, 패리티, 기어링, 손익분기,
476 | 잔본지지, ELW행사가, 전환비율, ELW만기일, 미결제약정, 미결제전일대비, 이론가,
477 | 내재변동성, 델타, 감마, 쎄타, 베가, 로
478 |
479 | :param codes: string - 한번에 100종목까지 조회가능하며 종목코드사이에 세미콜론(;)으로 구분.
480 | :param inquiry: int - api 문서는 bool 타입이지만, int로 처리(0: 조회, 1: 남은 데이터 이어서 조회)
481 | :param codeCount: int - codes에 지정한 종목의 갯수.
482 | :param requestName: string
483 | :param screenNo: string
484 | :param typeFlag: int - 주식과 선물옵션 구분(0: 주식, 3: 선물옵션), 주의: 매개변수의 위치를 맨 뒤로 이동함.
485 | :return: list - 중첩 리스트 [[종목코드, 종목명 ... 종목 정보], [종목코드, 종목명 ... 종목 정보]]
486 | """
487 |
488 | if not self.getConnectState():
489 | raise KiwoomConnectError()
490 |
491 | if not (isinstance(codes, str)
492 | and isinstance(inquiry, int)
493 | and isinstance(codeCount, int)
494 | and isinstance(requestName, str)
495 | and isinstance(screenNo, str)
496 | and isinstance(typeFlag, int)):
497 | raise ParameterTypeError()
498 |
499 | returnCode = self.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
500 | codes, inquiry, codeCount, typeFlag, requestName, screenNo)
501 |
502 | if returnCode != ReturnCode.OP_ERR_NONE:
503 | raise KiwoomProcessingError("commKwRqData(): " + ReturnCode.CAUSE[returnCode])
504 |
505 | # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다.
506 | self.requestLoop = QEventLoop()
507 | self.requestLoop.exec_()
508 |
509 | ###############################################################
510 | # 메서드 정의: 실시간 데이터 처리 관련 메서드 #
511 | ###############################################################
512 |
513 | def disconnect_real_data(self, screen_no):
514 | """
515 | 해당 화면번호로 설정한 모든 실시간 데이터 요청을 제거합니다.
516 |
517 | 화면을 종료할 때 반드시 이 메서드를 호출해야 합니다.
518 |
519 | :param screen_no: string
520 | """
521 |
522 | if not self.getConnectState():
523 | raise KiwoomConnectError()
524 |
525 | if not isinstance(screen_no, str):
526 | raise ParameterTypeError()
527 |
528 | self.dynamicCall("DisconnectRealData(QString)", screen_no)
529 |
530 | def get_comm_real_data(self, code, fid):
531 | """
532 | 실시간 데이터 획득 메서드
533 |
534 | 이 메서드는 반드시 receiveRealData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다.
535 |
536 | :param code: string - 종목코드
537 | :param fid: - 실시간 타입에 포함된 fid
538 | :return: string - fid에 해당하는 데이터
539 | """
540 |
541 | if not (isinstance(code, str)
542 | and isinstance(fid, int)):
543 | raise ParameterTypeError()
544 |
545 | value = self.dynamicCall("GetCommRealData(QString, int)", code, fid)
546 |
547 | return value
548 |
549 | def set_real_reg(self, screen_no, codes, fids, real_reg_type):
550 | """
551 | 실시간 데이터 요청 메서드
552 |
553 | 종목코드와 fid 리스트를 이용해서 실시간 데이터를 요청하는 메서드입니다.
554 | 한번에 등록 가능한 종목과 fid 갯수는 100종목, 100개의 fid 입니다.
555 | 실시간등록타입을 0으로 설정하면, 첫 실시간 데이터 요청을 의미하며
556 | 실시간등록타입을 1로 설정하면, 추가등록을 의미합니다.
557 |
558 | 실시간 데이터는 실시간 타입 단위로 receiveRealData() 이벤트로 전달되기 때문에,
559 | 이 메서드에서 지정하지 않은 fid 일지라도, 실시간 타입에 포함되어 있다면, 데이터 수신이 가능하다.
560 |
561 | :param screen_no: string
562 | :param codes: string - 종목코드 리스트(종목코드;종목코드;...)
563 | :param fids: string - fid 리스트(fid;fid;...)
564 | :param real_reg_type: string - 실시간등록타입(0: 첫 등록, 1: 추가 등록)
565 | """
566 |
567 | if not self.getConnectState():
568 | raise KiwoomConnectError()
569 |
570 | if not (isinstance(screen_no, str)
571 | and isinstance(codes, str)
572 | and isinstance(fids, str)
573 | and isinstance(real_reg_type, str)):
574 | raise ParameterTypeError()
575 |
576 | self.dynamicCall("SetRealReg(QString, QString, QString, QString)",
577 | screen_no, codes, fids, real_reg_type)
578 |
579 | def set_real_remove(self, screen_no, code):
580 | """
581 | 실시간 데이터 중지 메서드
582 |
583 | set_real_reg() 메서드로 등록한 종목만, 이 메서드를 통해 실시간 데이터 받기를 중지 시킬 수 있습니다.
584 |
585 | :param screen_no: string - 화면번호 또는 ALL 키워드 사용가능
586 | :param code: string - 종목코드 또는 ALL 키워드 사용가능
587 | """
588 |
589 | if not self.getConnectState():
590 | raise KiwoomConnectError()
591 |
592 | if not (isinstance(screen_no, str)
593 | and isinstance(code, str)):
594 | raise ParameterTypeError()
595 |
596 | self.dynamicCall("SetRealRemove(QString, QString)", screen_no, code)
597 |
598 | ###############################################################
599 | # 메서드 정의: 조건검색 관련 메서드와 이벤트 #
600 | ###############################################################
601 |
602 | def receive_condition_ver(self, receive, msg):
603 | """
604 | getConditionLoad() 메서드의 조건식 목록 요청에 대한 응답 이벤트
605 |
606 | :param receive: int - 응답결과(1: 성공, 나머지 실패)
607 | :param msg: string - 메세지
608 | """
609 |
610 | try:
611 | if not receive:
612 | return
613 |
614 | self.condition = self.get_condition_name_list()
615 | print("조건식 개수: ", len(self.condition))
616 |
617 | for key in self.condition.keys():
618 | print("조건식: ", key, ": ", self.condition[key])
619 | print("key type: ", type(key))
620 |
621 | except Exception as e:
622 | print(e)
623 |
624 | finally:
625 | self.condition_loop.exit()
626 |
627 | def receive_tr_condition(self, screen_no, codes, condition_name, condition_index, inquiry):
628 | """
629 | (1회성, 실시간) 종목 조건검색 요청시 발생되는 이벤트
630 |
631 | :param screen_no: string
632 | :param codes: string - 종목코드 목록(각 종목은 세미콜론으로 구분됨)
633 | :param condition_name: string - 조건식 이름
634 | :param condition_index: int - 조건식 인덱스
635 | :param inquiry: int - 조회구분(0: 남은데이터 없음, 2: 남은데이터 있음)
636 | """
637 |
638 | print("[receive_tr_condition]")
639 |
640 | try:
641 | if codes == "":
642 | return
643 |
644 | code_list = codes.split(';')
645 | del code_list[-1]
646 |
647 | print(code_list)
648 | print("종목개수: ", len(code_list))
649 |
650 | finally:
651 | self.condition_loop.exit()
652 |
653 | def receive_real_condition(self, code, event, condition_name, condition_index):
654 | """
655 | 실시간 종목 조건검색 요청시 발생되는 이벤트
656 |
657 | :param code: string - 종목코드
658 | :param event: string - 이벤트종류("I": 종목편입, "D": 종목이탈)
659 | :param condition_name: string - 조건식 이름
660 | :param condition_index: string - 조건식 인덱스(여기서만 인덱스가 string 타입으로 전달됨)
661 | """
662 |
663 | print("[receive_real_condition]")
664 |
665 | print("종목코드: ", code)
666 | print("이벤트: ", "종목편입" if event == "I" else "종목이탈")
667 |
668 | def get_condition_load(self):
669 | """ 조건식 목록 요청 메서드 """
670 |
671 | if not self.get_connect_state():
672 | raise KiwoomConnectError()
673 |
674 | is_load = self.dynamicCall("GetConditionLoad()")
675 |
676 | # 요청 실패시
677 | if not is_load:
678 | raise KiwoomProcessingError("getConditionLoad(): 조건식 요청 실패")
679 |
680 | # receiveConditionVer() 이벤트 메서드에서 루프 종료
681 | self.condition_loop = QEventLoop()
682 | self.condition_loop.exec_()
683 |
684 | def get_condition_name_list(self):
685 | """
686 | 조건식 획득 메서드
687 |
688 | 조건식을 딕셔너리 형태로 반환합니다.
689 | 이 메서드는 반드시 receiveConditionVer() 이벤트 메서드안에서 사용해야 합니다.
690 |
691 | :return: dict - {인덱스:조건명, 인덱스:조건명, ...}
692 | """
693 |
694 | data = self.dynamicCall("get_condition_name_list()")
695 |
696 | if data == "":
697 | raise KiwoomProcessingError("get_condition_name_list(): 사용자 조건식이 없습니다.")
698 |
699 | conditionList = data.split(';')
700 | del conditionList[-1]
701 |
702 | condition_dictionary = {}
703 |
704 | for condition in conditionList:
705 | key, value = condition.split('^')
706 | condition_dictionary[int(key)] = value
707 |
708 | return condition_dictionary
709 |
710 | def send_condition(self, screen_no, condition_name, condition_index, is_real_time):
711 | """
712 | 종목 조건검색 요청 메서드
713 |
714 | 이 메서드로 얻고자 하는 것은 해당 조건에 맞는 종목코드이다.
715 | 해당 종목에 대한 상세정보는 set_real_reg() 메서드로 요청할 수 있다.
716 | 요청이 실패하는 경우는, 해당 조건식이 없거나, 조건명과 인덱스가 맞지 않거나, 조회 횟수를 초과하는 경우 발생한다.
717 |
718 | 조건검색에 대한 결과는
719 | 1회성 조회의 경우, receiveTrCondition() 이벤트로 결과값이 전달되며
720 | 실시간 조회의 경우, receiveTrCondition()과 receiveRealCondition() 이벤트로 결과값이 전달된다.
721 |
722 | :param screen_no: string
723 | :param condition_name: string - 조건식 이름
724 | :param condition_index: int - 조건식 인덱스
725 | :param is_real_time: int - 조건검색 조회구분(0: 1회성 조회, 1: 실시간 조회)
726 | """
727 |
728 | if not self.get_connect_state():
729 | raise KiwoomConnectError()
730 |
731 | if not (isinstance(screen_no, str)
732 | and isinstance(condition_name, str)
733 | and isinstance(condition_index, int)
734 | and isinstance(is_real_time, int)):
735 | raise ParameterTypeError()
736 |
737 | is_request = self.dynamicCall("SendCondition(QString, QString, int, int",
738 | screen_no, condition_name, condition_index, is_real_time)
739 |
740 | if not is_request:
741 | raise KiwoomProcessingError("sendCondition(): 조건검색 요청 실패")
742 |
743 | # receiveTrCondition() 이벤트 메서드에서 루프 종료
744 | self.condition_loop = QEventLoop()
745 | self.condition_loop.exec_()
746 |
747 | def send_condition_stop(self, screen_no, condition_name, condition_index):
748 | """ 종목 조건검색 중지 메서드 """
749 |
750 | if not self.get_connect_state():
751 | raise KiwoomConnectError()
752 |
753 | if not (isinstance(screen_no, str)
754 | and isinstance(condition_name, str)
755 | and isinstance(condition_index, int)):
756 | raise ParameterTypeError()
757 |
758 | self.dynamicCall("SendConditionStop(QString, QString, int)", screen_no, condition_name, condition_index)
759 |
760 | ###############################################################
761 | # 메서드 정의: 주문과 잔고처리 관련 메서드 #
762 | # 1초에 5회까지 주문 허용 #
763 | ###############################################################
764 |
765 | def send_order(self, request_name, screen_no, account_no, order_type, code, qty, price, hoga_type, origin_order_no):
766 | """
767 | 주식 주문 메서드
768 |
769 | send_order() 메소드 실행시,
770 | OnReceiveMsg, OnReceiveTrData, OnReceiveChejanData 이벤트가 발생한다.
771 | 이 중, 주문에 대한 결과 데이터를 얻기 위해서는 OnReceiveChejanData 이벤트를 통해서 처리한다.
772 | OnReceiveTrData 이벤트를 통해서는 주문번호를 얻을 수 있는데, 주문후 이 이벤트에서 주문번호가 ''공백으로 전달되면,
773 | 주문접수 실패를 의미한다.
774 |
775 | :param request_name: string - 주문 요청명(사용자 정의)
776 | :param screen_no: string - 화면번호(4자리)
777 | :param account_no: string - 계좌번호(10자리)
778 | :param order_type: int - 주문유형(1: 신규매수, 2: 신규매도, 3: 매수취소, 4: 매도취소, 5: 매수정정, 6: 매도정정)
779 | :param code: string - 종목코드
780 | :param qty: int - 주문수량
781 | :param price: int - 주문단가
782 | :param hoga_type: string - 거래구분(00: 지정가, 03: 시장가, 05: 조건부지정가, 06: 최유리지정가, 그외에는 api 문서참조)
783 | :param origin_order_no: string - 원주문번호(신규주문에는 공백, 정정및 취소주문시 원주문번호르 입력합니다.)
784 | """
785 | if not self.get_connect_state():
786 | raise KiwoomConnectError()
787 |
788 | if not (isinstance(request_name, str)
789 | and isinstance(screen_no, str)
790 | and isinstance(account_no, str)
791 | and isinstance(order_type, int)
792 | and isinstance(code, str)
793 | and isinstance(qty, int)
794 | and isinstance(price, int)
795 | and isinstance(hoga_type, str)
796 | and isinstance(origin_order_no, str)):
797 | raise ParameterTypeError()
798 |
799 | return_code = self.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
800 | [request_name, screen_no, account_no, order_type, code, qty, price, hoga_type,
801 | origin_order_no])
802 |
803 | if return_code != ReturnCode.OP_ERR_NONE:
804 | raise KiwoomProcessingError("send_order(): " + ReturnCode.CAUSE[return_code])
805 |
806 | # receiveTrData() 에서 루프종료
807 | self.order_loop = QEventLoop()
808 | self.order_loop.exec_()
809 |
810 | def GetChejanData(self, nFid):
811 | cmd = 'GetChejanData("%s")' % nFid
812 | ret = self.dynamicCall(cmd)
813 | return ret
814 |
815 | ###############################################################
816 | # 기타 메서드 정의 #
817 | ###############################################################
818 |
819 | def get_code_list(self, *market):
820 | """
821 | 여러 시장의 종목코드를 List 형태로 반환하는 헬퍼 메서드.
822 |
823 | :param market: Tuple - 여러 개의 문자열을 매개변수로 받아 Tuple로 처리한다.
824 | :return: List
825 | """
826 | code_list = []
827 | for m in market:
828 | tmp_list = self.get_codelist_by_market(m)
829 | code_list += tmp_list
830 | return code_list
831 |
832 | def get_master_code_name(self, code):
833 | """
834 | 종목코드의 한글명을 반환한다.
835 |
836 | :param code: string - 종목코드
837 | :return: string - 종목코드의 한글명
838 | """
839 |
840 | if not self.get_connect_state():
841 | raise KiwoomConnectError()
842 |
843 | if not isinstance(code, str):
844 | raise ParameterTypeError()
845 |
846 | cmd = 'GetMasterCodeName("%s")' % code
847 | name = self.dynamicCall(cmd)
848 | return name
849 |
850 | def change_format(self, data, percent=0):
851 | if percent == 0:
852 | d = int(data)
853 | format_data = '{:-,d}'.format(d)
854 | elif percent == 1:
855 | f = float(data)
856 | f -= 100
857 | format_data = '{:-,.2f}'.format(f)
858 | elif percent == 2:
859 | f = float(data)
860 | format_data = '{:-,.2f}'.format(f)
861 | return format_data
862 |
863 | def opw_data_reset(self):
864 | """ 잔고 및 보유종목 데이터 초기화 """
865 | self.data_opw00001 = 0
866 | self.data_opw00018 = {'account_evaluation': [], 'stocks': []}
867 |
868 |
869 | class ParameterTypeError(Exception):
870 | """ 파라미터 타입이 일치하지 않을 경우 발생하는 예외 """
871 |
872 | def __init__(self, msg="파라미터 타입이 일치하지 않습니다."):
873 | self.msg = msg
874 |
875 | def __str__(self):
876 | return self.msg
877 |
878 |
879 | class ParameterValueError(Exception):
880 | """ 파라미터로 사용할 수 없는 값을 사용할 경우 발생하는 예외 """
881 |
882 | def __init__(self, msg="파라미터로 사용할 수 없는 값 입니다."):
883 | self.msg = msg
884 |
885 | def __str__(self):
886 | return self.msg
887 |
888 |
889 | class KiwoomProcessingError(Exception):
890 | """ 키움에서 처리실패에 관련된 리턴코드를 받았을 경우 발생하는 예외 """
891 |
892 | def __init__(self, msg="처리 실패"):
893 | self.msg = msg
894 |
895 | def __str__(self):
896 | return self.msg
897 |
898 | def __repr__(self):
899 | return self.msg
900 |
901 |
902 | class KiwoomConnectError(Exception):
903 | """ 키움서버에 로그인 상태가 아닐 경우 발생하는 예외 """
904 |
905 | def __init__(self, msg="로그인 여부를 확인하십시오"):
906 | self.msg = msg
907 |
908 | def __str__(self):
909 | return self.msg
910 |
911 |
912 | class ReturnCode(object):
913 | """ 키움 OpenApi+ 함수들이 반환하는 값 """
914 |
915 | OP_ERR_NONE = 0 # 정상처리
916 | OP_ERR_FAIL = -10 # 실패
917 | OP_ERR_LOGIN = -100 # 사용자정보교환실패
918 | OP_ERR_CONNECT = -101 # 서버접속실패
919 | OP_ERR_VERSION = -102 # 버전처리실패
920 | OP_ERR_FIREWALL = -103 # 개인방화벽실패
921 | OP_ERR_MEMORY = -104 # 메모리보호실패
922 | OP_ERR_INPUT = -105 # 함수입력값오류
923 | OP_ERR_SOCKET_CLOSED = -106 # 통신연결종료
924 | OP_ERR_SISE_OVERFLOW = -200 # 시세조회과부하
925 | OP_ERR_RQ_STRUCT_FAIL = -201 # 전문작성초기화실패
926 | OP_ERR_RQ_STRING_FAIL = -202 # 전문작성입력값오류
927 | OP_ERR_NO_DATA = -203 # 데이터없음
928 | OP_ERR_OVER_MAX_DATA = -204 # 조회가능한종목수초과
929 | OP_ERR_DATA_RCV_FAIL = -205 # 데이터수신실패
930 | OP_ERR_OVER_MAX_FID = -206 # 조회가능한FID수초과
931 | OP_ERR_REAL_CANCEL = -207 # 실시간해제오류
932 | OP_ERR_ORD_WRONG_INPUT = -300 # 입력값오류
933 | OP_ERR_ORD_WRONG_ACCTNO = -301 # 계좌비밀번호없음
934 | OP_ERR_OTHER_ACC_USE = -302 # 타인계좌사용오류
935 | OP_ERR_MIS_2BILL_EXC = -303 # 주문가격이20억원을초과
936 | OP_ERR_MIS_5BILL_EXC = -304 # 주문가격이50억원을초과
937 | OP_ERR_MIS_1PER_EXC = -305 # 주문수량이총발행주수의1%초과오류
938 | OP_ERR_MIS_3PER_EXC = -306 # 주문수량이총발행주수의3%초과오류
939 | OP_ERR_SEND_FAIL = -307 # 주문전송실패
940 | OP_ERR_ORD_OVERFLOW = -308 # 주문전송과부하
941 | OP_ERR_MIS_300CNT_EXC = -309 # 주문수량300계약초과
942 | OP_ERR_MIS_500CNT_EXC = -310 # 주문수량500계약초과
943 | OP_ERR_ORD_WRONG_ACCTINFO = -340 # 계좌정보없음
944 | OP_ERR_ORD_SYMCODE_EMPTY = -500 # 종목코드없음
945 |
946 | CAUSE = {
947 | 0: '정상처리',
948 | -10: '실패',
949 | -100: '사용자정보교환실패',
950 | -102: '버전처리실패',
951 | -103: '개인방화벽실패',
952 | -104: '메모리보호실패',
953 | -105: '함수입력값오류',
954 | -106: '통신연결종료',
955 | -200: '시세조회과부하',
956 | -201: '전문작성초기화실패',
957 | -202: '전문작성입력값오류',
958 | -203: '데이터없음',
959 | -204: '조회가능한종목수초과',
960 | -205: '데이터수신실패',
961 | -206: '조회가능한FID수초과',
962 | -207: '실시간해제오류',
963 | -300: '입력값오류',
964 | -301: '계좌비밀번호없음',
965 | -302: '타인계좌사용오류',
966 | -303: '주문가격이20억원을초과',
967 | -304: '주문가격이50억원을초과',
968 | -305: '주문수량이총발행주수의1%초과오류',
969 | -306: '주문수량이총발행주수의3%초과오류',
970 | -307: '주문전송실패',
971 | -308: '주문전송과부하',
972 | -309: '주문수량300계약초과',
973 | -310: '주문수량500계약초과',
974 | -340: '계좌정보없음',
975 | -500: '종목코드없음'
976 | }
977 |
978 |
979 | class FidList(object):
980 | """ receiveChejanData() 이벤트 메서드로 전달되는 FID 목록 """
981 |
982 | CHEJAN = {
983 | 9201: '계좌번호',
984 | 9203: '주문번호',
985 | 9205: '관리자사번',
986 | 9001: '종목코드',
987 | 912: '주문업무분류',
988 | 913: '주문상태',
989 | 302: '종목명',
990 | 900: '주문수량',
991 | 901: '주문가격',
992 | 902: '미체결수량',
993 | 903: '체결누계금액',
994 | 904: '원주문번호',
995 | 905: '주문구분',
996 | 906: '매매구분',
997 | 907: '매도수구분',
998 | 908: '주문/체결시간',
999 | 909: '체결번호',
1000 | 910: '체결가',
1001 | 911: '체결량',
1002 | 10: '현재가',
1003 | 27: '(최우선)매도호가',
1004 | 28: '(최우선)매수호가',
1005 | 914: '단위체결가',
1006 | 915: '단위체결량',
1007 | 938: '당일매매수수료',
1008 | 939: '당일매매세금',
1009 | 919: '거부사유',
1010 | 920: '화면번호',
1011 | 921: '921',
1012 | 922: '922',
1013 | 923: '923',
1014 | 949: '949',
1015 | 10010: '10010',
1016 | 917: '신용구분',
1017 | 916: '대출일',
1018 | 930: '보유수량',
1019 | 931: '매입단가',
1020 | 932: '총매입가',
1021 | 933: '주문가능수량',
1022 | 945: '당일순매수수량',
1023 | 946: '매도/매수구분',
1024 | 950: '당일총매도손일',
1025 | 951: '예수금',
1026 | 307: '기준가',
1027 | 8019: '손익율',
1028 | 957: '신용금액',
1029 | 958: '신용이자',
1030 | 959: '담보대출수량',
1031 | 924: '924',
1032 | 918: '만기일',
1033 | 990: '당일실현손익(유가)',
1034 | 991: '당일신현손익률(유가)',
1035 | 992: '당일실현손익(신용)',
1036 | 993: '당일실현손익률(신용)',
1037 | 397: '파생상품거래단위',
1038 | 305: '상한가',
1039 | 306: '하한가'
1040 | }
1041 |
1042 |
1043 | class RealType(object):
1044 | REALTYPE = {
1045 | '주식시세': {
1046 | 10: '현재가',
1047 | 11: '전일대비',
1048 | 12: '등락율',
1049 | 27: '최우선매도호가',
1050 | 28: '최우선매수호가',
1051 | 13: '누적거래량',
1052 | 14: '누적거래대금',
1053 | 16: '시가',
1054 | 17: '고가',
1055 | 18: '저가',
1056 | 25: '전일대비기호',
1057 | 26: '전일거래량대비',
1058 | 29: '거래대금증감',
1059 | 30: '거일거래량대비',
1060 | 31: '거래회전율',
1061 | 32: '거래비용',
1062 | 311: '시가총액(억)'
1063 | },
1064 |
1065 | '주식체결': {
1066 | 20: '체결시간(HHMMSS)',
1067 | 10: '체결가',
1068 | 11: '전일대비',
1069 | 12: '등락율',
1070 | 27: '최우선매도호가',
1071 | 28: '최우선매수호가',
1072 | 15: '체결량',
1073 | 13: '누적체결량',
1074 | 14: '누적거래대금',
1075 | 16: '시가',
1076 | 17: '고가',
1077 | 18: '저가',
1078 | 25: '전일대비기호',
1079 | 26: '전일거래량대비',
1080 | 29: '거래대금증감',
1081 | 30: '전일거래량대비',
1082 | 31: '거래회전율',
1083 | 32: '거래비용',
1084 | 228: '체결강도',
1085 | 311: '시가총액(억)',
1086 | 290: '장구분',
1087 | 691: 'KO접근도'
1088 | },
1089 |
1090 | '주식호가잔량': {
1091 | 21: '호가시간',
1092 | 41: '매도호가1',
1093 | 61: '매도호가수량1',
1094 | 81: '매도호가직전대비1',
1095 | 51: '매수호가1',
1096 | 71: '매수호가수량1',
1097 | 91: '매수호가직전대비1',
1098 | 42: '매도호가2',
1099 | 62: '매도호가수량2',
1100 | 82: '매도호가직전대비2',
1101 | 52: '매수호가2',
1102 | 72: '매수호가수량2',
1103 | 92: '매수호가직전대비2',
1104 | 43: '매도호가3',
1105 | 63: '매도호가수량3',
1106 | 83: '매도호가직전대비3',
1107 | 53: '매수호가3',
1108 | 73: '매수호가수량3',
1109 | 93: '매수호가직전대비3',
1110 | 44: '매도호가4',
1111 | 64: '매도호가수량4',
1112 | 84: '매도호가직전대비4',
1113 | 54: '매수호가4',
1114 | 74: '매수호가수량4',
1115 | 94: '매수호가직전대비4',
1116 | 45: '매도호가5',
1117 | 65: '매도호가수량5',
1118 | 85: '매도호가직전대비5',
1119 | 55: '매수호가5',
1120 | 75: '매수호가수량5',
1121 | 95: '매수호가직전대비5',
1122 | 46: '매도호가6',
1123 | 66: '매도호가수량6',
1124 | 86: '매도호가직전대비6',
1125 | 56: '매수호가6',
1126 | 76: '매수호가수량6',
1127 | 96: '매수호가직전대비6',
1128 | 47: '매도호가7',
1129 | 67: '매도호가수량7',
1130 | 87: '매도호가직전대비7',
1131 | 57: '매수호가7',
1132 | 77: '매수호가수량7',
1133 | 97: '매수호가직전대비7',
1134 | 48: '매도호가8',
1135 | 68: '매도호가수량8',
1136 | 88: '매도호가직전대비8',
1137 | 58: '매수호가8',
1138 | 78: '매수호가수량8',
1139 | 98: '매수호가직전대비8',
1140 | 49: '매도호가9',
1141 | 69: '매도호가수량9',
1142 | 89: '매도호가직전대비9',
1143 | 59: '매수호가9',
1144 | 79: '매수호가수량9',
1145 | 99: '매수호가직전대비9',
1146 | 50: '매도호가10',
1147 | 70: '매도호가수량10',
1148 | 90: '매도호가직전대비10',
1149 | 60: '매수호가10',
1150 | 80: '매수호가수량10',
1151 | 100: '매수호가직전대비10',
1152 | 121: '매도호가총잔량',
1153 | 122: '매도호가총잔량직전대비',
1154 | 125: '매수호가총잔량',
1155 | 126: '매수호가총잔량직전대비',
1156 | 23: '예상체결가',
1157 | 24: '예상체결수량',
1158 | 128: '순매수잔량(총매수잔량-총매도잔량)',
1159 | 129: '매수비율',
1160 | 138: '순매도잔량(총매도잔량-총매수잔량)',
1161 | 139: '매도비율',
1162 | 200: '예상체결가전일종가대비',
1163 | 201: '예상체결가전일종가대비등락율',
1164 | 238: '예상체결가전일종가대비기호',
1165 | 291: '예상체결가',
1166 | 292: '예상체결량',
1167 | 293: '예상체결가전일대비기호',
1168 | 294: '예상체결가전일대비',
1169 | 295: '예상체결가전일대비등락율',
1170 | 13: '누적거래량',
1171 | 299: '전일거래량대비예상체결률',
1172 | 215: '장운영구분'
1173 | },
1174 |
1175 | '장시작시간': {
1176 | 215: '장운영구분(0:장시작전, 2:장종료전, 3:장시작, 4,8:장종료, 9:장마감)',
1177 | 20: '시간(HHMMSS)',
1178 | 214: '장시작예상잔여시간'
1179 | },
1180 |
1181 | '업종지수': {
1182 | 20: '체결시간',
1183 | 10: '현재가',
1184 | 11: '전일대비',
1185 | 12: '등락율',
1186 | 15: '거래량',
1187 | 13: '누적거래량',
1188 | 14: '누적거래대금',
1189 | 16: '시가',
1190 | 17: '고가',
1191 | 18: '저가',
1192 | 25: '전일대비기호',
1193 | 26: '전일거래량대비(계약,주)'
1194 | },
1195 |
1196 | '업종등락': {
1197 | 20: '체결시간',
1198 | 252: '상승종목수',
1199 | 251: '상한종목수',
1200 | 253: '보합종목수',
1201 | 255: '하락종목수',
1202 | 254: '하한종목수',
1203 | 13: '누적거래량',
1204 | 14: '누적거래대금',
1205 | 10: '현재가',
1206 | 11: '전일대비',
1207 | 12: '등락율',
1208 | 256: '거래형성종목수',
1209 | 257: '거래형성비율',
1210 | 25: '전일대비기호'
1211 | },
1212 |
1213 | '주문체결': {
1214 | 9201: '계좌번호',
1215 | 9203: '주문번호',
1216 | 9205: '관리자사번',
1217 | 9001: '종목코드',
1218 | 912: '주문분류(jj:주식주문)',
1219 | 913: '주문상태(10:원주문, 11:정정주문, 12:취소주문, 20:주문확인, 21:정정확인, 22:취소확인, 90,92:주문거부)',
1220 | 302: '종목명',
1221 | 900: '주문수량',
1222 | 901: '주문가격',
1223 | 902: '미체결수량',
1224 | 903: '체결누계금액',
1225 | 904: '원주문번호',
1226 | 905: '주문구분(+:현금매수, -:현금매도)',
1227 | 906: '매매구분(보통, 시장가등)',
1228 | 907: '매도수구분(1:매도, 2:매수)',
1229 | 908: '체결시간(HHMMSS)',
1230 | 909: '체결번호',
1231 | 910: '체결가',
1232 | 911: '체결량',
1233 | 10: '체결가',
1234 | 27: '최우선매도호가',
1235 | 28: '최우선매수호가',
1236 | 914: '단위체결가',
1237 | 915: '단위체결량',
1238 | 938: '당일매매수수료',
1239 | 939: '당일매매세금'
1240 | },
1241 |
1242 | '잔고': {
1243 | 9201: '계좌번호',
1244 | 9001: '종목코드',
1245 | 302: '종목명',
1246 | 10: '현재가',
1247 | 930: '보유수량',
1248 | 931: '매입단가',
1249 | 932: '총매입가',
1250 | 933: '주문가능수량',
1251 | 945: '당일순매수량',
1252 | 946: '매도매수구분',
1253 | 950: '당일총매도손익',
1254 | 951: '예수금',
1255 | 27: '최우선매도호가',
1256 | 28: '최우선매수호가',
1257 | 307: '기준가',
1258 | 8019: '손익율'
1259 | },
1260 |
1261 | '주식시간외호가': {
1262 | 21: '호가시간(HHMMSS)',
1263 | 131: '시간외매도호가총잔량',
1264 | 132: '시간외매도호가총잔량직전대비',
1265 | 135: '시간외매수호가총잔량',
1266 | 136: '시간외매수호가총잔량직전대비'
1267 | }
1268 | }
1269 |
1270 |
1271 | def test_to_get_account():
1272 | kiwoom.set_input_value("계좌번호", "8086919011")
1273 | kiwoom.set_input_value("비밀번호", "0000")
1274 | kiwoom.comm_rq_data("계좌평가잔고내역요청", "opw00018", 2, "2000")
1275 | while kiwoom.inquiry == '2':
1276 | time.sleep(0.2)
1277 | kiwoom.set_input_value("계좌번호", "8086919011")
1278 | kiwoom.set_input_value("비밀번호", "0000")
1279 | kiwoom.comm_rq_data("계좌평가잔고내역요청", "opw00018", 2, "2")
1280 |
1281 | print(kiwoom.data_opw00018['account_evaluation'])
1282 | print(kiwoom.data_opw00018['stocks'])
1283 |
1284 |
1285 | def test_to_get_opt10081():
1286 | kiwoom.set_input_value("종목코드", "035420")
1287 | kiwoom.set_input_value("기준일자", "20170101")
1288 | kiwoom.set_input_value("수정주가구분", 1)
1289 | kiwoom.comm_rq_data("주식일봉차트조회요청", "opt10081", 0, "0101")
1290 | while kiwoom.inquiry == '2':
1291 | time.sleep(0.2)
1292 | kiwoom.set_input_value("종목코드", "035420")
1293 | kiwoom.set_input_value("기준일자", "20170101")
1294 | kiwoom.set_input_value("수정주가구분", 1)
1295 | kiwoom.comm_rq_data("주식일봉차트조회요청", "opt10081", 2, "0101")
1296 |
1297 |
1298 | def test_to_get_opt10086():
1299 | kiwoom.set_input_value("종목코드", "035420")
1300 | kiwoom.set_input_value("조회일자", "20170101")
1301 | kiwoom.set_input_value("표시구분", 1)
1302 | kiwoom.comm_rq_data("일별주가요청", "opt10086", 0, "0101")
1303 | while kiwoom.inquiry == '2':
1304 | time.sleep(0.2)
1305 | kiwoom.set_input_value("종목코드", "035420")
1306 | kiwoom.set_input_value("조회일자", "20170101")
1307 | kiwoom.set_input_value("표시구분", 1)
1308 | kiwoom.comm_rq_data("일별주가요청", "opt10086", 2, "0101")
1309 |
1310 |
1311 | if __name__ == "__main__":
1312 | app = QApplication(sys.argv)
1313 |
1314 | # Test Code
1315 | kiwoom = Kiwoom()
1316 | kiwoom.comm_connect()
1317 |
1318 | data = kiwoom.get_data_opt10086("035420", "20170101")
1319 | print(len(data))
1320 |
--------------------------------------------------------------------------------
/pykiwoom/wrapper/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 | import pandas as pd
4 | from datetime import datetime
5 | from PyQt5.QtWidgets import *
6 | import sqlite3
7 |
8 | TR_REQ_TIME_INTERVAL = 4
9 |
10 | app = QApplication(sys.argv)
11 |
12 | class KiwoomWrapper:
13 | def __init__(self, kiwoom):
14 | self.kiwoom = kiwoom
15 |
16 | def get_data_opt10081(self, code, date='20161231'):
17 | try:
18 | data = pd.read_hdf("../data/hdf/%s.hdf" % code, 'day').sort_index()
19 | start = str(data.index[-2])
20 | except (FileNotFoundError, IndexError) as e:
21 | start = "20010101"
22 | print("get 81 data from %s" % start)
23 | self.kiwoom.start_date = datetime.strptime(start, "%Y%m%d")
24 | self.kiwoom.data_opt10081 = [] * 15
25 | self.kiwoom.set_input_value("종목코드", code)
26 | self.kiwoom.set_input_value("기준일자", date)
27 | self.kiwoom.set_input_value("수정주가구분", 255)
28 | self.kiwoom.comm_rq_data("주식일봉차트조회요청", "opt10081", 0, "0101")
29 | while self.kiwoom.inquiry == '2':
30 | time.sleep(TR_REQ_TIME_INTERVAL)
31 | self.kiwoom.set_input_value("종목코드", code)
32 | self.kiwoom.set_input_value("기준일자", date)
33 | self.kiwoom.set_input_value("수정주가구분", 255)
34 | self.kiwoom.comm_rq_data("주식일봉차트조회요청", "opt10081", 2, "0101")
35 | self.kiwoom.data_opt10081.index = self.kiwoom.data_opt10081.loc[:, '일자']
36 | return self.kiwoom.data_opt10081.loc[:, ['현재가', '거래량', '거래대금', '시가', '고가', '저가']]
37 |
38 | def get_data_opt10086(self, code, date):
39 | try:
40 | data = pd.read_hdf("../data/hdf/%s.hdf" % code, 'day').sort_index()
41 | start = str(data.index[-2])
42 | except (FileNotFoundError, IndexError) as e:
43 | start = "20010101"
44 | print("get 86 data from %s" % start)
45 | self.kiwoom.start_date = datetime.strptime(start, "%Y%m%d")
46 | self.kiwoom.data_opt10086 = [] * 23
47 | self.kiwoom.set_input_value("종목코드", code)
48 | self.kiwoom.set_input_value("조회일자", date)
49 | self.kiwoom.set_input_value("표시구분", 1)
50 | self.kiwoom.comm_rq_data("일별주가요청", "opt10086", 0, "0101")
51 | while self.kiwoom.inquiry == '2':
52 | time.sleep(TR_REQ_TIME_INTERVAL)
53 | self.kiwoom.set_input_value("종목코드", code)
54 | self.kiwoom.set_input_value("조회일자", date)
55 | self.kiwoom.set_input_value("표시구분", 1)
56 | self.kiwoom.comm_rq_data("일별주가요청", "opt10086", 2, "0101")
57 | self.kiwoom.data_opt10086.index = self.kiwoom.data_opt10086.loc[:, '일자']
58 | return self.kiwoom.data_opt10086
59 |
60 | if __name__ == '__main__':
61 | from pykiwoom.kiwoom import Kiwoom
62 | kiwoom = Kiwoom()
63 | kiwoom_wrapper = KiwoomWrapper(kiwoom)
64 | kiwoom_wrapper.get_data_opt10081('000660', "20161231")
65 |
--------------------------------------------------------------------------------
/pymon.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PyQt5.QtCore import Qt, QTimer, QTime
3 | from PyQt5 import uic
4 | from PyQt5.QtWidgets import *
5 | import test_kiwoom
6 | import pandas as pd
7 | import time
8 | import datetime
9 | import webreader
10 |
11 | MARKET_KOSPI = 0
12 | MARKET_KOSDAK = 10
13 |
14 | class PyMon:
15 | def __init__(self):
16 | self.kiwoom = test_kiwoom.Kiwoom()
17 | self.kiwoom.comm_connect()
18 | self.get_code_list()
19 |
20 | def get_code_list(self):
21 | self.kospi_codes = self.kiwoom.get_codelist_by_market(MARKET_KOSPI)
22 | self.kosdak_codes = self.kiwoom.get_codelist_by_market(MARKET_KOSDAK)
23 |
24 | def get_ohlcv(self, code, start_date):
25 | # Init data structure
26 | self.kiwoom.initOHLCRawData()
27 |
28 | # Request TR and get data
29 | data = self.kiwoom.get_opt10081(code, start_date)
30 | # DataFrame
31 | col_name = ['종목코드', '현재가', '거래량', '거래대금', '일자', '시가', '고가', '저가',
32 | '수정주가구분', '수정비율', '대업종구분', '소업종구분', '종목정보', '수정주가이벤트', '전일종가']
33 | df = pd.DataFrame(data, columns=['open', 'high', 'low', 'close', 'volume'],
34 | index=self.kiwoom.ohlcv['date'])
35 | return df
36 |
37 | def check_speedy_rising_volume(self, code):
38 | today = datetime.datetime.today().strftime("%Y%m%d")
39 | df = self.get_ohlcv(code, today)
40 | volumes = df['volume']
41 |
42 | sum_vol20 = 0
43 | avg_vol20 = 0
44 |
45 | # Check small trading days
46 | if len(volumes) < 21:
47 | return False
48 |
49 | # Accumulation
50 | for i, vol in enumerate(volumes):
51 | if i == 0:
52 | today_vol = vol
53 | elif i >= 1 and i <= 20:
54 | sum_vol20 += vol
55 | elif i >= 21:
56 | break
57 |
58 | # Average and decision
59 | avg_vol20 = sum_vol20 / 20;
60 | if today_vol > avg_vol20 * 10:
61 | return True
62 | return False
63 |
64 | def update_buy_list(self, buy_list):
65 | f = open("buy_list.txt", "wt")
66 | for code in buy_list:
67 | f.writelines("매수;%s;시장가;10;0;매수전\n" % (code))
68 | f.close()
69 |
70 | def calculate_estimated_dividend_to_treasury(self, code):
71 | dividend_yield = webreader.get_estimated_dividend_yield(code)
72 |
73 | if pd.isnull(dividend_yield):
74 | dividend_yield = webreader.get_dividend_yield(code)
75 |
76 | dividend_yield = float(dividend_yield)
77 | current_3year_treasury = float(webreader.get_current_3year_treasury())
78 | estimated_dividend_to_treasury = dividend_yield / current_3year_treasury
79 | return estimated_dividend_to_treasury
80 |
81 | def get_min_max_dividend_to_treasury(self, code):
82 | previous_dividend_yield = webreader.get_previous_dividend_yield(code)
83 | three_years_treasury = webreader.get_3year_treasury()
84 |
85 | now = datetime.datetime.now()
86 | cur_year = now.year
87 | previous_dividend_to_treasury = {}
88 |
89 | for year in range(cur_year-5, cur_year):
90 | if year in previous_dividend_yield.keys() and year in three_years_treasury.keys():
91 | ratio = float(previous_dividend_yield[year]) / float(three_years_treasury[year])
92 | previous_dividend_to_treasury[year] = ratio
93 |
94 | print(previous_dividend_to_treasury)
95 | min_ratio = min(previous_dividend_to_treasury.values())
96 | max_ratio = max(previous_dividend_to_treasury.values())
97 |
98 | return min_ratio, max_ratio
99 |
100 | def buy_check_by_dividend_algorithm(self, code):
101 | estimated_dividend_to_treasury = self.calculate_estimated_dividend_to_treasury(code)
102 | (min_ratio, max_ratio) = self.get_min_max_dividend_to_treasury(code)
103 |
104 | if estimated_dividend_to_treasury > max_ratio:
105 | return 1, estimated_dividend_to_treasury
106 | else:
107 | return 0, estimated_dividend_to_treasury
108 |
109 | def run_dividend(self):
110 | buy_list = []
111 |
112 | for code in self.kospi_codes[0:50]:
113 | time.sleep(0.5)
114 | ret = self.buy_check_by_dividend_algorithm(code)
115 | if ret[0] == 1:
116 | buy_list.append((code, ret[1]))
117 |
118 | sorted_list = sorted(buy_list, key=lambda code:code[1], reverse=True)
119 |
120 | # Buy list
121 | buy_list = []
122 | for i in range(0, 5):
123 | code = sorted_list[i][0]
124 | buy_list.append(code)
125 |
126 | self.update_buy_list(buy_list)
127 |
128 | def run(self):
129 | buy_list = []
130 | num = len(self.kosdak_codes)
131 | for i, code in enumerate(self.kosdak_codes):
132 | print(i, ":", num)
133 | if self.check_speedy_rising_volume(code):
134 | print("급등주: %s, %s" % (code, self.kiwoom.get_master_code_name(code)))
135 | buy_list.append(code)
136 | self.update_buy_list(buy_list)
137 |
138 |
139 | if __name__ == "__main__":
140 | app = QApplication(sys.argv)
141 | pymon = PyMon()
142 | #pymon.run()
143 | #print(pymon.calculate_estimated_dividend_to_treasury('058470'))
144 | #print(pymon.get_min_max_dividend_to_treasury('058470'))
145 | #print(pymon.buy_check_by_dividend_algorithm('058470'))
146 | pymon.run_dividend()
147 |
--------------------------------------------------------------------------------
/pytrader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didw/PyTrader/eae75171e3e157ee58012d7af65f8270ce792e74/pytrader.png
--------------------------------------------------------------------------------
/pytrader.py:
--------------------------------------------------------------------------------
1 | """
2 | QtDesigner로 만든 UI와 해당 UI의 위젯에서 발생하는 이벤트를 컨트롤하는 클래스
3 |
4 | author: Jongyeol Yang
5 | last edit: 2017. 02. 23
6 | """
7 |
8 |
9 | import sys, time
10 | from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QTableWidget, QTableWidgetItem
11 | from PyQt5.QtCore import Qt, QTimer, QTime
12 | from PyQt5 import uic
13 | from pykiwoom.kiwoom import *
14 | from pykiwoom.wrapper import *
15 | import codecs
16 |
17 | form_class = uic.loadUiType("pytrader.ui")[0]
18 |
19 | class MyWindow(QMainWindow, form_class):
20 | def __init__(self):
21 | super().__init__()
22 | self.setupUi(self)
23 | self.show()
24 |
25 | self.kiwoom = Kiwoom()
26 | self.kiwoom.comm_connect()
27 | self.wrapper = KiwoomWrapper(self.kiwoom)
28 | if self.kiwoom.get_login_info("GetServerGubun"):
29 | self.server_gubun = "실제운영"
30 | else:
31 | self.server_gubun = "모의투자"
32 |
33 | self.code_list = self.kiwoom.get_code_list("0")
34 |
35 | # 메인 타이머
36 | self.timer = QTimer(self)
37 | self.timer.start(1000)
38 | self.timer.timeout.connect(self.timeout)
39 |
40 | # 자동 주문
41 | self.timer_stock = QTimer(self)
42 | self.timer_stock.start(1000*21)
43 | self.timer_stock.timeout.connect(self.timeout)
44 |
45 | # 잔고 및 보유종목 조회 타이머
46 | self.inquiryTimer = QTimer(self)
47 | self.inquiryTimer.start(1000*10)
48 | self.inquiryTimer.timeout.connect(self.timeout)
49 |
50 | self.setAccountComboBox()
51 | self.codeLineEdit.textChanged.connect(self.set_code_name)
52 | self.orderBtn.clicked.connect(self.send_order)
53 | self.inquiryBtn.clicked.connect(self.inquiry_balance)
54 |
55 | # 자동 주문
56 | # 자동 주문을 활성화 하려면 True로 설정
57 | self.is_automatic_order = True
58 | self.in_processing = False
59 |
60 | # 자동 선정 종목 리스트 테이블 설정
61 | self.set_automated_stocks()
62 | self.inquiry_balance()
63 |
64 | def timeout(self):
65 | """ 타임아웃 이벤트가 발생하면 호출되는 메서드 """
66 | # 어떤 타이머에 의해서 호출되었는지 확인
67 | sender = self.sender()
68 | if self.in_processing:
69 | return
70 | # 메인 타이머
71 | if id(sender) == id(self.timer):
72 | current_time = QTime.currentTime().toString("hh:mm:ss")
73 | # 상태바 설정
74 | state = ""
75 | if self.kiwoom.get_connect_state() == 1:
76 | state = self.server_gubun + " 서버 연결중"
77 | else:
78 | state = "서버 미연결"
79 | self.statusbar.showMessage("현재시간: " + current_time + " | " + state)
80 | # log
81 | if self.kiwoom.msg:
82 | self.logTextEdit.append(self.kiwoom.msg)
83 | self.kiwoom.msg = ""
84 | elif id(sender) == id(self.timer_stock):
85 | automatic_order_time = QTime.currentTime().toString("hhmm")
86 | # 자동 주문 실행
87 | # 1100은 11시 00분을 의미합니다.
88 | print("current time: %d" % int(automatic_order_time))
89 | if self.is_automatic_order and int(automatic_order_time) >= 900 and int(automatic_order_time) <= 930:
90 | self.is_automatic_order = False
91 | self.automatic_order()
92 | self.set_automated_stocks()
93 | # 실시간 조회 타이머
94 | else:
95 | if self.realtimeCheckBox.isChecked():
96 | self.inquiry_balance()
97 |
98 | def set_code_name(self):
99 | """ 종목코드에 해당하는 한글명을 codeNameLineEdit에 설정한다. """
100 | code = self.codeLineEdit.text()
101 |
102 | if code in self.code_list:
103 | code_name = self.kiwoom.get_master_code_name(code)
104 | self.codeNameLineEdit.setText(code_name)
105 |
106 | def setAccountComboBox(self):
107 | """ accountComboBox에 계좌번호를 설정한다. """
108 |
109 | try:
110 | cnt = int(self.kiwoom.get_login_info("ACCOUNT_CNT"))
111 | accountList = self.kiwoom.get_login_info("ACCNO").split(';')
112 | self.accountComboBox.addItems(accountList[0:cnt])
113 | except (KiwoomConnectError, ParameterTypeError, ParameterValueError) as e:
114 | self.show_dialog('Critical', e)
115 |
116 | def send_order(self):
117 | """ 키움서버로 주문정보를 전송한다. """
118 | order_type_table = {'신규매수': 1, '신규매도': 2, '매수취소': 3, '매도취소': 4}
119 | hoga_type_table = {'지정가': "00", '시장가': "03"}
120 |
121 | account = self.accountComboBox.currentText()
122 | order_type = order_type_table[self.orderTypeComboBox.currentText()]
123 | code = self.codeLineEdit.text()
124 | hoga_type = hoga_type_table[self.hogaTypeComboBox.currentText()]
125 | qty = self.qtySpinBox.value()
126 | price = self.priceSpinBox.value()
127 |
128 | try:
129 | self.kiwoom.send_order("수동주문", "0101", account, order_type, code, qty, price, hoga_type, "")
130 | except (ParameterTypeError, KiwoomProcessingError) as e:
131 | self.show_dialog('Critical', e)
132 |
133 | def inquiry_balance(self):
134 | """ 예수금상세현황과 계좌평가잔고내역을 요청후 테이블에 출력한다. """
135 | self.in_processing = True
136 | #self.inquiryTimer.stop()
137 | #self.timer_stock.stop()
138 |
139 | try:
140 | # 예수금상세현황요청
141 | self.kiwoom.set_input_value("계좌번호", self.accountComboBox.currentText())
142 | self.kiwoom.set_input_value("비밀번호", "0000")
143 | self.kiwoom.comm_rq_data("예수금상세현황요청", "opw00001", 0, "2000")
144 |
145 | # 계좌평가잔고내역요청 - opw00018 은 한번에 20개의 종목정보를 반환
146 | self.kiwoom.set_input_value("계좌번호", self.accountComboBox.currentText())
147 | self.kiwoom.set_input_value("비밀번호", "0000")
148 | self.kiwoom.comm_rq_data("계좌평가잔고내역요청", "opw00018", 0, "2000")
149 | while self.kiwoom.inquiry == '2':
150 | time.sleep(0.2)
151 | self.kiwoom.set_input_value("계좌번호", self.accountComboBox.currentText())
152 | self.kiwoom.set_input_value("비밀번호", "0000")
153 | self.kiwoom.comm_rq_data("계좌평가잔고내역요청", "opw00018", 2, "2")
154 | except (ParameterTypeError, ParameterValueError, KiwoomProcessingError) as e:
155 | self.show_dialog('Critical', e)
156 |
157 | # accountEvaluationTable 테이블에 정보 출력
158 |
159 | item = QTableWidgetItem(self.kiwoom.data_opw00001)
160 | item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
161 | self.accountEvaluationTable.setItem(0, 0, item)
162 |
163 | for i in range(1, 6):
164 | item = QTableWidgetItem(self.kiwoom.data_opw00018['account_evaluation'][i-1])
165 | item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
166 | self.accountEvaluationTable.setItem(0, i, item)
167 |
168 | self.accountEvaluationTable.resizeRowsToContents()
169 |
170 | # Item list
171 | item_count = len(self.kiwoom.data_opw00018['stocks'])
172 | self.stocksTable.setRowCount(item_count)
173 |
174 | with open('../data/stocks_in_account.txt', 'wt', encoding='utf-8') as f_stock:
175 | f_stock.write('%d\n'%self.kiwoom.data_opw00001)
176 | for i in range(item_count):
177 | row = self.kiwoom.data_opw00018['stocks'][i]
178 | for j in range(len(row)-1):
179 | f_stock.write('%s,'%row[j].replace(',', ''))
180 | if j == len(row)-2:
181 | f_stock.write('%s,'%row[-1])
182 | item = QTableWidgetItem(row[j])
183 | item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
184 | self.stocksTable.setItem(i, j, item)
185 | f_stock.write('\n')
186 |
187 | self.stocksTable.resizeRowsToContents()
188 |
189 | # 데이터 초기화
190 | self.kiwoom.opw_data_reset()
191 |
192 | self.in_processing = False
193 | # inquiryTimer 재시작
194 | #self.inquiryTimer.start(1000*10)
195 | #self.timer_stock.start(1000*100)
196 |
197 |
198 | # 경고창
199 | def show_dialog(self, grade, error):
200 | grade_table = {'Information': 1, 'Warning': 2, 'Critical': 3, 'Question': 4}
201 |
202 | dialog = QMessageBox()
203 | dialog.setIcon(grade_table[grade])
204 | dialog.setText(error.msg)
205 | dialog.setWindowTitle(grade)
206 | dialog.setStandardButtons(QMessageBox.Ok)
207 | dialog.exec_()
208 |
209 | def set_automated_stocks(self):
210 | file_list = ["../data/sell_list.txt", "../data/buy_list.txt"]
211 | automated_stocks = []
212 |
213 | try:
214 | for file in file_list:
215 | # utf-8로 작성된 파일을
216 | # cp949 환경에서 읽기위해서 encoding 지정
217 | with open(file, 'rt', encoding='utf-8') as f:
218 | stocks_list = f.readlines()
219 | automated_stocks += stocks_list
220 | except Exception as e:
221 | print(e)
222 | e.msg = "set_automated_stocks() 에러"
223 | self.show_dialog('Critical', e)
224 | return
225 |
226 | # 테이블 행수 설정
227 | cnt = len(automated_stocks)
228 | self.automatedStocksTable.setRowCount(cnt)
229 |
230 | # 테이블에 출력
231 | for i in range(cnt):
232 | stocks = automated_stocks[i].split(';')
233 | for j in range(len(stocks)):
234 | if j == 1:
235 | name = self.kiwoom.get_master_code_name(stocks[j].rstrip())
236 | item = QTableWidgetItem(name)
237 | else:
238 | item = QTableWidgetItem(stocks[j].rstrip())
239 | item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter)
240 | self.automatedStocksTable.setItem(i, j, item)
241 | self.automatedStocksTable.resizeRowsToContents()
242 |
243 | def automatic_order(self):
244 | file_list = ["../data/sell_list.txt", "../data/buy_list.txt"]
245 | hoga_type_table = {'지정가': "00", '시장가': "03"}
246 | account = self.accountComboBox.currentText()
247 | automated_stocks = []
248 | self.in_processing = True
249 | # 파일읽기
250 | try:
251 | for file in file_list:
252 | # utf-8로 작성된 파일을
253 | # cp949 환경에서 읽기위해서 encoding 지정
254 | with open(file, 'rt', encoding='utf-8') as f:
255 | stocks_list = f.readlines()
256 | automated_stocks += stocks_list
257 | except Exception as e:
258 | print(e)
259 | #e.msg = "automatic_order() 에러"
260 | #self.show_dialog('Critical', e)
261 | return
262 |
263 | cnt = len(automated_stocks)
264 |
265 | # 주문하기
266 | buy_result = []
267 | sell_result = []
268 |
269 | for i in range(cnt):
270 | time.sleep(0.3)
271 | stocks = automated_stocks[i].split(';')
272 |
273 | code = stocks[1]
274 | hoga = stocks[2]
275 | qty = stocks[3]
276 | price = stocks[4]
277 |
278 | try:
279 | if stocks[5].rstrip() == '매수전':
280 | self.kiwoom.send_order("자동매수주문", "0101", account, 1, code, int(qty), int(price), hoga_type_table[hoga], "")
281 | print("order_no: ", self.kiwoom.order_no)
282 |
283 | # 주문 접수시
284 | if self.kiwoom.order_no:
285 | buy_result += automated_stocks[i].replace("매수전", "매수완료")
286 | self.kiwoom.order_no = ""
287 | # 주문 미접수시
288 | else:
289 | buy_result += automated_stocks[i]
290 |
291 | # 참고: 해당 종목을 현재도 보유하고 있다고 가정함.
292 | elif stocks[5].rstrip() == '매도전':
293 | self.kiwoom.send_order("자동매도주문", "0101", account, 2, code, int(qty), 0, hoga_type_table[hoga], "")
294 | print("order_no: ", self.kiwoom.order_no)
295 |
296 | # 주문 접수시
297 | if self.kiwoom.order_no:
298 | sell_result += automated_stocks[i].replace("매도전", "매도완료")
299 | self.kiwoom.order_no = ""
300 | # 주문 미접수시
301 | else:
302 | sell_result += automated_stocks[i]
303 | elif stocks[5].rstrip() == '매수완료':
304 | buy_result += automated_stocks[i]
305 | elif stocks[5].rstrip() == '매도완료':
306 | sell_result += automated_stocks[i]
307 |
308 | except (ParameterTypeError, KiwoomProcessingError) as e:
309 | #self.show_dialog('Critical', e)
310 | print(e)
311 |
312 | # 잔고및 보유종목 디스플레이 갱신
313 | self.inquiry_balance()
314 |
315 | # 결과저장하기
316 | for file, result in zip(file_list, [sell_result, buy_result]):
317 | with open(file, 'wt', encoding='utf-8') as f:
318 | for data in result:
319 | f.write(data)
320 | self.in_processing = False
321 |
322 |
323 | if __name__ == "__main__":
324 | app = QApplication(sys.argv)
325 | myWindow = MyWindow()
326 | myWindow.show()
327 | app.exec_()
328 |
--------------------------------------------------------------------------------
/pytrader.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 903
10 | 580
11 |
12 |
13 |
14 | PyTrader v0.5
15 |
16 |
17 |
18 |
19 |
20 | 20
21 | 20
22 | 181
23 | 291
24 |
25 |
26 |
27 | 수동주문
28 |
29 |
30 |
31 |
32 | 58
33 | 140
34 | 101
35 | 22
36 |
37 |
38 | -
39 |
40 | 지정가
41 |
42 |
43 | -
44 |
45 | 시장가
46 |
47 |
48 |
49 |
50 |
51 |
52 | 20
53 | 200
54 | 31
55 | 20
56 |
57 |
58 |
59 | 가격
60 |
61 |
62 |
63 |
64 |
65 | 20
66 | 170
67 | 31
68 | 20
69 |
70 |
71 |
72 | 수량
73 |
74 |
75 |
76 |
77 |
78 | 20
79 | 140
80 | 31
81 | 20
82 |
83 |
84 |
85 | 종류
86 |
87 |
88 |
89 |
90 |
91 | 60
92 | 110
93 | 101
94 | 20
95 |
96 |
97 |
98 | background-color: rgb(0, 255, 255);
99 |
100 |
101 |
102 |
103 |
104 | 60
105 | 170
106 | 101
107 | 22
108 |
109 |
110 |
111 | 100
112 |
113 |
114 |
115 |
116 |
117 | 60
118 | 200
119 | 101
120 | 22
121 |
122 |
123 |
124 | 5000
125 |
126 |
127 | 50
128 |
129 |
130 |
131 |
132 |
133 | 20
134 | 230
135 | 141
136 | 41
137 |
138 |
139 |
140 | 현금주문
141 |
142 |
143 |
144 |
145 |
146 | 60
147 | 80
148 | 101
149 | 20
150 |
151 |
152 |
153 |
154 |
155 |
156 | 21
157 | 80
158 | 31
159 | 20
160 |
161 |
162 |
163 | 종목
164 |
165 |
166 |
167 |
168 |
169 | 20
170 | 50
171 | 31
172 | 20
173 |
174 |
175 |
176 | 주문
177 |
178 |
179 |
180 |
181 |
182 | 60
183 | 50
184 | 101
185 | 20
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 | 60
213 | 20
214 | 101
215 | 20
216 |
217 |
218 |
219 |
220 |
221 |
222 | 21
223 | 20
224 | 31
225 | 20
226 |
227 |
228 |
229 | 계좌
230 |
231 |
232 |
233 |
234 |
235 |
236 | 209
237 | 19
238 | 681
239 | 311
240 |
241 |
242 |
243 | 잔고 및 보유종목 현황
244 |
245 |
246 |
247 |
248 | 15
249 | 21
250 | 651
251 | 51
252 |
253 |
254 |
255 | 1
256 |
257 |
258 |
259 |
260 | 예수금 (d+2)
261 |
262 |
263 |
264 |
265 | 총매입
266 |
267 |
268 |
269 |
270 | 총평가
271 |
272 |
273 |
274 |
275 | 총손익
276 |
277 |
278 |
279 |
280 | 총수익률 (%)
281 |
282 |
283 |
284 |
285 | 추정자산
286 |
287 |
288 |
289 |
290 |
291 |
292 | 15
293 | 81
294 | 651
295 | 181
296 |
297 |
298 |
299 |
300 | 종목명
301 |
302 |
303 |
304 |
305 | 보유량
306 |
307 |
308 |
309 |
310 | 매입가
311 |
312 |
313 |
314 |
315 | 현재가
316 |
317 |
318 |
319 |
320 | 평가손익
321 |
322 |
323 |
324 |
325 | 수익률 (%)
326 |
327 |
328 |
329 |
330 |
331 |
332 | 500
333 | 280
334 | 81
335 | 20
336 |
337 |
338 |
339 | 실시간 조회
340 |
341 |
342 |
343 |
344 |
345 | 590
346 | 270
347 | 75
348 | 31
349 |
350 |
351 |
352 | 조회
353 |
354 |
355 |
356 |
357 |
358 |
359 | 209
360 | 329
361 | 681
362 | 211
363 |
364 |
365 |
366 | 자동 선정 종목 리스트
367 |
368 |
369 |
370 |
371 | 15
372 | 21
373 | 651
374 | 171
375 |
376 |
377 |
378 |
379 | 주문유형
380 |
381 |
382 |
383 |
384 | 종목명
385 |
386 |
387 |
388 |
389 | 호가구분
390 |
391 |
392 |
393 |
394 | 수량
395 |
396 |
397 |
398 |
399 | 가격
400 |
401 |
402 |
403 |
404 | 상태
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 | 19
413 | 319
414 | 181
415 | 221
416 |
417 |
418 |
419 | 로그
420 |
421 |
422 |
423 |
424 | 13
425 | 20
426 | 161
427 | 191
428 |
429 |
430 |
431 |
432 |
433 |
443 |
444 |
445 |
446 |
447 |
448 |
--------------------------------------------------------------------------------
/save_data.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PyQt5.QtWidgets import QApplication
3 | from pykiwoom.kiwoom import *
4 | from pykiwoom.wrapper import *
5 | import numpy as np
6 | import pandas as pd
7 | import sqlite3
8 | import datetime
9 |
10 | MARKET_KOSPI = 0
11 | MARKET_KOSDAK = 10
12 |
13 | class DailyData:
14 | def __init__(self):
15 | self.kiwoom = Kiwoom()
16 | self.kiwoom.comm_connect()
17 | self.wrapper = KiwoomWrapper(self.kiwoom)
18 | self.get_code_list()
19 | print(self.kospi_codes)
20 | print(self.kosdak_codes)
21 |
22 | def get_code_list(self):
23 | self.kospi_codes = self.kiwoom.get_codelist_by_market(MARKET_KOSPI)
24 | self.kosdak_codes = self.kiwoom.get_codelist_by_market(MARKET_KOSDAK)
25 |
26 | def check_recent_file(self, code):
27 | import os
28 | from time import strftime, gmtime, time
29 | fname = '../data/hdf/%s.hdf'%code
30 | try:
31 | print(time() - os.path.getmtime(fname))
32 | if (time() - os.path.getmtime(fname)) < 200000:
33 | return True
34 | except FileNotFoundError:
35 | return False
36 | return False
37 |
38 | def save_all_data(self):
39 | today = datetime.date.today().strftime("%Y%m%d")
40 | #today = datetime.date(2011,9,1).strftime("%Y%m%d")
41 | print(today, len(self.kosdak_codes), len(self.kospi_codes))
42 |
43 | # load code list from account
44 | DATA = []
45 | with open('../data/stocks_in_account.txt', encoding='utf-8') as f_stocks:
46 | for line in f_stocks.readlines():
47 | data = line.split(',')
48 | DATA.append([data[6].replace('A', ''), data[1], data[0]])
49 | for idx, code in enumerate(DATA):
50 | if code == '':
51 | continue
52 | print("get data of %s" % code)
53 | if self.check_recent_file(code[0]): continue
54 | self.save_table(code[0], today)
55 |
56 | for code in self.kospi_codes:
57 | if code == '':
58 | continue
59 | print("get data of %s" % code)
60 | if self.check_recent_file(code): continue
61 | self.save_table(code, today)
62 | for code in self.kosdak_codes:
63 | if code == '':
64 | continue
65 | print("get data of %s" % code)
66 | if self.check_recent_file(code): continue
67 | self.save_table(code, today)
68 |
69 | def save_table(self, code, date):
70 | TR_REQ_TIME_INTERVAL = 4
71 | time.sleep(TR_REQ_TIME_INTERVAL)
72 | data_81 = self.wrapper.get_data_opt10081(code, date)
73 | time.sleep(TR_REQ_TIME_INTERVAL)
74 | data_86 = self.wrapper.get_data_opt10086(code, date)
75 | col_86 = ['전일비', '등락률', '금액(백만)', '신용비', '개인', '기관', '외인수량', '외국계', '프로그램',
76 | '외인비', '체결강도', '외인보유', '외인비중', '외인순매수', '기관순매수', '개인순매수', '신용잔고율']
77 | data = pd.concat([data_81, data_86.loc[:, col_86]], axis=1)
78 | #con = sqlite3.connect("../data/stock.db")
79 | try:
80 | data = data.loc[data.index > int(self.kiwoom.start_date.strftime("%Y%m%d"))]
81 | #orig_data = pd.read_sql("SELECT * FROM '%s'" % code, con, index_col='일자').sort_index()
82 | orig_data = pd.read_hdf("../data/hdf/%s.hdf" % code, 'day').sort_index()
83 | end_date = orig_data.index[-1]
84 | orig_data = orig_data.loc[orig_data.index < end_date]
85 | data = data.loc[data.index >= end_date]
86 | data = pd.concat([orig_data, data], axis=0)
87 | except (FileNotFoundError, IndexError) as e:
88 | print(e)
89 | pass
90 | finally:
91 | data.index.name = '일자'
92 | if len(data) != 0:
93 | #data.to_sql(code, con, if_exists='replace')
94 | data.to_hdf('../data/hdf/%s.hdf'%code, 'day', mode='w')
95 |
96 |
97 | if __name__ == '__main__':
98 | app = QApplication(sys.argv)
99 |
100 | daily_data = DailyData()
101 |
102 | daily_data.save_all_data()
103 |
104 | import glob
105 | import zipfile
106 | filelist = glob.glob('../data/hdf/*.hdf')
107 | with zipfile.ZipFile('../data/hdf.zip', 'w', zipfile.ZIP_DEFLATED) as myzip:
108 | for f in filelist:
109 | myzip.write(f)
110 |
--------------------------------------------------------------------------------
/sell_list.txt:
--------------------------------------------------------------------------------
1 | 매도;002680;시장가;235;0;매도전;
2 | 매도;004870;시장가;68;0;매도전;
3 | 매도;016670;시장가;137;0;매도전;
4 | 매도;027830;시장가;2;0;매도전;
5 | 매도;033320;시장가;79;0;매도전;
6 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | import pandas as pd
3 | import sqlite3
4 | import glob
5 |
6 | def test_dataframe_replace():
7 | a = pd.DataFrame([{'a': '20170102', 'b': '--10', 'c': '+20'}, {'a': '20170103', 'b': '--20', 'c': '--20'}])
8 | a.loc[:,'a'] = a.loc[:,'a'].str[4:6]
9 | for c in a.columns:
10 | a.loc[:,c] = a.loc[:,c].str.replace('--', '-')
11 | print(a)
12 |
13 | def concat_df():
14 | A = pd.DataFrame([{'일자':'20161201', '가격': '1231', '거래': '1231'}, {'일자':'20161200', '가격': '1231', '거래': '1231'}])
15 | B = pd.DataFrame([{'일자':'20161101', '가격': '1231', '거래': '1231'}, {'일자':'20161100', '가격': '1231', '거래': '1231'}])
16 | C = A.loc[:, ['가격', '거래']]
17 | C.index = A.loc[:,'일자']
18 | D = B.loc[:, ['가격', '거래']]
19 | D.index = B.loc[:,'일자']
20 | print(C)
21 | print(D)
22 | E = pd.concat([C,D], axis=0)
23 | print(E)
24 | F = E.loc[E.index < C.index[0]]
25 | print(F)
26 |
27 | def get_sqlite(code):
28 | con = sqlite3.connect("stock.db")
29 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자')
30 | print(data.index[0])
31 | print(data.head())
32 | data = data.loc[data.index > '20170102']
33 | data.to_sql(code, con, if_exists='replace')
34 |
35 | def convert_index_sqlite():
36 | con = sqlite3.connect("stock.db")
37 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
38 | for code in code_list:
39 | print("convert %s" % code[0])
40 | if '(' in code[0]:
41 | continue
42 | try:
43 | data = pd.read_sql("SELECT * from '%s'" % code[0], con, index_col='index')
44 | except:
45 | data = pd.read_sql("SELECT * from '%s'" % code[0], con, index_col='일자')
46 | data.index.name = '일자'
47 | #data = data.loc[data.index > '20010101']
48 | data.to_sql(code[0], con, if_exists='replace')
49 |
50 | def delete_table(table_name):
51 | con = sqlite3.connect("stock.db")
52 | con.execute("DROP TABLE '%s'" % table_name)
53 |
54 | def print_table_columns():
55 | con = sqlite3.connect("../data/stock.db")
56 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
57 | code = code_list[0][0]
58 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자')
59 | print(data.columns)
60 |
61 | def print_table_tail():
62 | con = sqlite3.connect("../data/stock.db")
63 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
64 | code = code_list[0][0]
65 | for code in code_list:
66 | code = code[0]
67 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자')
68 | print(data.index[len(data)-3:len(data)])
69 |
70 | def convert_sql_h5():
71 | con = sqlite3.connect("../data/stock.db")
72 | code_list = con.execute("SELECT name from sqlite_master WHERE type='table'").fetchall()
73 | for code in code_list:
74 | code = code[0]
75 | data = pd.read_sql("SELECT * from '%s'" % code, con, index_col='일자')
76 | data.to_hdf('../data/stock/%s.h5'%code,'table',append=True)
77 |
78 | def read_h5():
79 | code_list = glob.glob('../data/stock/*.h5')
80 | for code in code_list[:10]:
81 | data = pd.read_hdf(code, 'table').sort_index()
82 | data = data.loc[data.index >= str(20160101)]
83 | data = data.loc[data.index <= str(20160630)]
84 | print(data.head())
85 |
86 | if __name__ == '__main__':
87 | read_h5()
88 |
--------------------------------------------------------------------------------
/test_kiwoom.py:
--------------------------------------------------------------------------------
1 | from pykiwoom.kiwoom import *
2 | from pykiwoom.wrapper import *
3 |
4 |
5 | if __name__ == '__main__':
6 | kiwoom = Kiwoom()
7 | kiwoom.comm_connect()
8 | wrapper = KiwoomWrapper(kiwoom)
9 |
10 | data = wrapper.get_data_opt10086("035420", "20170101")
11 | print(len(data))
12 |
--------------------------------------------------------------------------------
/update_version.py:
--------------------------------------------------------------------------------
1 | from pywinauto import application
2 | from pywinauto import timings
3 | import time
4 | import os
5 |
6 |
7 | # Account
8 | account = []
9 | with open("account.txt", 'r') as f:
10 | account = f.readlines()
11 | app = application.Application()
12 | app.start("C:/Kiwoom/KiwoomFlash2/khministarter.exe")
13 |
14 | title = "번개 Login"
15 | dlg = timings.WaitUntilPasses(20, 0.5, lambda: app.window_(title=title))
16 |
17 | idForm = dlg.Edit0
18 | idForm.SetFocus()
19 | idForm.TypeKeys(account[0])
20 |
21 | passForm = dlg.Edit2
22 | passForm.SetFocus()
23 | passForm.TypeKeys(account[1])
24 |
25 | certForm = dlg.Edit3
26 | certForm.SetFocus()
27 | certForm.TypeKeys(account[2])
28 |
29 | loginBtn = dlg.Button0
30 | loginBtn.Click()
31 |
32 | # 업데이트가 완료될 때 까지 대기
33 | while True:
34 | time.sleep(5)
35 | with os.popen('tasklist /FI "IMAGENAME eq khmini.exe"') as f:
36 | lines = f.readlines()
37 | if len(lines) >= 3:
38 | break
39 |
40 | # 번개2 종료
41 | time.sleep(30)
42 | os.system("taskkill /im khmini.exe")
43 |
44 |
--------------------------------------------------------------------------------
/webreader.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import pandas as pd
3 | from bs4 import BeautifulSoup
4 | import datetime
5 |
6 | def get_financial_statements(code):
7 | url = "http://companyinfo.stock.naver.com/v1/company/ajax/cF1001.aspx?cmp_cd=%s&fin_typ=0&freq_typ=Y" % (code)
8 | html = requests.get(url).text
9 |
10 | html = html.replace('
연간 | ', "")
11 | html = html.replace("(IFRS연결)", "")
12 | html = html.replace("(IFRS별도)", "")
13 | html = html.replace('\t', '')
14 | html = html.replace('\n', '')
15 | html = html.replace('\r', '')
16 |
17 | html = html.replace('2011/12', '2011')
18 | html = html.replace('2012/03', '2011')
19 | html = html.replace('2012/12', '2012')
20 | html = html.replace('2013/03', '2012')
21 | html = html.replace('2013/12', '2013')
22 | html = html.replace('2014/03', '2013')
23 | html = html.replace('2014/12', '2014')
24 | html = html.replace('2015/03', '2014')
25 | html = html.replace('2015/12', '2015')
26 |
27 | df_list = pd.read_html(html, index_col='주요재무정보')
28 | df = df_list[0]
29 | return df
30 |
31 | def get_3year_treasury():
32 | url = "http://www.index.go.kr/strata/jsp/showStblGams3.jsp?stts_cd=107301&idx_cd=1073"
33 | html = requests.get(url).text
34 |
35 | soup = BeautifulSoup(html, 'lxml')
36 | tr_data = soup.find_all('tr', id='tr_107301_1')
37 | td_data = tr_data[0].find_all('td')
38 |
39 | treasury_3year = {}
40 | start_year = 1997
41 |
42 | for x in td_data:
43 | treasury_3year[start_year] = x.text
44 | start_year += 1
45 |
46 | print(treasury_3year)
47 | return treasury_3year
48 |
49 | def get_dividend_yield(code):
50 | url = "http://companyinfo.stock.naver.com/company/c1010001.aspx?cmp_cd=" + code
51 | html = requests.get(url).text
52 |
53 | soup = BeautifulSoup(html, 'lxml')
54 | td_data = soup.find_all('td', {'class': 'cmp-table-cell td0301'})
55 | dt_data = td_data[0].find_all('dt')
56 |
57 | dividend_yield = dt_data[5].text
58 | dividend_yield = dividend_yield.split(' ')[1]
59 | dividend_yield = dividend_yield[:-1]
60 | return dividend_yield
61 |
62 | def get_estimated_dividend_yield(code):
63 | df = get_financial_statements(code)
64 |
65 | column = df.columns[5]
66 | cur_year = df[column]
67 | estimated_dividend_yield = cur_year['현금배당수익률']
68 | return estimated_dividend_yield
69 |
70 | def get_current_3year_treasury():
71 | url = "http://info.finance.naver.com/marketindex/interestDailyQuote.nhn?marketindexCd=IRR_GOVT03Y&page=1"
72 | html = requests.get(url).text
73 |
74 | soup = BeautifulSoup(html, 'lxml')
75 | tbody_data = soup.find_all('tbody')
76 | tr_data = tbody_data[0].find_all('tr')
77 | td_data = tr_data[0].find_all('td')
78 | return td_data[1].text
79 |
80 | def get_previous_dividend_yield(code):
81 | df = get_financial_statements(code)
82 | dividend_yields = df.ix['현금배당수익률']
83 |
84 | ret = {}
85 | now = datetime.datetime.now()
86 | year = now.year - 5
87 |
88 | for dividend_yield in dividend_yields.values:
89 | ret[year] = dividend_yield
90 | year += 1
91 |
92 | return ret
93 |
94 |
95 | if __name__ == "__main__":
96 | #print(get_financial_statements('035720'))
97 | #get_3year_treasury()
98 | #print(get_dividend_yield('058470'))
99 | #print(get_estimated_dividend_yield('058470'))
100 | #print(get_current_3year_treasury())
101 | print(get_previous_dividend_yield('058470'))
102 |
--------------------------------------------------------------------------------