[특강 4] 역배열 차트 주식 탐지 알고리즘(4), 5일/20일 이평선 만든 후 역배열 탐지하기
본문 바로가기

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

[특강 4] 역배열 차트 주식 탐지 알고리즘(4), 5일/20일 이평선 만든 후 역배열 탐지하기

반응형

특강 3에서 주식 일봉 차트를 불러왔습니다. 그러면 우리는 이 데이터를 어떻게 이평선 데이터로 만들고 또한 역배열을 판단할 수 있는지 간단히 확인해 보겠습니다.

 

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

 

1. 역배열 차트 복습하기

 (1) 장기(120), 중기(60), 단기(20) 일 선 전부 하락

 (2) 하락 순서 : 장기(120) < 중기(60) < 단기(20)

 (3) 장기(120), 중기(60), 단기(20)일 선 아래에 현재가 형성

역배열 다시 이해하기
역배열 다시 이해하기

2. 이평선 코딩하기

 저번 강의에서 설명드렸듯이

 - self.Predic_start에는 600일치의 현재가가 포함되어 있습니다.

 - self.calcul_data에는 600일치의 고가/저가/현재가 등등이 포함되어 있습니다.

이평선 만들기
이평선 만들기

 우선 self.calcul_data가 비어있거나(네트워크 오류나 진짜 데이터가 없는 경우[상장폐지 등]) 또는 총 데이터 길이가 200일 이하인 경우 역배열 판단이 불가능한 코드를 작성합니다.

 - self.k.acc_portfolio[self.code_in_all].update({"역배열": "데이터 없음"}) : self.k에 있는 acc_portfolio의 slef.code_in_all(현재 접속된 코드)에 접속한 후 크 코드의 딕셔너리에 역배열 : 데이터 없음을 추가시킵니다.

 만약 self.calcul_data가 비어잇지 않거나 200일 치 이상의 데이터 있다면 아래와 같은 코드를 실행시킵니다.

 - total_five_price : 5일선 데이터를 저장할 리스트 공간

 - total_twenty_price : 20일선 데이터를 저장할 리스트 공간

 그리고 이평선을 이제 만들 건데 이평선 만드는 수식은 아래와 같습니다.

 - 아래와 같은 이평선 데이터가 10개 있습니다.

 - 5일 선일 경우 5일 치 데이터의 합을 5로 나누면 5일 이평선이 됩니다.

 - sum(self.Predic_start [k: 5+ k]) / 5 : self.Predic_start의 0:5까지의 모든 데이터를 sum(더한) 후 5로 나눕니다.

 - 20일 선일 경우 20일 치 데이터의 합을 20으로 나누면 20일 이평선이 됩니다.

 - sum(self.Predic_start [k: 20+ k]) / 20 : self.Predic_start의 0:20까지의 모든 데이터를 sum(더한) 후 20으로 나눕니다.

 위 코드를 실행하면 5일선 데이터 10개와 20일선 데이터 10개가 나오게 됩니다.

 

3. 역배열 탐지하기

 1에서 설명드린 방법들은 제외하고 간단히 아래와 같은 알고리즘을 만들었습니다.

역배열 탐지
역배열 탐지

 (1) 5일선 데이터가 20일선 데이터보다 작고

  - if float(total_five_price [k]) < float(total_twenty_price [k]

 (2) 현재가가 20일선 데이터보다 작으면

  - and float(self.calcul_data[k][1]) < float(total_twenty_price[k]):

 (3) add_time에 1씩 더한다

  - add_item += 1

 (4) add_item이 8 이상이면(10개 중 하락이 8개 이상이면) 역배열이 맞음을 acc_portfolio에 업데이트

  - if add_item >= 8:

  - self.k.acc_portfolio[self.code_in_all].update({"역배열":"맞음"})

 (5) 그렇지 않으면 역배열이 아님을 acc_portfolio에 업데이트 

  - else:

  - self.k.acc_portfolio[self.code_in_all].update({"역배열":"아님"})

 

 그리고 다음 코드를 위해 self.calcul_data와 self.Predic_start를 clear() 함수를 이용해 초기화합니다. 또한 모든 과정이 끝나면 이벤트 루프를 종료합니다.

 - self.calcul_data.clear()

 - self.Predic_start.clear()

 - self.detail_account_info_event_loop.exit()

 

4. 탐지 결과를 표에 삽입

 31강에서 설명드린 부분에 역배열을 추가하였습니다. GUI는 별도 수정이 없어도 됩니다.

탐지결과를 표에 삽입
탐지결과를 표에 삽입

 최종 결과는 아래와 같습니다. 제작된 알고리즘은 1에서 설명드린 모든 것을 반영하지는 않았습니다. 누구든 만들 수 있게 간단히 설명한 것이므로 본인의 성향에 맞게 각 조건들을 바꾸시면 됩니다.

열배열 GUI 화면
열배열 GUI 화면

가상계좌에 종목을 보니.. 역배열인 게 많이 있네요. 현대제철을 볼까요? 역시 이평선들이 다 내려가고 있습니다. 물론 더 디테일하게 만드셔야 됩니다.

 

현대 제철 10일 치
현대 제철 10일 치

5. 코드 공유 : 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.Predic_Screen = "1400"  # 일봉차트를 가져오기위한 스크린
        self.calcul_data = []        # 받아온 종목의 다양한 값(현재가/고가/저가 등)을 계산한다.
        self.second_filter = []      # 역배열인지 확인
        self.Predic_start = []       # 미래예측


        ###### 슬롯
        self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot)  # 내가 알고 있는 Tr 슬롯에다 특정 값을 던져 준다.

        ###### EventLoop
        self.detail_account_info_event_loop = QEventLoop()  # 계좌 이벤트루프

        ###### 기관외국인 평균가 가져오기
        self.C_K_F_class()

        ###### 역배열 평가
        self.Invers_arrangement()

        ###### 결과 붙이기(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]["위험도"]))
            self.parent.Danger_wd.setItem(index2, 3, 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 Invers_arrangement(self):

        code_list = []
        for code in self.k.acc_portfolio.keys():
            code_list.append(code)

        print("계좌포함 종목 %s" % (code_list))

        for idx, code in enumerate(code_list):
            QTest.qWait(1000)

            self.code_in_all = code  # 종목코드 선언 (중간에 코드 정보 받아오기 위해서)

            print("%s 종목 검사 중 코드이름 : %s." % (idx + 1, self.code_in_all))

            self.k.kiwoom.dynamicCall("DisconnectRealData(QString)", self.Predic_Screen)  # 해당 스크린을 끊고 다시 시작
            self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
            self.k.kiwoom.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1") # 수정주가구분 0: 액면분할등이 포함되지 않음, 1: 포함됨
            self.k.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "주식일봉차트조회", "opt10081", "0", self.Predic_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()



        if sRQName == "주식일봉차트조회":

            code = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "종목코드")
            code = code.strip()  # 여백 발생 방지
            cnt = self.k.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)

            # print("데이터 일수 %s" % cnt)
            # 600일치 데이터를 한번에 받아오는 함수 : GetCommDataEx,  리스트로 반환
            # data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
            # [['', '현재가', '거래량', '거래대금', '날짜', '시가', '고가', '저가', ''], [ ........

            for i in range(cnt):  # [0] ~ [599]

                data = []
                current_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "현재가")
                value = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래량")
                trading_value = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래대금")
                date = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "일자")  # 접수, 확인, 채결
                start_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "시가")
                high_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "고가")
                low_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "저가")

                data.append("")  # 빈칸을 만들어 주는 이유는 GetCommDataEx함수의 반환값과 동일하게 하기 위해서
                data.append(current_price.strip())
                data.append(value.strip())
                data.append(trading_value.strip())
                data.append(date.strip())
                data.append(start_price.strip())
                data.append(high_price.strip())
                data.append(low_price.strip())
                data.append("")

                self.Predic_start.append(int(current_price.strip()))
                self.calcul_data.append(data.copy())  # 리스트로 데이터가 들어간다.


            if self.calcul_data == None or len(self.calcul_data) < 210:

                self.k.acc_portfolio[self.code_in_all].update({"역배열":"데이터 없음"})

            else:  # 만약 120개의 데이터가 존재한다면

                total_five_price = []  # 다음번 코드를 위해 0으로 초기화
                total_twenty_price = []  # 다음번 코드를 위해 0으로 초기화

                for k in range(10):    # range(10) = 0 ,1 .... 9
                    total_five_price.append(sum(self.Predic_start[k: 5 + k]) / 5)  # a[0:5] = 0, 1, 2, 3, 4

                for k in range(10):

                    total_twenty_price.append(sum(self.Predic_start[k: 20 + k]) / 20)


                add_item = 0

                for k in range(10):

                    if float(total_five_price[k]) < float(total_twenty_price[k]) and float(self.calcul_data[k][1]) < float(total_twenty_price[k]):
                        add_item += 1
                    else:
                        pass

                if add_item >=8:
                    self.k.acc_portfolio[self.code_in_all].update({"역배열": "맞음"})

                else:
                    self.k.acc_portfolio[self.code_in_all].update({"역배열": "아님"})


            self.calcul_data.clear()  # 코드에 들어 있는 일봉 데이터 삭제
            self.Predic_start.clear()

            self.detail_account_info_event_loop.exit()
반응형

.link_tit