import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QAxContainer import *
import logging
import logging.handlers
from datetime import datetime
form_class = uic.loadUiType("upperLimitPriceTrading.ui")[0] # ui 파일을 로드하여 form_class 생성
class MyWindow(QMainWindow, form_class): # MyWindow 클래스 QMainWindow, form_class 클래스를 상속 받아 생성됨
def __init__(self): # MyWindow 클래스의 초기화 함수(생성자)
super().__init__() # 부모클래스 QMainWindow 클래스의 초기화 함수(생성자)를 호출
self.setupUi(self) # ui 파일 화면 출력
global logger # 전체에서 로그를 사용하기 위해 global로 선언을 해줌(전역 변수)
logger = logging.getLogger('upperLimitPriceTradingLogger') # 로그 인스턴스를 만든다
self.set_logger() # 로그 인스턴스 환경 설정을 셋팅함
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1") # 키움증권 Open API+의 ProgID를 사용하여 생성된 QAxWidget을 kiwoom 변수에 할당
self.btnStart.setDisabled(True) # 거래시작 버튼을 비활성화 상태로 변경
self.btnStop.setDisabled(True) # 거래중지 버튼을 비활성화 상태로 변경
self.btnReStart.setDisabled(True) # 재시작 버튼을 비활성화 상태로 변경
self.lePrice.setDisabled(True) # 투입금 입력란을 비활성화 상태로 변경
self.cbAcctNo.setDisabled(True) # 계좌번호 선택란을 비활성화 상태로 변경
self.leD2Price.setDisabled(True) # D2예수금 입력란을 비활성화 상태로 변경
self.cbCdtNm.setDisabled(True) # 조건검색명 선택란을 비활성화 상태로 변경
self.leTotalPrice.setDisabled(True) # 총투입금 입력란을 비활성화 상태로 변경
self.leProfits.setDisabled(True) # 수익금 입력란을 비활성화 상태로 변경
self.leProfitsPc.setDisabled(True) # 수익률 입력란을 비활성화 상태로 변경
self.pteBuyLog.setDisabled(True) # 매도 종목 노출란을 비활성화 상태로 변경
self.pteSellLog.setDisabled(True) # 매수 종목 노출란을 비활성화 상태로 변경
self.pteLog.setDisabled(True) # 전체 내역 노출란을 비활성화 상태로 변경
self.btnLogin.clicked.connect(self.btn_login) # ui 파일을 생성할때 작성한 로그인 버튼의 objectName 으로 클릭 이벤트가 발생할 경우 btn_login 함수를 호출
self.kiwoom.OnEventConnect.connect(self.event_connect) # 키움 서버 접속 관련 이벤트가 발생할 경우 event_connect 함수 호출
self.kiwoom.OnReceiveConditionVer.connect(self.receive_condition_var) # 키움 사용자 조건검색식 수신 관련 이벤트가 발생할 경우 receive_condition_var 함수 호출
def set_logger(self): # 로그 환경을 설정해주는 함수
fomatter = logging.Formatter('[%(levelname)s|%(lineno)s] %(asctime)s > %(message)s') # 로그를 남길 방식으로 "[로그레벨|라인번호] 날짜 시간,밀리초 > 메시지" 형식의 포매터를 만든다
logday = datetime.today().strftime("%Y%m%d") # 로그 파일 네임에 들어갈 날짜를 만듬 (YYYYmmdd 형태)
fileMaxByte = 1024 * 1024 * 100 # 파일 최대 용량인 100MB를 변수에 할당 (100MB, 102,400KB)
fileHandler = logging.handlers.RotatingFileHandler('./log/stock_' + str(logday) + '.log', maxBytes=fileMaxByte, backupCount=10) # 파일에 로그를 출력하는 핸들러 (100MB가 넘으면 최대 10개까지 신규 생성)
streamHandler = logging.StreamHandler() # 콘솔에 로그를 출력하는 핸들러
fileHandler.setFormatter(fomatter) # 파일에 로그를 출력하는 핸들러에 포매터를 지정
streamHandler.setFormatter(fomatter) # 콘솔에 로그를 출력하는 핸들러에 포매터를 지정
logger.addHandler(fileHandler) # 로그 인스턴스에 파일에 로그를 출력하는 핸들러를 추가
logger.addHandler(streamHandler) # 로그 인스턴스에 콘솔에 로그를 출력하는 핸들러를 추가
logger.setLevel(logging.DEBUG) # 로그 레벨을 디버그로 만듬
def btn_login(self): # Login 버튼 클릭 시 실행되는 함수
logger.debug("Login 버튼 클릭") # debug 레벨 로그를 남김
ret = self.kiwoom.dynamicCall("CommConnect()") # 키움 로그인 윈도우를 실행
def event_connect(self, err_code): # 키움 서버 접속 관련 이벤트가 발생할 경우 실행되는 함수
if err_code == 0: # err_code가 0이면 로그인 성공 그외 실패
logger.info("로그인 성공") # info 레벨 로그를 남김
self.pteLog.appendPlainText("로그인 성공") # ui 파일을 생성할때 작성한 plainTextEdit의 objectName 으로 해당 plainTextEdit에 텍스트를 추가함
account_num = self.kiwoom.dynamicCall("GetLoginInfo(QString)", ["ACCNO"]) # 키움 dynamicCall 함수를 통해 GetLoginInfo 함수를 호출하여 계좌번호를 가져옴
logger.debug("계좌번호: " + account_num.rstrip(';')) # debug 레벨 로그를 남김
self.pteLog.appendPlainText("계좌번호: " + account_num.rstrip(';')) # 키움은 전체 계좌를 반환하며 각 계좌 번호 끝에 세미콜론(;)이 붙어 있음으로 제거하여 plainTextEdit에 텍스트를 추가함
self.cbAcctNo.addItem(account_num.rstrip(';')) # 계좌번호 선택 박스에 계좌번호 셋팅
self.btnStart.setDisabled(False) # 거래시작 버튼을 활성화 상태로 변경
self.lePrice.setDisabled(False) # 투입금 입력란을 활성화 상태로 변경
self.cbAcctNo.setDisabled(False) # 계좌번호 선택란을 활성화 상태로 변경
self.cbCdtNm.setDisabled(False) # 조건검색명 선택란을 활성화 상태로 변경
self.pteBuyLog.setDisabled(False) # 매도 종목 노출란을 활성화 상태로 변경
self.pteSellLog.setDisabled(False) # 매수 종목 노출란을 활성화 상태로 변경
self.pteLog.setDisabled(False) # 전체 내역 노출란을 활성화 상태로 변경
self.btnLogin.setDisabled(True) # 로그인 버튼을 비활성화 상태로 변경
self.leTotalPrice.setDisabled(True) # 총투입금 입력란을 비활성화 상태로 변경
self.leProfits.setDisabled(True) # 수익금 입력란을 비활성화 상태로 변경
self.leProfitsPc.setDisabled(True) # 수익률 입력란을 비활성화 상태로 변경
self.leTotalPrice.setText('0') # 총투입금을 0원으로 셋팅
self.leProfits.setText('0') # 수익금을 0원으로 셋팅
self.leProfitsPc.setText('0%') # 수익률을 0%으로 셋팅
self.kiwoom.dynamicCall('GetConditionLoad()') # 키움 서버에 사용자 조건식 목록을 요청
QMessageBox.about(self, "message", "계좌 비밀번호 입력 및 환경 설정을 진행해주세요.")
else:
logger.info("로그인 실패") # info 레벨 로그를 남김
self.pteLog.appendPlainText("로그인 실패") # ui 파일을 생성할때 작성한 plainTextEdit의 objectName 으로 해당 plainTextEdit에 텍스트를 추가함
def receive_condition_var(self, bRet, sMsg): # 사용자 조건검색식 수신 함수
logger.debug("receive_condition_var bRet: "+str(bRet)) # debug 레벨 로그를 남김
logger.debug("receive_condition_var sMsg: "+str(sMsg)) # debug 레벨 로그를 남김
conditionNameList = self.kiwoom.dynamicCall('GetConditionNameList()') # 수신된 사용자 조건검색식 리스트를 받아옴 (ex. 인덱스^조건명;)
conditionNameListArray = conditionNameList.rstrip(';').split(';') # 조건검색식 리스트에 마지막 ";" 기호를 삭제하고 ";" 기호 기준 분리
logger.info("conditionNameListArray:" + str(conditionNameListArray)) # info 레벨 로그를 남김
for i in range(0, len(conditionNameListArray)): # 조건검색식 개수만큼 반복
self.cbCdtNm.addItem(conditionNameListArray[i]) # 콤보 박스에 조건 셋팅
index = self.cbCdtNm.findText('191^ULPTrading') # 조건검색식 명으로 순번을 찾음
if index >= 0: # 해당 순번이 있다면 (해당 조건검색식 명이 있다면)
self.cbCdtNm.setCurrentIndex(index) # 해당 순번을 선택 (해당 조검검색식을 선택)
# py 파일 실행시 제일 먼저 동작
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow() # MyWindow 클래스를 생성하여 myWondow 변수에 할당
myWindow.show() # MyWindow 클래스를 노출
app.exec_() # 메인 이벤트 루프에 진입 후 프로그램이 종료될 때까지 무한 루프 상태 대기