[키움 자동매매 프로그램] - 계좌평가잔고내역 조회(Qthread_1.py) 구현
Qthread_1.py 구현
이전 포스팅인 pytrader.py 구현에서 계좌평가잔고내역 조회가 구현된 코드이다.
Qthread_1.py - init 생성
Qthread_1은 pytrader와 마찬가지로 클래스로 구성되므로 생성자 생성 및 초기화해주는 부분이 필요하다.
from PyQt5.QtCore import *
from kiwoom import Kiwoom
from PyQt5.QtWidgets import *
class Thread1(QThread) :
def __init__(self, parent) :
super().__init__(parent)
self.parent = parent
self.k = Kiwoom()
self.Acc_Screen = "1000"
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot)
self.detail_account_info_event_loop = QEventLoop()
self.getItemList()
self.detail_account_mystock()
QThread를 상속하며 Kiwoom 객체를 초기화한다.
Acc_Screen은 서버에 데이터를 요청하기 위한 값 이라 생각하면 된다.
계좌평가잔고내역 조회
구현과정은 사용자가 서버에 특정 종목에 대한 데이터를 요청하면 서버에서 데이터를 처리하여 사용자에게 전달한다.
서버 데이터 요청 및 수신
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot)
self.detail_account_info_event_loop = QEventLoop()
pytrader.py를 구현할 때 설명했던 OnReceiveTrdata로 계좌평가잔고내역을 수신하기 위해 필요한 이벤트이다.
또한 QEventLoop()로 이벤트를 대기하고 이벤트가 처리될 때 까지 기다린다.
trdata_slot 메서드를 보면 다음과 같다.
def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
if sRQName == "계좌평가잔고내역요청":
column_head = ["종목번호", "종목명", "보유수량", "매입가", "현재가", "평가손익", "수익률(%)"]
colCount = len(column_head)
rowCount = self.k.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
self.parent.stocklistTableWidget_2.setColumnCount(colCount)
self.parent.stocklistTableWidget_2.setRowCount(rowCount)
self.parent.stocklistTableWidget_2.setHorizontalHeaderLabels(column_head)
totalBuyingPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총매입금액"))
currentTotalPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총평가금액"))
balanceAsset = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "추정예탁자산"))
totalEstimateProfit = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총평가손익금액"))
total_profit_loss_rate = float(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총수익률(%)"))
self.parent.label_l6.setText(str(format(totalBuyingPrice, ",")))
self.parent.label_l7.setText(str(format(currentTotalPrice, ",")))
self.parent.label_l8.setText(str(format(balanceAsset, ",")))
self.parent.label_l9.setText(str(format(totalEstimateProfit, ",")))
self.parent.label_l10.setText(str(format(total_profit_loss_rate, ",")))
for index in range(rowCount) :
itemCode = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "종목번호").strip(" ").strip("A")
itemName = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "종목명")
amount = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "보유수량"))
buyingPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매입가"))
currentPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "현재가"))
estimateProfit = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "평가손익"))
profitRate = float(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "수익률(%)"))
total_chegual_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매입금액")
total_chegual_price = int(total_chegual_price.strip())
possible_quantity = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매매가능수량")
possible_quantity = int(possible_quantity.strip())
if itemCode in self.k.acc_portfolio :
pass
else :
self.k.acc_portfolio.update({itemCode : {}})
self.k.acc_portfolio[itemCode].update({"종목명": itemName.strip()})
self.k.acc_portfolio[itemCode].update({"보유수량": amount})
self.k.acc_portfolio[itemCode].update({"매입가": buyingPrice})
self.k.acc_portfolio[itemCode].update({"수익률(%)": profitRate})
self.k.acc_portfolio[itemCode].update({"현재가": currentPrice})
self.k.acc_portfolio[itemCode].update({"매입금액": total_chegual_price})
self.k.acc_portfolio[itemCode].update({"매매가능수량": possible_quantity})
self.parent.stocklistTableWidget_2.setItem(index, 0, QTableWidgetItem(str(itemCode)))
self.parent.stocklistTableWidget_2.setItem(index, 1, QTableWidgetItem(str(itemName)))
self.parent.stocklistTableWidget_2.setItem(index, 2, QTableWidgetItem(str(format(amount, ","))))
self.parent.stocklistTableWidget_2.setItem(index, 3, QTableWidgetItem(str(format(buyingPrice, ","))))
self.parent.stocklistTableWidget_2.setItem(index, 4, QTableWidgetItem(str(format(currentPrice, ","))))
self.parent.stocklistTableWidget_2.setItem(index, 5, QTableWidgetItem(str(format(estimateProfit, ","))))
self.parent.stocklistTableWidget_2.setItem(index, 6, QTableWidgetItem(str(format(profitRate, ","))))
self.parent.stocklistTableWidget_2.item(index, 0).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 1).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 2).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 3).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 4).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 5).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.parent.stocklistTableWidget_2.item(index, 6).setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
if sPrevNext == "2":
self.detail_acount_mystock(sPrevNext="2")
else:
self.detail_account_info_event_loop.exit()
특정 요청(여기서는 계좌평가잔고내역요청)이 들어오면 메서드 내의 기능들을 수행한다.
요청하는 데이터가 계좌평가잔고내역이므로 서버에서는 요청한 계좌에 대한 정보들을 반환해준다.
“총매입금액”, “총평가금액”, “추정예탁자산”, “총평가손익금액”, “총수익률(%)” 이 5가지 값들을 반환하고 반환한 값들을
label_l6, label_l7, …, label_l10에 각각 넣어준다.
또한 계좌에 존재하는 모든 종목들을 대상으로 종목의 정보들(“종목번호”, “종목명”, “보유수량”, “매입가” 등등)을 서버에서 받아와 계좌 포트폴리오에 업데이트하고 stocklistTableWidget_2에 전시한다.
종목 코드, 종목명 리스트 저장
현재 시장에 어떤 종목들이 상장되어 있는지 종목 코드와 종목명을 모두 불러와 저장한다.
def getItemList(self) :
marketList = ["0", "10"]
for market in marketList :
codeList = self.k.kiwoom.dynamicCall("GetCodeListByMarket(QString)", market).split(";")[:-1]
for code in codeList :
name = self.k.kiwoom.dynamicCall("GetMasterCodeName(QString)", code)
self.k.All_Stock_Code.update({code : {"종목명" : name}})
marketList가 “0” 이면 코스피, “10”이면 코스닥이다. 즉, 우리나라 주식 시장에 상장된 종목들을 대상으로 저장한다.
각 시장의 모든 종목 코드와 종목이름을 All_Stock_Code라는 Dictionary에 저장한다.
조회
시장에 상장된 종목들을 모두 불러왔으므로 이제 사용자의 계좌정보와 어떤 종목들을 보유하고 있는지 조회할 수 있다.
def detail_account_mystock(self, sPrevNext=0) :
print("계좌평가잔고내역 조회")
account = self.parent.accComboBox.currentText()
self.account_num = account
print(f"선택 계좌는 {self.account_num}")
self.k.acc_number = account
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "계좌번호", account)
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "비밀번호", "")
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "조회구분", "2")
self.k.kiwoom.dynamicCall("CommRqData(String, String, int, String)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.Acc_Screen)
self.detail_account_info_event_loop.exec_()
accComboBox는 계좌번호를 선택할 수 있는 박스로 사용자가 보유한 주식 계좌들을 선택할 수 있다.
선택한 계좌번호와 비밀번호, 비밀번호입력매체구분 등 서버가 처리하기 위한 데이터들을 전송하고 처리 완료될 때 까지
self.detail_account_info_event_loop.exec_() 로 기다린다.
해당 코드의 결과는 다음과 같다.
비밀번호 입력 오류 창
지금까지 문제없이 코드를 작성했다면 계좌평가잔고내역 조회를 했을 시 다음과 같은 에러 창이 나올 것이다.
해당 에러는 계좌를 조회하기 위해서 비밀번호가 존재하는데 우리가 위에서 요청할 때 다음과 같이 빈 칸으로 요청했기에
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "비밀번호", "")
계좌 비밀번호를 입력해달라고 요청하는 것이다.
이를 해결하기 위한 방법은 다음과 같다.
먼저 KOAStudio를 실행하면 작업표시줄 우측에 API 아이콘을 우클릭하여 계좌비밀번호 저장을 누르면 다음과 같이 나온다.
해당 창에서 비밀번호를 입력하고 등록, AUTO에 체크표시 후 재접속하면 이상없이 계좌평가잔고내역이 조회된다.