약 7강에 걸쳐 계좌 관리하기를 마무리하였습니다. 이번 강의에서는 전체 강의 내용을 요약해서 설명드리고 코드 또한 같이 올려 드리겠습니다. 강의 마지막에 말씀드리겠지만 이번 계좌 관리하기 강의를 조금 더 할 생각이니 자세히 봐주세요.
[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.
1. 계좌관리하기 결과물 : GUI 확인
완성물입니다. 빨간 줄로 그어진 계좌관리 부분이 확실히 작동되는 것을 알 수 있습니다.
프로그램에서 테스가 관심을 나타내고 있군요. 관심은 외국인/기관이 이틀 연속으로 매도했을 경우 이죠? 아래 네이버 증권의 테스 종목 외국인/기관 매매동향을 보니 역시 이틀 연속 팔았군요. 성공하였습니다.
그런데... 뭔가좀 아쉽죠? 이평선/거래량 등등의 정보도 받으면 얼마나 더 정확히 판단할 수 있을까요? 그래서 강의 방향을 조금 틀어야겠습니다. 원칙적으로는 다음 강의부터 종목 추천하는 방법 그리고 매수/매도 하기를 끝으로 강의 마무리하려고 하였는데 저도 계좌관리는 크게 신경 쓰지 않았거든요.
이유는 종목을 잘 선정하면 무조건 본전 또는 이득을 보고 나오니 종목 선정에만 많은 에너지를 들였습니다. 하지만 역시 망한계좌도 살릴 수 있는 물타기 기법 또는 손절을 해야겠죠.
개인적으로 하나의 기업 수장이기도 한 어니스트님의 유튜브를 많이 참고하여 박살난 계좌를 우선 살릴 수 있는 프로그램을 제작하도록 하겠습니다. 그 기법 중 하나가 빌드업이라고 표현하더군요.
너무 글이 길어졌는데 어찌 되었든 다음 강의 부터는 저희 계좌를 빌드업해보겠습니다.
2. 계좌관리 코드 요약 정리
아래의 총 6가지 코드를 제작하였네요. 계좌 관리하기 1~7강까지 보시면 완전히 설명되어 있습니다.
(1) GUI 및 스크립트 만들기
(2) opt10045 주문 전송하기 하기 전 기본 세팅하기
(3) opt10045 주문 전송하기
(4) 주문 전송 결과받아오기
(5) 받아온 결과 분석하여 계좌관리에 적용하기
(6) 적용 결과 GUI 화면에 전시하기
- 여러분들은 아래 3개의 스크립트와 1개의 ui가 있어야 합니다.
- ALBA.ui : GUI 설정 결과
- kiwoom.py : 메타 클래스 기반 싱글턴
- Lecture_0511.py : 메인 스크립트
- Qthread_1.py : 계좌정보 가져오기
- Qthread_2.py : 계좌 관리하기
- kiwoom.py : 메타 클래스 기반 싱글턴
from PyQt5.QtWidgets import * # GUI의 그래픽적 요소를 제어 하단의 terminal 선택, activate py37_32, pip install pyqt5, 전부다 y
from PyQt5.QAxContainer import * # 키움증권의 클레스를 사용할 수 있게 한다.(QAxWidget)
from PyQt5Singleton import Singleton
class Kiwoom(QWidget, metaclass=Singleton): # QMainWindow : PyQt5에서 윈도우 생성시 필요한 함수
def __init__(self, parent=None, **kwargs): # Main class의 self를 초기화 한다.
print("로그인 프로그램을 실행합니다.")
super().__init__(parent, **kwargs)
################ 로그인 관련 정보
self.kiwoom = QAxWidget('KHOPENAPI.KHOpenAPICtrl.1') # CLSID
################# 전체 공유 데이터
self.All_Stock_Code = {} # 코스피, 코스닥 전체 코드넘버 입력
self.acc_portfolio = {} # 계좌에 들어있는 종목의 코드, 수익률 등등 입력
- Lecture_0511.py : 메인 스크립트
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_1.py : 계좌정보 가져오기
from PyQt5.QtCore import * # 쓰레드 함수를 불러온다.
from kiwoom import Kiwoom # 로그인을 위한 클래스
from PyQt5.QtWidgets import * #PyQt import
class Thread1(QThread):
def __init__(self, parent): # 부모의 윈도우 창을 가져올 수 있다.
super().__init__(parent) # 부모의 윈도우 창을 초기화 한다.
self.parent = parent # 부모의 윈도우를 사용하기 위한 조건
################## 키움서버 함수를 사용하기 위해서 kiwoom의 능력을 상속 받는다.
self.k = Kiwoom()
##################
################## 사용되는 변수
self.Acc_Screen = "1000" # 계좌평가잔고내역을 받기위한 스크린
###### 슬롯
self.k.kiwoom.OnReceiveTrData.connect(self.trdata_slot) # 내가 알고 있는 Tr 슬롯에다 특정 값을 던져 준다.
###### EventLoop
self.detail_account_info_event_loop = QEventLoop() # 계좌 이벤트루프
###### 계좌정보 가져오기
self.getItemList() # 종목 이름 받아오기
self.detail_acount_mystock() # 계좌평가잔고내역 가져오기
def getItemList(self):
marketList = ["0", "10"]
for market in marketList:
codeList = self.k.kiwoom.dynamicCall("GetCodeListByMarket(QString)", market).split(";")[:-1]
for code in codeList:
name = self.k.kiwoom.dynamicCall("GetMasterCodeName(QString)", code)
self.k.All_Stock_Code.update({code: {"종목명": name}})
def detail_acount_mystock(self, sPrevNext="0"):
print("계좌평가잔고내역 조회")
account = self.parent.accComboBox.currentText() # 콤보박스 안에서 가져오는 부분
self.account_num = account
print("최종 선택 계좌는 %s" % self.account_num)
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "계좌번호", account)
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "비밀번호", "0000") # 모의투자 0000
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "비밀번호입력매체구분", "00")
self.k.kiwoom.dynamicCall("SetInputValue(String, String)", "조회구분", "2")
self.k.kiwoom.dynamicCall("CommRqData(String, String, int, String)", "계좌평가잔고내역요청", "opw00018", sPrevNext, self.Acc_Screen)
self.detail_account_info_event_loop.exec_()
def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
if sRQName == "계좌평가잔고내역요청":
column_head = ["종목번호", "종목명", "보유수량", "매입가", "현재가", "평가손익", "수익률(%)"]
colCount = len(column_head)
rowCount = self.k.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
self.parent.stocklistTableWidget_2.setColumnCount(colCount) # 행 갯수
self.parent.stocklistTableWidget_2.setRowCount(rowCount) # 열 갯수 (종목 수)
self.parent.stocklistTableWidget_2.setHorizontalHeaderLabels(column_head) # 행의 이름 삽입
print("계좌에 들어있는 종목 수 %s" % rowCount)
totalBuyingPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총매입금액"))
currentTotalPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총평가금액"))
balanceAsset = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "추정예탁자산"))
totalEstimateProfit = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, 0, "총평가손익금액"))
total_profit_loss_rate = float(self.k.kiwoom.dynamicCall("GetCommData(String, String, int, String)", sTrCode, sRQName, 0, "총수익률(%)"))
#################################### 텍스트 라벨에 집어 넣기
self.parent.label_1.setText(str(totalBuyingPrice))
self.parent.label_2.setText(str(currentTotalPrice))
self.parent.label_3.setText(str(balanceAsset))
self.parent.label_4.setText(str(totalEstimateProfit))
self.parent.label_5.setText(str(total_profit_loss_rate))
#################################################################
for index in range(rowCount):
itemCode = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "종목번호").strip(" ").strip("A")
itemName = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "종목명")
amount = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "보유수량"))
buyingPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매입가"))
currentPrice = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "현재가"))
estimateProfit = int(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "평가손익"))
profitRate = float(self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "수익률(%)"))
total_chegual_price = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매입금액")
total_chegual_price = int(total_chegual_price.strip())
possible_quantity = self.k.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, index, "매매가능수량")
possible_quantity = int(possible_quantity.strip())
if itemCode in self.k.acc_portfolio:
pass
else:
self.k.acc_portfolio.update({itemCode:{}}) # self.account_stock_dict[code] = {}
self.k.acc_portfolio[itemCode].update({"종목명": itemName.strip()})
self.k.acc_portfolio[itemCode].update({"보유수량": amount})
self.k.acc_portfolio[itemCode].update({"매입가": buyingPrice})
self.k.acc_portfolio[itemCode].update({"수익률(%)": profitRate})
self.k.acc_portfolio[itemCode].update({"현재가": currentPrice})
self.k.acc_portfolio[itemCode].update({"매입금액": total_chegual_price})
self.k.acc_portfolio[itemCode].update({"매매가능수량": possible_quantity})
self.parent.stocklistTableWidget_2.setItem(index, 0, QTableWidgetItem(str(itemCode)))
self.parent.stocklistTableWidget_2.setItem(index, 1, QTableWidgetItem(str(itemName)))
self.parent.stocklistTableWidget_2.setItem(index, 2, QTableWidgetItem(str(amount)))
self.parent.stocklistTableWidget_2.setItem(index, 3, QTableWidgetItem(str(buyingPrice)))
self.parent.stocklistTableWidget_2.setItem(index, 4, QTableWidgetItem(str(currentPrice)))
self.parent.stocklistTableWidget_2.setItem(index, 5, QTableWidgetItem(str(estimateProfit)))
self.parent.stocklistTableWidget_2.setItem(index, 6, QTableWidgetItem(str(profitRate)))
if sPrevNext == "2":
self.detail_acount_mystock(sPrevNext="2") # 다음 페이지가 있으면 전부 검색한다.
else:
self.detail_account_info_event_loop.exit() # 끊어 준다.
- 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()
'주식 자동매매 강의 > 기초반(모든 코딩의 뿌리)' 카테고리의 다른 글
[특강 2] 역배열 차트 주식 탐지 알고리즘(2), 주식일봉차트 보내기 (2) | 2022.05.21 |
---|---|
[특강 1] 역배열 차트 주식 탐지 알고리즘(1), 개념 설명, 알면 피할 수 있다 (2) | 2022.05.20 |
주식자동매매 30강. 계좌관리하기 (7), opt10045 결과 GUI에 입력하기 (2) | 2022.05.18 |
주식자동매매 29강. 계좌관리하기 (6), opt10045 결과를 이용한 계좌 위험도 판단하기 (1) | 2022.05.18 |
주식자동매매 28강. 계좌관리하기 (5), opt10045 전송 주문 결과 받아오기 (8) | 2022.05.17 |