[키움 자동매매 프로그램] - 분할 자동매매 페이지(Division_Stock.py) 구현
다음으로 구현할 부분은 분할 자동매매이다.
메인 윈도우의 빨간 동그라미 부분에 해당한다.
먼저 분할 매수매도의 윈도우의 UI와 코드를 연결하고 거래를 제외한 나머지 기능들을 구현하기 위한 Division_Stock.py를 생성한다.
Division_Stock.py 구현
먼저 UI를 다시 살펴보면

거래할 종목명을 메인 윈도우에서 지정한 다음 해당 페이지에서 입력 후 종목 추가(또는 종목코드 추가), 감시가격 설정, DB에 저장, 자동매매 시작 및 종료 기능을 구현해야한다.
Division_Stock - init() 생성
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtCore import *
from kiwoom import Kiwoom
from Qthread_6 import Thread6 # 분할 자동 매매
form_thirdwindow = uic.loadUiType("pytrader3.ui")[0]
class Thirdwindow(QMainWindow, QWidget, form_thirdwindow) :
def __init__(self) :
super(Thirdwindow, self).__init__()
self.initUi()
self.show()
self.doubleSpinBox_1.setValue(0)
self.doubleSpinBox_2.setValue(0)
self.doubleSpinBox_3.setValue(0)
self.doubleSpinBox_4.setValue(0)
self.doubleSpinBox_5.setValue(0)
self.doubleSpinBox_6.setValue(0)
self.doubleSpinBox_7.setValue(0)
self.doubleSpinBox_8.setValue(0)
self.doubleSpinBox_9.setValue(0)
self.doubleSpinBox_10.setValue(0)
self.doubleSpinBox_11.setValue(0)
self.doubleSpinBox_12.setValue(0)
self.doubleSpinBox_13.setValue(0)
self.doubleSpinBox_14.setValue(0)
self.doubleSpinBox_15.setValue(0)
self.doubleSpinBox_16.setValue(0)
self.doubleSpinBox_1.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_1.setDecimals(0)
self.doubleSpinBox_2.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_2.setDecimals(0)
self.doubleSpinBox_3.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_3.setDecimals(0)
self.doubleSpinBox_4.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_4.setDecimals(0)
self.doubleSpinBox_5.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_5.setDecimals(0)
self.doubleSpinBox_6.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_6.setDecimals(0)
self.doubleSpinBox_7.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_7.setDecimals(0)
self.doubleSpinBox_8.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_8.setDecimals(0)
self.doubleSpinBox_9.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_9.setDecimals(0)
self.doubleSpinBox_10.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_10.setDecimals(0)
self.doubleSpinBox_11.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_11.setDecimals(0)
self.doubleSpinBox_12.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_12.setDecimals(0)
self.doubleSpinBox_13.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_13.setDecimals(0)
self.doubleSpinBox_14.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_14.setDecimals(0)
self.doubleSpinBox_15.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_15.setDecimals(0)
self.doubleSpinBox_16.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.doubleSpinBox_16.setDecimals(0)
self.k = Kiwoom()
self.detail_account_info_event_loop = QEventLoop()
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot)
self.additemlast.clicked.connect(self.searchItem2)
self.Deletecode.clicked.connect(self.deletecode)
self.Getanal_code = []
self.Load_Stock.clicked.connect(self.Load_code)
self.Save_Stock.clicked.connect(self.Save_selected_code)
self.Del_Stock.clicked.connect(self.delete_code)
self.Start_Auto.clicked.connect(self.start_real_auto)
self.Stop_Everyting.clicked.connect(self.stop_auto)
def initUi(self) :
self.setupUi(self)
UI를 불러오고 doubleSpinBox는 매수/매도 가격 및 수량을 설정할 수 있는 박스이다.
Divison_Stock - 서버 데이터 요청 및 수신
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot)
self.additemlast.clicked.connect(self.searchItem2)
메인 윈도우에 자동매매를 구현했던 것과 마찬가지로 먼저 거래할 종목에 대한 데이터를 받아오고 추가해야한다.
def trdata_slot(self, sCrNo, sRQName, sTrCode, sRecordName, sPrevNext) :
if sTrCode == "opt10001" :
if sRQName == "주식기본정보요청" :
currentPrice = abs(int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "현재가")))
row_count = self.buylast.rowCount()
self.buylast.setItem(row_count - 1, 2, QTableWidgetItem(str(format(int(currentPrice), ","))))
self.detail_account_info_event_loop.exit()
이전의 ppytrader.py에 구현했던 것과 유사하게 구현하되, 매수 가격, 매수 수량 등의 파라미터들은 감시박스에서 구현하므로
거래 종목의 현재가만 buylast에 기입한다.
def searchItem2(self) :
self.itemName = self.searchItemTextEdit2.toPlainText()
self.new_code = None
if self.itemName != "" :
for code in self.k.All_Stock_Code.keys() :
if self.itemName == self.k.All_Stock_Code[code]['종목명'] :
self.new_code = code
if self.new_code != "" and self.itemName == "" :
self.new_code = self.searchItemTextEdit3.toPlainText().strip()
for code in self.k.All_Stock_Code.keys() :
if self.new_code == code :
self.itemName = self.k.All_Stock_Code[code]['종목명']
column_head = ["종목코드", "종목명", "현재가", "매수가격_1", "매수수량_1", "매수가격_2", "매수수량_2", "매수가격_3", "매수수량_3", "매수가격_4", "매수수량_4",
"매도가격_1", "매도수량_1", "매도가격_2", "매도수량_2", "매도가격_3", "매도수량_3", "매도가격_4", "매도수량_4"]
colCount = len(column_head)
row_count = self.buylast.rowCount()
self.buylast.setColumnCount(colCount)
self.buylast.setRowCount(row_count+1)
self.buylast.setHorizontalHeaderLabels(column_head)
self.buylast.setItem(row_count, 0, QTableWidgetItem(str(self.new_code)))
self.buylast.setItem(row_count, 1, QTableWidgetItem(str(self.itemName)))
self.getItemInfo(self.new_code)
self.buylast.setItem(row_count, 3, QTableWidgetItem(str(format(int(self.doubleSpinBox_1.value()), ","))))
self.buylast.setItem(row_count, 4, QTableWidgetItem(str(format(int(self.doubleSpinBox_2.value()), ","))))
self.buylast.setItem(row_count, 5, QTableWidgetItem(str(format(int(self.doubleSpinBox_3.value()), ","))))
self.buylast.setItem(row_count, 6, QTableWidgetItem(str(format(int(self.doubleSpinBox_4.value()), ","))))
self.buylast.setItem(row_count, 7, QTableWidgetItem(str(format(int(self.doubleSpinBox_5.value()), ","))))
self.buylast.setItem(row_count, 8, QTableWidgetItem(str(format(int(self.doubleSpinBox_6.value()), ","))))
self.buylast.setItem(row_count, 9, QTableWidgetItem(str(format(int(self.doubleSpinBox_7.value()), ","))))
self.buylast.setItem(row_count, 10, QTableWidgetItem(str(format(int(self.doubleSpinBox_8.value()), ","))))
self.buylast.setItem(row_count, 11, QTableWidgetItem(str(format(int(self.doubleSpinBox_9.value()), ","))))
self.buylast.setItem(row_count, 12, QTableWidgetItem(str(format(int(self.doubleSpinBox_10.value()), ","))))
self.buylast.setItem(row_count, 13, QTableWidgetItem(str(format(int(self.doubleSpinBox_11.value()), ","))))
self.buylast.setItem(row_count, 14, QTableWidgetItem(str(format(int(self.doubleSpinBox_12.value()), ","))))
self.buylast.setItem(row_count, 15, QTableWidgetItem(str(format(int(self.doubleSpinBox_13.value()), ","))))
self.buylast.setItem(row_count, 16, QTableWidgetItem(str(format(int(self.doubleSpinBox_14.value()), ","))))
self.buylast.setItem(row_count, 17, QTableWidgetItem(str(format(int(self.doubleSpinBox_15.value()), ","))))
self.buylast.setItem(row_count, 18, QTableWidgetItem(str(format(int(self.doubleSpinBox_16.value()), ","))))
self.doubleSpinBox_1.setValue(0)
self.doubleSpinBox_2.setValue(0)
self.doubleSpinBox_3.setValue(0)
self.doubleSpinBox_4.setValue(0)
self.doubleSpinBox_5.setValue(0)
self.doubleSpinBox_6.setValue(0)
self.doubleSpinBox_7.setValue(0)
self.doubleSpinBox_8.setValue(0)
self.doubleSpinBox_9.setValue(0)
self.doubleSpinBox_10.setValue(0)
self.doubleSpinBox_11.setValue(0)
self.doubleSpinBox_12.setValue(0)
self.doubleSpinBox_13.setValue(0)
self.doubleSpinBox_14.setValue(0)
self.doubleSpinBox_15.setValue(0)
self.doubleSpinBox_16.setValue(0)
종목명 or 종목코드를 입력 후 종목 추가를 누르면 해당 종목의 데이터들을 buylast에 추가한다.
종목코드, 종목명, 현재가, 매수가격_1, 매수가격_2, …, 매도수량_4 까지의 파라미터들을 설정하고 마찬가지로 buylast에 추가한다.
searchItem2() - getItemInfo()
거래할 종목을 추가할 때, 서버에 데이터를 전송하는 부분의 메서드이다.
def getItemInfo(self, new_code) :
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", new_code)
self.k.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "주식기본정보요청", "opt10001", 0, "100")
종목코드를 설정하고 주식기본정보요청으로 서버에 데이터를 요청한다.
선정종목 삭제
buylast에 추가한 종목 중 거래를 원하지 않는 종목이 존재할 시 해당 종목을 삭제하는 코드이다.
코드상으로는 다음에 해당한다.
self.Deletecode.clicked.connect(self.deletecode)
def deletecode(self) :
x = self.buylast.selectedIndexes()
self.buylast.removeRow(x[0].row())
간단하게 다음과 같이 구현할 수 있다.
UI에 전시만 된 값들이므로 UI에서 제거해주면 된다.
데이터베이스 조작
선정한 종목들의 데이터를 데이터베이스에 저장하고, 불러오기 및 삭제를 할 수 있는 코드이다.
미리 저장해놨던 종목들의 데이트를 불러와서 바로 자동매매 가능하도록 즉, 이용에 용이함을 목적으로 구현하였다.
코드상으로는 다음에 해당한다.
self.Getanal_code = []
self.Load_Stock.clicked.connect(self.Load_code)
self.Save_Stock.clicked.connect(self.Save_selected_code)
self.Del_Stock.clicked.connect(self.delete_code)
Getanal_code 데이터베이스에 저장할 종목들을 임시로 저장하는 리스트이다.
데이터베이스 - Load_code()
def Load_code(self) :
self.Load_Stock.setText("클릭")
self.Load_Stock.setStyleSheet("Color : red")
if os.path.exists("dist/Selected_code.txt") :
f = open("dist/Selected_code.txt", "r", encoding="utf8")
lines = f.readlines()
if not lines :
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("파일이 비어있습니다.")
msg.setWindowTitle("경고")
msg.exec_()
for line in lines :
if line != "" :
ls = line.split("\t")
code_n = ls[0]
name = ls[1]
price = ls[2]
a1 = ls[3]
a2 = ls[4]
a3 = ls[5]
a4 = ls[6]
a5 = ls[7]
a6 = ls[8]
a7 = ls[9]
a8 = ls[10]
a9 = ls[11]
a10 = ls[12]
a11 = ls[13]
a12 = ls[14]
a13 = ls[15]
a14 = ls[16]
a15 = ls[17]
a16 = ls[18].split("\n")[0]
self.Getanal_code.append([code_n, name, price, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16])
f.close()
이전에 데이터베이스를 구현했던 것과 동일한 방식으로 구현한다.
추가되는 항목으로는 감시박스에 추가된 데이터들로 매수가격_1, 매수수량_1, … 등에 해당한다.
데이터베이스 - Save_selected_code()
def Save_selected_code(self) :
self.Save_Stock.setText("클릭")
self.Save_Stock.setStyleSheet("Color : red")
for row in range(self.buylast.rowCount()) :
code_n = self.buylast.item(row, 0).text()
name = self.buylast.item(row, 1).text()
price = self.buylast.item(row, 2).text()
a1 = self.buylast.item(row, 3).text()
a2 = self.buylast.item(row, 4).text()
a3 = self.buylast.item(row, 5).text()
a4 = self.buylast.item(row, 6).text()
a5 = self.buylast.item(row, 7).text()
a6 = self.buylast.item(row, 8).text()
a7 = self.buylast.item(row, 9).text()
a8 = self.buylast.item(row, 10).text()
a9 = self.buylast.item(row, 11).text()
a10 = self.buylast.item(row, 12).text()
a11 = self.buylast.item(row, 13).text()
a12 = self.buylast.item(row, 14).text()
a13 = self.buylast.item(row, 15).text()
a14 = self.buylast.item(row, 16).text()
a15 = self.buylast.item(row, 17).text()
a16 = self.buylast.item(row, 18).text()
f = open("dist/Selected_code.txt", "a", encoding="utf8")
f.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (code_n, name, price, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16))
f.close()
데이터베이스에 선정한 종목들을 추가하고 저장하는 코드이다.
해당 버튼을 클릭하면 “클릭” 으로 바뀌며 데이터베이스에 저장된다.
데이터베이스 - delete_code()
def delete_code(self) :
self.Del_Stock.setText("클릭")
self.Del_Stock.setStyleSheet("Color : red")
if os.path.exists("dist/Selected_code.txt") :
os.remove("dist/Selected_code.txt")
데이터베이스에 저장되어 있는 종목들을 삭제한다.
해당 버튼을 클릭하면 “클릭” 으로 바뀌며 데이터베이스의 모든 종목을에 대한 정보들이 삭제된다.
자동매매 시작
def start_real_auto(self) :
print("분할 자동 매매 시작하기")
self.Start_Auto.setText("자동매매 중")
self.Start_Auto.setStyleSheet("Color : red")
h6 = Thread6(self)
h6.start()
해당 메서드를 실행함으로 선정한 종목들을 대상으로 자동매매가 시작된다.
자동매매 알고리즘 구현은 Thread6(Qthread_6.py)에 구현한다.
자동매매 종료
def stop_auto(self) :
print("자동 매매 종료하기")
self.Stop_Everything.setText("자동매매 종료")
self.Stop_Everything.setStyleSheet("Color : red")
if hasattr(self, 'h6'):
self.h6.quit()
self.h6.wait(5000)
스레드를 quit() 메서드로 중단시키면 된다.
결과 출력
간단하게 현재가를 기준으로 손절 및 익절 가격을 설정하고 자동매매를 다음처럼 할 수 있다.