특강 1~4까지에 걸쳐 역배열 차트 탐지 알고리즘 제작을 완료하였습니다. 중점만 추려 간략히 정리해 드리겠습니다. 앞으로 이런 식으로 특강을 열어 다양한 기법들을 하나씩 계속 만들어 보도록 하겠습니다.
[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.
1. 주식일봉차트 가져오기 : opt10081
어떤 분석이든 데이터가 있어야 합니다. 주식 일봉 차트를 가져오면 아래와 같은 데이터들을 얻을 수 있습니다.
- 싱글데이터 : 코드번호
- 멀티데이터 : 현재가/시가/저가/고가 등
위에서 중요한 것은 멀티 데이터이다. 멀티 데이터는 한 번에 600개의 데이터를 개발자에게 넘겨준다. 즉 일봉일 경우 600일 치의 값을 보내준다는 의미입니다.
2. 이평선 만들기
이평선은 크게 아래와 같이 나뉩니다.
- 장기 이평선 : 120일 선
- 중기 이평선 : 60일 선
- 단기 이평선 : 20일 선
- 초단기 이평선 : 5일 선
이평선 만드는 법은 아래와 같습니다.
- 5일선 만들기
* D-day 5일선 : 오늘로 부터 5일 전까지의 종가를 모두 더한 후 5로 나눈 값
* D-1 5일선 : 어제로 부터 5일 전까지의 종가를 모두 더한 후 5로 나눈 값
* ex. (10000 + 9900 + 10100 + 10200 + 9900) / 5
상당히 간단하지 않은가? 우리는 주식일봉차트에서 받아온 600일 치의 데이터를 이용하여 20일선의 경우 총 580일 치의 이평선을 만들 수 있다. 하지만 실제로는 그렇게 긴 이평선은 의미가 없으므로 10일 정도의 데이터만 확인합니다.
3. 이평선을 이용해 역배열 판단하기
역배열을 판단할 수 있는 조건은 아래와 같습니다.
- 20일선 < 60일선 < 120일선
- 20일/60일/120일 모두 하락
- 20일선 아래 5일선 위치
위의 조건으로 역배열 탐지 결과를 확인해봅시다.
금호석유가 역배열이고 나머지는 괜찮습니다.(역배열 조건을 조금 타이트하게 내 놓았습니다.
4. 코드 공유
이런 식으로 계속 계좌를 관리할 수 있는 조건들을 찾아 만들기만 하신다면 클릭 한 번에 본인만의 성향에 적합한 투자를 아주 손쉽게 할 수 있습니다. 앞으로 계속 이렇게 저만의 생각과 기술 그리고 다른 유튜브들이 설명하는 것을 만들 것입니다. 수고하셨습니다.
코드 : 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 = []
total_sixty_price = []
total_onehtwenty_price = []
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)
for k in range(10):
total_sixty_price.append(sum(self.Predic_start[k: 60 + k]) / 60)
for k in range(10):
total_onehtwenty_price.append(sum(self.Predic_start[k: 120 + k]) / 120)
add_item = 0
for k in range(10):
if float(total_twenty_price[0]) < float(total_twenty_price[9]) and float(total_sixty_price[0]) < float(total_sixty_price[9]) and float(total_onehtwenty_price[0]) < float(total_onehtwenty_price[9]):
#if float(total_five_price[k]) < float(total_twenty_price[k]):
if float(total_twenty_price[k]) < float(total_sixty_price[k]) and float(total_sixty_price[k]) < float(total_onehtwenty_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()
'주식 자동매매 강의 > 기초반(모든 코딩의 뿌리)' 카테고리의 다른 글
주식자동매매 33강. 자동매매 종목 선정(2), 입력 종목 GUI에 출력 하기 (5) | 2022.05.26 |
---|---|
주식자동매매 32강. 자동매매 종목 선정(1), GUI 구성하기 (2) | 2022.05.25 |
[특강 4] 역배열 차트 주식 탐지 알고리즘(4), 5일/20일 이평선 만든 후 역배열 탐지하기 (3) | 2022.05.24 |
[특강 3] 역배열 차트 주식 탐지 알고리즘(3), 주식일봉차트 가져오기 (3) | 2022.05.23 |
[특강 2] 역배열 차트 주식 탐지 알고리즘(2), 주식일봉차트 보내기 (2) | 2022.05.21 |