주식자동매매 30강. 계좌관리하기 (7), opt10045 결과 GUI에 입력하기
본문 바로가기

주식 자동매매 강의/기초반(모든 코딩의 뿌리)

주식자동매매 30강. 계좌관리하기 (7), opt10045 결과 GUI에 입력하기

반응형

29강에서 opt10045 : 기관/외국인매매동향 정보를 받아왔습니다. 그리고 관련 자료를 분석하여 계좌 잔고의 위험도를 판단하였죠. 그 결과를 저희가 만든 GUI에 입력해 보겠습니다.

 

[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.

1. GUI에 Push 버튼 및 테이블위젯 생성

 Push 버튼을 누르면 저희가 만든 QThread_2.py를 실행시켜야 하며, QThread_2.py에서 분석된 결과를 테이블 위젯에 입력시키겠습니다.

 - Qt Dsigner에 Push 버튼 생성

  Push Button 이름은 계좌관리이며, objectName은 acc_manage 입니다.

Push 버튼 생성
Push 버튼 생성

 - Qt Dsigner에 Table Widget 표 생성

  Table Widget의 objectName은 Danger_wd 입니다.

Table Widget 표 생성
Table Widget 표 생성

2. Qthread_2.py 실행 코드 코딩

 - 메인 스크립트의 부가 기능 수행 부분에서 from Qthread_2 import Thread2를 적어 줍니다.  Qthread_2.py의 Thread2 클래스 기능을 할당 받겠다는 뜻입니다.

Qthread_2.py의 Thread2 클래스 기능을 할당 받기
Qthread_2.py의 Thread2 클래스 기능을 할당 받기

 - 앞서 정의한 push 버튼이 클릭되면 우리가 만들 함수가 실행되게 정의합니다. 코드를 보시면 self.acc_manage를 clicked 했을경우 self.a_manage 함수가 실행되는 것입니다.

push 버튼이 클릭되면 우리가 만들 함수가 실행되게 정의
push 버튼이 클릭되면 우리가 만들 함수가 실행되게 정의

 - a_manage 함수를 정의 합니다. def를 사용하여 함수의 시작을 알리고 a_manage(self): 함수를 작성합니다. 내용은 Thread2(self)를 h2에 인스턴스화 하고 h2를 시작하게 했습니다. 이러한 일련의 과정을 거치면 Qthread_2.py 의 Thread2 클래스가 실행됩니다.

a_manage 함수 정의하기
a_manage 함수 정의하기

 

3. 계좌관리정보 Table Widget에 입력하기

 29강에서 설명드린 아래 위험도 판단 함수 결과를 Table Widget에 입력해 보겠습니다.

29강에서 설명드린 아래 위험도 판단 함수
29강에서 설명드린 아래 위험도 판단 함수

 - 기관외국인 평균가를 가져오는 self.C_K_F_class() 함수아래 결과를 GUI 입력하는 함수를 작성합니다. 이 함수 작성 방법은 19강에서도 아주 자세히 설명되어 있습니다.

결과를 GUI 입력
결과를 GUI 입력

 

 - 위 코드는 테이블의 행과 열을 정의하며, 열의 개수는 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()
반응형

 

반응형

.link_tit