├── .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 | 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 | 21 | 22 | 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 | 126 | 127 | 140 | 141 | 142 | 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 | 181 | 182 | 183 | 184 | 187 | 188 | 191 | 192 | 193 | 194 | 197 | 198 | 201 | 202 | 205 | 206 | 207 | 208 | 211 | 212 | 215 | 216 | 219 | 220 | 223 | 224 | 225 | 226 | 229 | 230 | 233 | 234 | 237 | 238 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 269 | 270 | 286 | 287 | 298 | 299 | 317 | 318 | 336 | 337 | 357 | 358 | 379 | 380 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 415 | 416 | 417 | 418 | 1487475077022 419 | 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 | 453 | 456 | 457 | 458 | 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 | ![pytrader](pytrader.png) 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 | 434 | 435 | 436 | 0 437 | 0 438 | 903 439 | 21 440 | 441 | 442 | 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 | --------------------------------------------------------------------------------