40강에서 종목 선정 후 매수/매도 가격을 QtableWidget에 입력까지 완료하였습니다. 이제는 QtableWidget에 입력된 값을 불러와 실시간 자동매매를 하기 위한 기초작업을 다지겠습니다.
[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.
1. 자동매매를 위한 GUI(Push Button) 생성
아래 그림과 같이 Push Button을 생성 후 objectName은 Auto_start라고 입력하였으며, 버튼 이름은 자동매매 시작이라고 정의 하였습니다. 혹시 이름이 마음에 들지 않으시면 원하는 이름으로 교체하시면 됩니다.
2. 자동매매를 위한 스레드 스크립트 생성
오랜만에 스레드 스크립트를 생성합니다. Qthread_1에서는 계좌평가잔고내역을 가져왔으며, Qthread_2에서는 계좌관리 정보를 가져왔습니다. 이번에는 Qthread_3 스크립트를 생성하여 자동매매 시작을 위한 코드를 구성해 보겠습니다.
(1) Main Script에 추가될 내용
Main Script에서 자동매매 시작을 위한 Qthread_3을 사용할 수 있게 불러옵니다.
- form Qthread_3 import Thread3 : Qthread_3 스크립트에서 Thread3 클래스를 가져옵니다.
앞서 만든 Push Button인 Auto_start를 클릭하면 self.auto라는 함수가 실행되게 합니다.
- self.Auto_start.clicked.connect(self.suto)
auto 함수가 호출되면, Thread3 클래스가 실행되게 합니다.
- def auto(self): auto 함수 생성
- h3 = Thread3(self) : Thread3(self) 기능을 h3에 인스턴스화 합니다.
- h3.start() : h3 객체를 실행합니다.
(2)Qhtread_3 구성하기
Qhtread_3.py라는 스크립트를 만듭니다. 혹시.... 이거 못 만드시는 분은 강의를 처음부터 정주행 하기를 바랍니다.
그리고 4개의 함수를 불러옵니다. os부터 Kiwoom까지는 이전 강의에서 계속 설명 드렸던 부분이라 생략... 하려고 했으나 다시 한번 더 말씀드리겠습니다.
- improt os : 현재 디렉터리가 어디인지 알려주는 함수입니다. 텍스트로 저장한(DB) 값들을 불러올 때 특정 디렉터리에 저장되어 있는지 확인하는 과정 시 사용됩니다.
- from PyQt5.QtCore import * : 스레드 함수를 사용하기 위한 클래스를 불러옵니다. 여기 *안에 QThread함수도 담겨 있습니다.
- from kiwoom import Kiwoom : 우리가 만든 절대 언어이죠? 여기에는 키움 서버에 명령을 전송하기 위한 레지스터가 저장되어 있습니다.
- from KiwoomType import * : 이 부분은 실시간 정보를 받아오기 위해 반드시 필요한 FID 번호를 저장한 곳입니다. 이 부분은 다음 강의에서 상세히 다루도록 하겠습니다. 그만큼 중요하기 때문입니다.
다음으로 Thread3 클래스를 구성하고 다시 말씀드리지만 클래스 시작 시 대문자로 하셔야 됩니다.
- class Thread3(QThread) : Thread3라는 클래스를 만들었으며, 스레드 기능을 수행하기 위해 QThread라는 부모 클래스의 상속을 받습니다. 다시 말씀드리지만 상속은 정말 좋은 시스템입니다. 내가 아무것도 가지고 있지 않아도 상속만 받는다면 부모가 이뤄놓은 모든 것을 누릴 수 있기 때문입니다.
- def __init__(self, parent) : 자식 클래스에서 부모 클래스의 내용을 모두 사용하고 싶을 때 사용하는 문구입니다. parent는 부모의 윈도우 창을 사용하기 위한 매개변수이며, init은 클래스 생성자를 이용하여 self라는 인스턴스에 parent라는 매개변수를 넣어 사용하겠다는 의미입니다.
- supter().__init__(parent) : 부모 클래스를 초기화하며, 반드시 초기화 작업을 하셔야 부모의 기능을 사용하실 수 있습니다.
- self.k = Kiwoom() : 키움에 명령을 전송하기 위한 다양한 기능 들을 메타 클래스 기반 싱글턴이라는 마법의 언어(Kiwoom)에서 self.k로 인스턴스화 합니다. 앞으로 self.k만 치시면 Kiwoom() 클래스 기능을 모두 사용하실 수 있습니다.
여기까지 작성하시면 기본 기능은 다 되신 거고, 주문 전송 등을 하기 위해선는 현재 계좌 정보가 필요합니다. 각 Thread는 통신을 하지 못하기 때문에 Thread3에서 계좌번호를 알 수 있도록 앞서 만든 GUI에 입력된 계좌번호를 가져옵니다.
- account = self.parent.accComboBox.currentText() : 우선 Thread3에서 부모 GUI에 접속하고 싶으면 앞서 설정한 parent 객체를 사용합니다. self.parent를 입력하면 부모 GUI 사용이 가능합니다. 그리고 부모 GUI에 accComboBox에 입력된 내용을 가져오기 위해 currentText() 함수를 사용하고 최종적으로 account에 입력합니다.
- self.account_num = account : account는 self에 담겨져 있지 않기 때문에 현재 스크립트에 어디서든/누구나 계좌번호를 확인하지 못합니다. 따라서, self.account_num을 만들어 관련 내용을 넣습니다.
3. 요약 및 코드
앞선 강의와 상당히 중복되는 부분이 많지만 다시 한번 복습하는 차원에서 상세히 말씀드렸습니다. 혹시 이해가 되지 않으시면, 모든 내용이 더욱 자세히 설명되어 있는 앞선 강의를 다시 한 번 더 정주행 하시길 바랍니다. 이번 강의에서는 자동매매를 위한 GUI push button 생성 및 Thread3을 만드는 과정을 진행하였습니다. 감사합니다.
Main Script 코드
import os # 현재 디렉토리 확인 기능
import sys # system specific parameters and functions : 파이썬 스크립트 관리
from PyQt5.QtWidgets import * # GUI의 그래픽적 요소를 제어 하단의 terminal 선택, activate py37_32, pip install pyqt5, 전부다 y
from PyQt5 import uic # ui 파일을 가져오기위한 함수
from PyQt5.QtCore import * # eventloop/스레드를 사용 할 수 있는 함수 가져옴.
################# 부가 기능 수행(일꾼) #####################################
from kiwoom import Kiwoom # 키움증권 함수/공용 방 (싱글턴)
from Qthread_1 import Thread1 # 계좌평가잔고내역 가져오기
from Qthread_2 import Thread2 # 계좌 관리
from Qthread_3 import Thread3 # 자동매매 시작
#=================== 프로그램 실행 프로그램 =========================#
form_class = uic.loadUiType("ALBA.ui")[0] # 만들어 놓은 ui 불러오기
class Login_Machnine(QMainWindow, QWidget, form_class): # QMainWindow : PyQt5에서 윈도우 생성시 필요한 함수
def __init__(self, *args, **kwargs): # Main class의 self를 초기화 한다.
print("Login Machine 실행합니다.")
super(Login_Machnine, self).__init__(*args, **kwargs)
form_class.__init__(self) # 상속 받은 from_class를 실행하기 위한 초기값(초기화)
self.setUI() # UI 초기값 셋업 반드시 필요
### 초기 셋팅 : 계좌평가잔고내역
self.label_11.setText(str("총매입금액"))
self.label_12.setText(str("총평가금액"))
self.label_13.setText(str("추정예탁자산"))
self.label_14.setText(str("총평가손익금액"))
self.label_15.setText(str("총수익률(%)"))
########## 종목 추가 하기 우측 정렬
self.searchItemTextEdit2.setAlignment(Qt.AlignRight)
########## 더블 스핀 박스 우측정렬 및 소수점 삭제
self.buy_price.setAlignment(Qt.AlignRight)
self.buy_price.setDecimals(0)
self.n_o_stock.setAlignment(Qt.AlignRight)
self.n_o_stock.setDecimals(0)
self.profit_price.setAlignment(Qt.AlignRight)
self.profit_price.setDecimals(0)
self.loss_price.setAlignment(Qt.AlignRight)
self.loss_price.setDecimals(0)
#### 기타 함수
self.login_event_loop = QEventLoop() # 이때 QEventLoop()는 block 기능을 가지고 있다.
####키움증권 로그인 하기
self.k = Kiwoom() # Kiwoom()을 실행하며 상속 받는다. Kiwoom()은 전지적인 아이다.
self.set_signal_slot() # 키움로그인을 위한 명령어 전송 시 받는 공간을 미리 생성한다.
self.signal_login_commConnect()
#####이벤트 생성 및 진행
self.call_account.clicked.connect(self.c_acc) # 계좌정보가져오기
self.acc_manage.clicked.connect(self.a_manage) # 계좌관리하기
self.Auto_start.clicked.connect(self.auto) # 자동매매 시작
################# 부가기능 1 : 종목선택하기 새로운 종목 추가 및 삭제
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot) # 키움서버 데이터 받는 곳
self.additmelast.clicked.connect(self.searchItem2) # 종목 추가
self.Deletcode.clicked.connect(self.deltecode) # 종목 삭제
################# 부가기능 2 : 데이터베이스화 하기, 저장, 삭제, 불러오기
self.Getanal_code = [] # 불러온 파일 저장
self.Save_Stock.clicked.connect(self.Save_selected_code) # 종목 저장
self.Del_Stock.clicked.connect(self.delet_code) # 종목 삭제
self.Load_Stock.clicked.connect(self.Load_code) # 종목 불러오기
####################
def Load_code(self):
if os.path.exists("dist/Selected_code.txt"):
f = open("dist/Selected_code.txt", "r", encoding="utf8")
lines = f.readlines() # 여러 종목이 저장되어 있다면 모든 항목을 가져온다.
for line in lines:
if line != "": # 만약에 line이 비어 있지 않다면
ls = line.split("\t") # \t(tap)로 구분을 지어 놓는다.
t_code = ls[0]
t_name = ls[1]
curren_price = ls[2]
dept = ls[3]
mesu = ls[4]
n_o_stock = ls[5]
profit = ls[6]
loss = ls[7].split("\n")[0]
self.Getanal_code.append([t_code, t_name, curren_price, dept, mesu, n_o_stock, profit, loss])
f.close()
column_head = ["종목코드", "종목명", "현재가", "신용비율", "매수가", "매수수량", "익절가", "손절가"]
colCount = len(column_head)
rowCount = len(self.Getanal_code)
self.buylast.setColumnCount(colCount) # 행 갯수
self.buylast.setRowCount(rowCount) # 열 갯수 (종목 수)
self.buylast.setHorizontalHeaderLabels(column_head) # 행의 이름 삽입
self.buylast.setSelectionMode(QAbstractItemView.SingleSelection)
for index in range(rowCount):
self.buylast.setItem(index, 0, QTableWidgetItem(str(self.Getanal_code[index][0])))
self.buylast.setItem(index, 1, QTableWidgetItem(str(self.Getanal_code[index][1])))
self.buylast.setItem(index, 2, QTableWidgetItem(str(self.Getanal_code[index][2])))
self.buylast.setItem(index, 3, QTableWidgetItem(str(self.Getanal_code[index][3])))
self.buylast.setItem(index, 4, QTableWidgetItem(str(self.Getanal_code[index][4])))
self.buylast.setItem(index, 5, QTableWidgetItem(str(self.Getanal_code[index][5])))
self.buylast.setItem(index, 6, QTableWidgetItem(str(self.Getanal_code[index][6])))
self.buylast.setItem(index, 7, QTableWidgetItem(str(self.Getanal_code[index][7])))
def Save_selected_code(self):
for row in range(self.buylast.rowCount()):
code_n = self.buylast.item(row, 0).text()
name = self.buylast.item(row, 1).text().strip()
price = self.buylast.item(row, 2).text()
dept = self.buylast.item(row, 3).text()
mesu = self.buylast.item(row, 4).text()
n_o_stock = self.buylast.item(row, 5).text()
profit = self.buylast.item(row, 6).text()
loss = self.buylast.item(row, 7).text()
f = open("dist/Selected_code.txt", "a",encoding="utf8") # "a" 달아 쓴다. "w" 덮어 쓴다. files라느 파이썬 페키지 볼더를 만든다.
f.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (code_n, name, price, dept, mesu, n_o_stock, profit, loss)) # t는 tap을 의미한다.
f.close()
def delet_code(self):
if os.path.exists("dist/Selected_code.txt"):
os.remove("dist/Selected_code.txt")
def deltecode(self):
x = self.buylast.selectedIndexes() # 리스트로 선택된 행번호와 열번호가 x에 입력된다.
self.buylast.removeRow(x[0].row())
def searchItem2(self): # 종목추가시 사용됨.
itemName = self.searchItemTextEdit2.toPlainText()
if itemName != "":
for code in self.k.All_Stock_Code.keys(): # 포트폴리오에 저장된 코드들을 실시간 등록
# 주식체결 정보 가져오기(틱 데이터) : 현재가, 전일대비, 등락률, 매도호가, 매수호가, 거래량, 누적거래량, 고가, 시가, 저가
if itemName == self.k.All_Stock_Code[code]['종목명']:
self.new_code = code
column_head = ["종목코드", "종목명", "현재가", "신용비율", "매수 가격", "매수 수량", "익절 가격", "손절 가격"]
colCount = len(column_head)
row_count = self.buylast.rowCount()
self.buylast.setColumnCount(colCount) # 행 갯수
self.buylast.setRowCount(row_count+1) # colum_haed가 한 행을 잡아 먹는다. 실제 입력 되는 값은 1행 부터이다.
self.buylast.setHorizontalHeaderLabels(column_head) # 행의 이름 삽입
self.buylast.setItem(row_count, 0, QTableWidgetItem(str(self.new_code))) # 실제 입력값은 1행부터이나 0부터 들어가야 된다.
self.buylast.setItem(row_count, 1, QTableWidgetItem(str(itemName)))
################## 더블 스핀 박스 내용 읽기
self.buylast.setItem(row_count, 4, QTableWidgetItem(str(int(self.buy_price.value()))))
self.buylast.setItem(row_count, 5, QTableWidgetItem(str(int(self.n_o_stock.value()))))
self.buylast.setItem(row_count, 6, QTableWidgetItem(str(int(self.profit_price.value()))))
self.buylast.setItem(row_count, 7, QTableWidgetItem(str(int(self.loss_price.value()))))
self.getItemInfo(self.new_code)
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")
def setUI(self):
self.setupUi(self) # UI 초기값 셋업
def set_signal_slot(self):
self.k.kiwoom.OnEventConnect.connect(self.login_slot) # 내가 알고 있는 login_slot에다가 특정 값을 던져 준다.
def signal_login_commConnect(self):
self.k.kiwoom.dynamicCall("CommConnect()") # 네트워크적 서버 응용프로그램에 데이터를 전송할 수 있게 만든 함수
self.login_event_loop.exec_() # 로그인이 완료될 때까지 계속 반복됨. 꺼지지 않음.
def login_slot(self, errCode):
if errCode == 0:
print("로그인 성공")
self.statusbar.showMessage("로그인 성공")
self.get_account_info() # 로그인시 계좌정보 가져오기
elif errCode == 100:
print("사용자 정보교환 실패")
elif errCode == 101:
print("서버접속 실패")
elif errCode == 102:
print("버전처리 실패")
self.login_event_loop.exit() # 로그인이 완료되면 로그인 창을 닫는다.
def get_account_info(self):
account_list = self.k.kiwoom.dynamicCall("GetLoginInfo(String)", "ACCNO")
for n in account_list.split(';'):
self.accComboBox.addItem(n)
def c_acc(self):
print("선택 계좌 정보 가져오기")
##### 1번 일꾼 실행
h1 = Thread1(self)
h1.start()
def a_manage(self):
print("계좌 관리")
h2 = Thread2(self)
h2.start()
def auto(self):
print("자동매매 시작")
h3 = Thread3(self)
h3.start()
def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
if sTrCode == "opt10001":
if sRQName == "주식기본정보요청":
currentPrice = abs(int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "현재가")))
D_R = (self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "신용비율")).strip()
row_count = self.buylast.rowCount()
self.buylast.setItem(row_count - 1, 2, QTableWidgetItem(str(currentPrice)))
self.buylast.setItem(row_count - 1, 3, QTableWidgetItem(str(D_R)))
if __name__=='__main__': # import된 것들을 실행시키지 않고 __main__에서 실행하는 것만 실행 시킨다.
# 즉 import된 다른 함수의 코드를 이 화면에서 실행시키지 않겠다는 의미이다.
app = QApplication(sys.argv) # PyQt5로 실행할 파일명을 자동으로 설정, PyQt5에서 자동으로 프로그램 실행
CH = Login_Machnine() # Main 클래스 myApp으로 인스턴스화
CH.show() # myApp에 있는 ui를 실행한다.
app.exec_() # 이벤트 루프
QThread_3.py 스크립트
import os # 현재 디렉토리 확인 기능
from PyQt5.QtCore import * # 쓰레드 함수를 불러온다.
from kiwoom import Kiwoom # 로그인을 위한 클래스
from kiwoomType import *
class Thread3(QThread):
def __init__(self, parent): # 부모의 윈도우 창을 가져올 수 있다.
super().__init__(parent) # 부모의 윈도우 창을 초기화 한다.
self.parent = parent # 부모의 윈도우를 사용하기 위한 조건
################## 키움서버 함수를 사용하기 위해서 kiwoom의 능력을 상속 받는다.
self.k = Kiwoom()
##################
################## 사용되는 변수
account = self.parent.accComboBox.currentText() # 콤보박스 안에서 가져오는 부분
self.account_num = account
# 계좌번호 가져오는 부분은 Qthread_3 분리 시 로그인 후 계좌번호를 가져오는 함수로 교체된다. Lecture_0529.py
'주식 자동매매 강의 > 기초반(모든 코딩의 뿌리)' 카테고리의 다른 글
주식자동매매 43강. 실시간 자동매매 구현(5), DB에 저장된 종목 가져오기 (12) | 2022.06.09 |
---|---|
주식자동매매 42강. 실시간 자동매매 구현(4), KOA Studio 실시간 데이터 가져오기 개념 설명 (2) | 2022.06.09 |
[특강] 주식자동매매 프로그램 GUI 우측 정렬, 천(1000) 단위 구분 콤마(comma) 설정하기 (10) | 2022.06.05 |
주식자동매매 40강. 실시간 자동매매 구현(2), 매수/매도 관련 내용 QTable Widget에 입력하기 (8) | 2022.06.02 |
주식자동매매 39강. 실시간 자동매매 구현(1), KOA Studio등 실시간 매매 개념 잡기,GUI 구성하기 (5) | 2022.06.01 |