29강에서 opt10045 : 기관/외국인매매동향 정보를 받아왔습니다. 그리고 관련 자료를 분석하여 계좌 잔고의 위험도를 판단하였죠. 그 결과를 저희가 만든 GUI에 입력해 보겠습니다.
[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.
1. GUI에 Push 버튼 및 테이블위젯 생성
Push 버튼을 누르면 저희가 만든 QThread_2.py를 실행시켜야 하며, QThread_2.py에서 분석된 결과를 테이블 위젯에 입력시키겠습니다.
- Qt Dsigner에 Push 버튼 생성
Push Button 이름은 계좌관리이며, objectName은 acc_manage 입니다.
- Qt Dsigner에 Table Widget 표 생성
Table Widget의 objectName은 Danger_wd 입니다.
2. Qthread_2.py 실행 코드 코딩
- 메인 스크립트의 부가 기능 수행 부분에서 from Qthread_2 import Thread2를 적어 줍니다. Qthread_2.py의 Thread2 클래스 기능을 할당 받겠다는 뜻입니다.
- 앞서 정의한 push 버튼이 클릭되면 우리가 만들 함수가 실행되게 정의합니다. 코드를 보시면 self.acc_manage를 clicked 했을경우 self.a_manage 함수가 실행되는 것입니다.
- a_manage 함수를 정의 합니다. def를 사용하여 함수의 시작을 알리고 a_manage(self): 함수를 작성합니다. 내용은 Thread2(self)를 h2에 인스턴스화 하고 h2를 시작하게 했습니다. 이러한 일련의 과정을 거치면 Qthread_2.py 의 Thread2 클래스가 실행됩니다.
3. 계좌관리정보 Table Widget에 입력하기
29강에서 설명드린 아래 위험도 판단 함수 결과를 Table Widget에 입력해 보겠습니다.
- 기관외국인 평균가를 가져오는 self.C_K_F_class() 함수아래 결과를 GUI 입력하는 함수를 작성합니다. 이 함수 작성 방법은 19강에서도 아주 자세히 설명되어 있습니다.
- 위 코드는 테이블의 행과 열을 정의하며, 열의 개수는 colum_head 즉 열의 이름 개수와 동일합니다. 이름은 종목코드, 종목명, 위험도로 분류하겠습니다.
- 행의 개수는 계좌에 들어있는 종목 개수와 동일합니다. 계좌에 10개 종목이 있다면 행도 10개가 되어야 겠죠. 이때, 개수는 파이썬 내장함수 len()을 사용하여 rowCount = len(self.k.acc_portfolio)를 합니다. 여기서 self.k를 사용하여 메타클래스 영역에 접속 후 acc_portfolio를 가져오는 것입니다.
- 다음으로 self.parent를 사용하여 Qthread에서 GUI에 접속합니다. parent는 부모의 윈도우창을 가져오는 기능입니다. 그리고 Table Widget인 Danger_wd에 행의 개수, 열의 개수, 열의 이름을 입력합니다. 관련 함수는 sefColumnCount()/setRowCount()/setHorizontalHeaderLabels() 입니다.
- 다음으로 acc_portfolio에 있는 종목 번호를 하나씩 for k in self.k.acc_portfolio.key()를 사용하여 가져옵니다. 그리고 종목이 하나씩 늘어날 때마다 행도 늘려야 하기 때문에 index += 1 씩 증가시킵니다.
- 그리고 각 행과 열에 setItem을 사용하여 특정 값을 입력할 준비를 하고, QTableWidgetItem을 사용하여 종목 코드/ 종목명/위험도를 그대로 입력합니다.
- index2는 행을, 0,1,2는 열을 뜻합합니다.
4. 요약 및 코드 공개
대망의 계좌관리 강의 편이 마무리 되었습니다. 솔직히 여기서 끝이 아니죠? 물론 기관매매동향 정보만 보더라도 나의 종목이 위험한지 아닌지를 판단할 수 있습니다. 여기서 더해 차트의 전고점과 전저점 구간 그리고 20일선 60일선 120일선 지지등의 값들이 동시에 고려되면 당연히 좋겠죠? 이 부분은 여러분들이 스스로 고민하셔서 자신만의 필요한 정보들을 가져오시기 바랍니다. 그래도 차트 그리는 법은 알려드려야 스스로 하시기 편하시겠죠. 이번 강의가 완전히 마무리 된 후 차트를 그리는 법을 알려 드리겠습니다. 감사합니다.
- 메인 스크립트
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 # 계좌 관리
#=================== 프로그램 실행 프로그램 =========================#
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.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) # 계좌정보가져오기
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()
if __name__=='__main__': # import된 것들을 실행시키지 않고 __main__에서 실행하는 것만 실행 시킨다.
# 즉 import된 다른 함수의 코드를 이 화면에서 실행시키지 않겠다는 의미이다.
app = QApplication(sys.argv) # PyQt5로 실행할 파일명을 자동으로 설정, PyQt5에서 자동으로 프로그램 실행
CH = Login_Machnine() # Main 클래스 myApp으로 인스턴스화
CH.show() # myApp에 있는 ui를 실행한다.
app.exec_() # 이벤트 루프
- Qthread_2.py
from PyQt5.QtCore import * # eventloop/스레드를 사용 할 수 있는 함수 가져옴.
from kiwoom import Kiwoom # 로그인을 위한 클래스
from PyQt5.QtWidgets import * # PyQt import
from PyQt5.QtTest import * # 시간관련 함수
from datetime import datetime, timedelta # 특정 일자를 조회
class Thread2(QThread):
def __init__(self, parent): # 부모의 윈도우 창을 가져올 수 있다.
super().__init__(parent) # 부모의 윈도우 창을 초기화 한다.
self.parent = parent # 부모의 윈도우를 사용하기 위한 조건
################## 키움서버 함수를 사용하기 위해서 kiwoom의 능력을 상속 받는다.
self.k = Kiwoom()
################## 사용되는 변수
self.Find_down_Screen = "1200" # 계좌평가잔고내역을 받기위한 스크린
self.code_in_all = None # 1600개 코드 중 1개 코드, 쌓이지 않고 계속 갱신
###### 슬롯
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot) # 내가 알고 있는 Tr 슬롯에다 특정 값을 던져 준다.
###### EventLoop
self.detail_account_info_event_loop = QEventLoop() # 계좌 이벤트루프
###### 기관외국인 평균가 가져오기
self.C_K_F_class()
###### 결과 붙이기(gui)
column_head = ["종목코드", "종목명", "위험도"]
colCount = len(column_head)
rowCount = len(self.k.acc_portfolio)
self.parent.Danger_wd.setColumnCount(colCount) # 행 갯수
self.parent.Danger_wd.setRowCount(rowCount) # 열 갯수 (종목 수)
self.parent.Danger_wd.setHorizontalHeaderLabels(column_head) # 행의 이름 삽입
index2 = 0
for k in self.k.acc_portfolio.keys():
self.parent.Danger_wd.setItem(index2, 0, QTableWidgetItem(str(k)))
self.parent.Danger_wd.setItem(index2, 1, QTableWidgetItem(self.k.acc_portfolio[k]["종목명"]))
self.parent.Danger_wd.setItem(index2, 2, QTableWidgetItem(self.k.acc_portfolio[k]["위험도"]))
index2 += 1
def C_K_F_class(self):
code_list = []
for code in self.k.acc_portfolio.keys():
code_list.append(code)
print("계좌 종목 개수 %s" % (code_list))
#self.parent.progressBar5.setMaximum(len(code_list) - 1) /차 후 설명 드리겠습니다.
for idx, code in enumerate(code_list):
#self.parent.progressBar5.setValue(idx) / 차 후 설명드리겠습니다.
QTest.qWait(1000)
self.k.kiwoom.dynamicCall("DisconnectRealData(QString)", self.Find_down_Screen) # 해당 스크린을 끊고 다시 시작
self.code_in_all = code # 종목코드 선언 (중간에 코드 정보 받아오기 위해서)
print("%s / %s : 종목 검사 중 코드이름 : %s." % (idx + 1, len(code_list), self.code_in_all))
date_today = datetime.today().strftime("%Y%m%d")
date_prev = datetime.today() - timedelta(10) # 넉넉히 10일전의 데이터를 받아온다. 또는 20일이상 데이터도 필요
date_prev = date_prev.strftime("%Y%m%d")
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "시작일자", date_prev)
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종료일자", date_today)
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "기관추정단가구분", "1")
self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "외인추정단가구분", "1")
self.k.kiwoom.dynamicCall("CommRqData(String, String, int, String)", "종목별기관매매추이요청2", "opt10045", "0", self.Find_down_Screen)
self.detail_account_info_event_loop.exec_()
def kigwan_meme_dong2(self, a, c): # a. 기관일별순매수량, b. 종가/기관/외국인 평균가, c. 외국인일별순매수량, d. 등락률
a = a[0:4]
c = c[0:4]
print(a)
# a = sum(a, [])
# c = sum(c, [])
if a[0] < 0 and a[1] < 0 and a[2] < 0 and a[3] < 0 and c[0] < 0 and c[1] < 0 and c[2] < 0 and c[3] < 0:
self.k.acc_portfolio[self.code_in_all].update({"위험도": "손절"})
elif a[0] < 0 and a[1] < 0 and a[2] < 0 and c[0] < 0 and c[1] < 0 and c[2] < 0:
self.k.acc_portfolio[self.code_in_all].update({"위험도": "주의"})
elif a[0] < 0 and a[1] < 0 and c[0] < 0 and c[1] < 0:
self.k.acc_portfolio[self.code_in_all].update({"위험도": "관심"})
else:
self.k.acc_portfolio[self.code_in_all].update({"위험도": "낮음"})
def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
if sRQName == "종목별기관매매추이요청2":
cnt2 = self.k.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName) # 10일치 이상을 하려면 이부분에 10일치 이상데이터 필요
self.calcul2_data = []
self.calcul2_data2 = []
self.calcul2_data3 = []
self.calcul2_data4 = []
for i in range(cnt2): #
Kigwan_meme = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, i, "기관일별순매매수량"))
Kigwan_meme_ave = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "기관추정평균가"))
Forgin_meme = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, i, "외인일별순매매수량"))
Forgin_meme_ave = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "외인추정평균가"))
percentage = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, i, "등락율"))
Jongga = (self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, i, "종가"))
self.calcul2_data.append(int(Kigwan_meme.strip()))
self.calcul2_data2.append(abs(int(Jongga.strip())))
self.calcul2_data2.append(abs(int(Kigwan_meme_ave.strip())))
self.calcul2_data2.append(abs(int(Forgin_meme_ave.strip())))
self.calcul2_data3.append(int(Forgin_meme.strip()))
self.calcul2_data4.append(float(percentage.strip()))
# 여기까지 code의 기관일별순매수량, 외국인일별순매수량, 기관/외국인 평균가, 등락률 정보가 나온다.
# self.kigwan_meme_dong2(self.calcul2_data, self.calcul2_data2[0:3], self.calcul2_data3, self.calcul2_data4)
self.kigwan_meme_dong2(self.calcul2_data, self.calcul2_data3)
self.detail_account_info_event_loop.exit()
'주식 자동매매 강의 > 기초반(모든 코딩의 뿌리)' 카테고리의 다른 글
[특강 1] 역배열 차트 주식 탐지 알고리즘(1), 개념 설명, 알면 피할 수 있다 (2) | 2022.05.20 |
---|---|
주식자동매매 31강. 계좌관리하기 (8), opt10045 최종 요약 및 코드 공개 (7) | 2022.05.19 |
주식자동매매 29강. 계좌관리하기 (6), opt10045 결과를 이용한 계좌 위험도 판단하기 (1) | 2022.05.18 |
주식자동매매 28강. 계좌관리하기 (5), opt10045 전송 주문 결과 받아오기 (8) | 2022.05.17 |
주식자동매매 27강. 계좌관리하기 (4), opt10045 주문 전송하기 (4) | 2022.05.16 |