파이썬으로 어떤 프로그램을 만들어본게 이 프로그램이 처음이었고
주식 또한 작년 초에 잠깐 했었으나 한 달 하고 그만뒀었고, 제대로 공부해본적도 없었습니다.
올해도 프로그램을 먼저 만들어두고 공부를 하고 시작해야지 하던걸 해외주식만 하느라
프로그램도 뒷전으로, 주식공부 조금, 종목분석 조금씩만 하고있었네요.
그러다 수수료나 세금을 감당할 생각에 (그만큼 수익을 낼 자신도 없지만)
한국주식을 다시 해봐야겠다는 생각이 들었습니다.
이 프로그램은 자동매매보다는 제가 원하는 종목 분석정도로만 사용할 예정입니다.
그러다보니 뺄 부분은 빼고 남길 부분만 남겼습니다.
깃헙은 아직도 사용법을 제대로 모르겠습니다. 수박 겉핥기 식으로 배운 프로그래밍은
프로그램을 짜는 법만 알려주지 그 외의 것들은 다 제가 해봐야 하더라고요.
물론 배우려고 하지도 않았습니다. 해야할 것도 많았고 놀고싶기도 했고.
아무튼 지금 올리는 코드는 kiwoom.py 코드입니다.
제가 원하는 분석은 그저 25일 이평선 아래에 있는 종목을 찾아주는겁니다.
그 외의 오를만한 주식인지, 떨어질 주식인지는 추려진 종목들 중에서 봐야겠죠.
코드를 다시 한 번 쭉 읽으면서 수정해야하지만 우선은 이평선 기준으로 파일에 넣는 것만 했습니다.
글을 적으면서 생각해보니 현재가가 25일 이평선 아래에 있고, 전날 대비 상승한 종목을 넣는 것 같은데
코드를 다시 봐야겠습니다.
티스토리 블로그를 잊고있다가 오랜만에 들어왔는데 한 분도 보지 않을줄 알았던 게시물들이
생각보다 조회수가 나오고 있었고 (모든 게시글 합산 일 최대 26회면 저에겐 과분합니다)
중간점검 게시글에도 두 분이나 하트를 찍어주신걸 보고 조금이나마 기록을 더 남겨보려합니다.
import os
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
from config.errorCode import *
from PyQt5.QtTest import *
from config.kiwoomType import *
from config.slack import *
from config.log_class import *
class Kiwoom(QAxWidget):
def __init__(self):
super().__init__()
self.logging = Logging()
self.logging.logger.debug("Kiwoom() class start.")
self.realType = RealType()
self.slack = Slack() # 슬랙 동작
######## 이벤트 루프를 실행하기 위한 변수 모음
self.login_event_loop = QEventLoop() # 로그인 요청용 이벤트 루프
self.detail_account_info_event_loop = QEventLoop() # 예수금 요청용 이벤트 루프
self.calculator_event_loop = QEventLoop() # 종목 분석용 이벤트 루프
# self.ing_event_loop = QEventLoop() # 장 진행 중 전일대비 상승량 분석용 이벤트 루프
#################################################
######## 전체 종목 관리
self.all_stock_dict = {}
#################################################
######## 계좌 관련된 변수
self.account_stock_dict = {} # 보유 종목 정보 딕셔너리 생성
self.not_account_stock_dict = {} # 미체결 딕셔너리 생성
self.account_num = None # 계좌번호를 담아줄 변수
self.deposit = 0 # 예수금
self.use_money = 0 # 실제 투자에 사용할 금액
self.use_money_percent = 0.5 # 예수금에서 실제 사용할 비율
self.output_deposit = 0 # 출력가능 금액
self.total_profit_loss_money = 0 # 총 평가 손익금액
self.total_profit_loss_rate = 0.0 # 총 수익률(%)
#################################################
######## 종목 분석용
self.calcul_data = []
self.netchange = 0
#################################################
######## 종목 정보 가져오기
self.jango_dict = {}
self.portfolio_stock_dict = {}
#################################################
######## 내가 쓰는 임의 변수들
self.moving = 25 # 이평선 날짜 기준
self.net_change = 0 # 전일대비 상승량
#################################################
######## 요청 스크린 번호
self.screen_my_info = "2000" # 계좌 관련한 스크린 번호
self.screen_calculation_stock = "4000" # 계산용 스크린 번호
self.screen_real_stock = "5000" # 종목별 할당할 스크린 번호
self.screen_meme_stock = "6000" # 종목별 할당할 주문용 스크린 번호
self.screen_start_stop_real = "1000" # 장 시작/조료 실시간 스크린 번호
self.screen_ing_cal = '7000' # 장 중 계산용
#################################################
######## 초기 셋팅 함수들 바로 실행
self.get_ocx_instance() # OCX 방식을 파이썬에 사용할 수 있게 변환해 주는 함수 실행
self.event_slots() # 키움과 연결하기 위한 시그널 / 슬롯 모음
self.real_event_slot() # 실시간 이벤트 시그널 / 슬롯 연결
self.signal_login_commConnect() # 로그인 요청 함수 포함
#####
# self.get_account_info() # 계좌번호 가져오기
# self.detail_account_info() # 예수금 요청 시그널 포함
# self.detail_account_mystock() # 계좌평가잔고내역 가져오기
# QTimer.singleShot(5000, self.not_concluded_account) # 5초 뒤에 미체결 종목들 가져오기 실행
##### 조회만 할거라 내 계좌 정보를 받아올 필요가 없음
# self.logging.logger.debug("초기함수")
# self.calculator_fnc()
#################################################
QTest.qWait(10000)
self.read_code()
self.screen_number_setting()
# self.logging.logger.debug("10초")
QTest.qWait(5000)
# 실시간 수신 관련 함수
self.dynamicCall("SetRealReg(QString, QString, QString, QString)",
self.screen_start_stop_real, ' ', self.realType.REALTYPE['장시작시간']['장운영구분'], 0)
# self.logging.logger.debug("5초")
for code in self.portfolio_stock_dict.keys():
screen_num = self.portfolio_stock_dict[code]['스크린번호']
fids = self.realType.REALTYPE['주식체결']['체결시간']
self.dynamicCall("SetRealReg(QString, QString, QString, QString)",
screen_num, code, fids, "1")
self.slack.notification(
pretext=str(os.times()),
title="주식자동화 프로그램 동작",
fallback="주식자동화 프로그램 동작",
text=str(os.times())
)
QTest.qWait(5000)
self.calculator_fnc()
def get_ocx_instance(self):
# self.logging.logger.debug("get_ocx_instance")
self.setControl("KHOPENAPI.KHOpenAPICtrl.1") # 레지스트리에 저장된 api 모듈 불러오기
def event_slots(self):
# self.logging.logger.debug("event_slots")
self.OnEventConnect.connect(self.login_slot) # 로그인 관련 이벤트
self.OnReceiveTrData.connect(self.trdata_slot) # 트랜잭션 요청 관련 이벤트
self.OnReceiveMsg.connect(self.msg_slot) # 메시지 받는 이벤트
def signal_login_commConnect(self):
# self.logging.logger.debug("signal_login_commConnect")
self.dynamicCall("CommConnect()") # 로그인 요청 시그널
self.login_event_loop.exec_() # 이벤트 루프 실행
## 로그인 요청 후 프로그램이 끝나면 안되기 때문에 이벤트루프를 실행하여
## 프로그램의 종료를 막아 안정성을 확보한다.
def login_slot(self, err_code):
# self.logging.logger.debug("login_slot")
self.logging.logger.debug(errors(err_code)[1])
# 로그인 처리가 완료됐으면 이벤트 루프를 종료한다.
self.login_event_loop.exit()
# get_account_info 안씀
def get_account_info(self):
# self.logging.logger.debug("get_account_info")
account_list = self.dynamicCall("GetLoginInfo(QString)", "ACCNO") # 계좌번호 반환
account_num = account_list.split(';')[0] # a;b;c -> [a, b, c]
self.account_num = account_num
self.logging.logger.debug("계좌번호 : %s" % account_num)
# 얘도 안씀
def detail_account_info(self, sPrevNext="0"):
# self.logging.logger.debug("detail_account_info")
self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
self.dynamicCall("SetInputValue(QString, QString)", "비밀번호", "0000")
self.dynamicCall("SetInputValue(QString, QString)", "비밀번호입력매체구분", "00")
self.dynamicCall("SetInputValue(QString, QString)", "조회구분", "1")
self.dynamicCall("CommRqData(QString, QString, int, QString)",
"예수금상세현황요청", "opw00001", sPrevNext, self.screen_my_info)
self.detail_account_info_event_loop.exec_()
def trdata_slot(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext):
# self.logging.logger.debug("trdata_slot")
if sRQName == "예수금상세현황요청":
self.logging.logger.debug("예수금상세현황요청")
deposit = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "예수금")
self.deposit = int(deposit)
use_money = float(self.deposit) * self.use_money_percent
self.use_money = int(use_money)
self.use_money = self.use_money / 4
output_deposit = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "출금가능금액")
self.output_deposit = int(output_deposit)
self.logging.logger.debug("예수금 : %s" % self.output_deposit)
self.stop_screen_cancel(self.screen_my_info)
self.detail_account_info_event_loop.exit()
elif sRQName == "계좌평가잔고내역요청":
# self.logging.logger.debug("계좌평가잔고내역요청")
total_buy_money = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "총매입금액")
self.total_buy_money = int(total_buy_money)
total_profit_loss_money = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "총평가손익금액")
self.total_profit_loss_money = int(total_profit_loss_money)
total_profit_loss_rate = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "총수익률(%)")
self.total_profit_loss_rate = float(total_profit_loss_rate)
self.logging.logger.debug("계좌평가잔고내역요청 싱글데이터 : %s - %s - %s" % (total_buy_money,
total_profit_loss_money,
total_profit_loss_rate))
self.stop_screen_cancel(self.screen_my_info)
# 책에는 있지만 깃헙에는 없는 내용
rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
# 계좌가 보유중인 종목의 갯수를 카운트해준다.
for i in range(rows):
code = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "종목번호")
code = code.strip()[1:]
code_nm = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "종목명")
stock_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "보유수량")
buy_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "매입가") # 매입가 평균
learn_rate = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "수익률(%)")
current_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "현재가")
total_chegual_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "매입금액")
possible_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "매매가능수량")
if code in self.account_stock_dict:
pass
else:
self.account_stock_dict[code] = {}
code_nm = code_nm.strip()
stock_quantity = int(stock_quantity.strip())
buy_price = int(buy_price.strip())
learn_rate = float(learn_rate.strip())
current_price = int(current_price.strip())
total_chegual_price = int(total_chegual_price.strip())
possible_quantity = int(possible_quantity.strip())
self.account_stock_dict[code].update({"종목명": code_nm})
self.account_stock_dict[code].update({"보유수량": stock_quantity})
self.account_stock_dict[code].update({"매입가": buy_price})
self.account_stock_dict[code].update({"수익률(%)": learn_rate})
self.account_stock_dict[code].update({"현재가": current_price})
self.account_stock_dict[code].update({"매입금액": total_chegual_price})
self.account_stock_dict[code].update({'매매가능수량': possible_quantity})
self.logging.logger.debug("종목코드: %s - 종목명: %s - 보유수량: %s - 매입가:%s - 수익률: %s - 현재가: %s" % (
code, code_nm, stock_quantity, buy_price, learn_rate, current_price))
self.logging.logger.debug("sPrevNext : %s" % sPrevNext)
self.logging.logger.debug("계좌에 가지고 있는 종목은 %s " % rows)
if sPrevNext == "2":
self.detail_account_mystock(sPrevNext="2")
else:
self.detail_account_info_event_loop.exit()
elif sRQName == "실시간미체결요청":
# self.logging.logger.debug("실시간미체결요청")
rows = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
for i in range(rows):
# code = self.dynamicCall("GetCommData(QString, QString, int, QString)",
# sTrCode, sRQName, i, "종목코드")
code = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "종목코드")
code_nm = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "종목명")
order_no = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "주문번호")
order_status = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "주문상태") # 접수,확인,체결
order_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "주문수량")
order_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "주문가격")
order_gubun = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "주문구분") # -매도, +매수, -매도정정, +매수정정
not_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "미체결수량")
ok_quantity = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "체결량")
code = code.strip()
code_nm = code_nm.strip()
order_no = int(order_no.strip())
order_status = order_status.strip()
order_quantity = int(order_quantity.strip())
order_price = int(order_price.strip())
order_gubun = order_gubun.strip().lstrip('+').lstrip('-')
not_quantity = int(not_quantity.strip())
ok_quantity = int(ok_quantity.strip())
if order_no in self.not_account_stock_dict:
pass
else:
self.not_account_stock_dict[order_no] = {}
self.not_account_stock_dict[order_no].update({'종목코드': code})
self.not_account_stock_dict[order_no].update({'종목명': code_nm})
self.not_account_stock_dict[order_no].update({'주문번호': order_no})
self.not_account_stock_dict[order_no].update({'주문상태': order_status})
self.not_account_stock_dict[order_no].update({'주문수량': order_quantity})
self.not_account_stock_dict[order_no].update({'주문가격': order_price})
self.not_account_stock_dict[order_no].update({'주문구분': order_gubun})
self.not_account_stock_dict[order_no].update({'미체결수량': not_quantity})
self.not_account_stock_dict[order_no].update({'체결량': ok_quantity})
self.logging.logger.debug("미체결 종목 : %s " % self.not_account_stock_dict[order_no])
self.detail_account_info_event_loop.exit()
elif sRQName == "전일대비":
# self.logging.logger.debug("sRQName == 전일대비")
netchange = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "전일대비")
self.logging.logger.debug("GetCommData netchange = %s" % (netchange))
self.net_change = netchange
# self.logging.logger.debug("net_change 구하기")
self.stop_screen_cancel(self.screen_ing_cal)
# self.ing_event_loop.exit()
elif sRQName == "주식일봉차트조회":
# self.logging.logger.debug("주식일봉차트조회")
code = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, 0, "종목코드")
code = code.strip()
# data = self.dynamicCall("GetCommDataEx(QString, QString)", sTrCode, sRQName)
cnt = self.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName)
self.logging.logger.debug("남은 일자 수 %s" % cnt)
if cnt >= self.moving:
cnt = self.moving
if cnt >= 25:
##### 여기서부터 탭
for i in range(cnt):
data = []
current_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "현재가") # 출력 00070
value = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "거래량") # 출력 00070
trading_value = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "거래대금") # 출력 00070
date = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "일자") # 출력 00070
start_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "시가") # 출력 00070
high_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "고가") # 출력 00070
low_price = self.dynamicCall("GetCommData(QString, QString, int, QString)",
sTrCode, sRQName, i, "저가") # 출력 00070
data.append("")
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.calcul_data.append(data.copy())
## 여기까지 for문
## 25일치 차트 정보를 calcul_data 딕셔너리에 입력
# self.logging.logger.debug("break")
# if sPrevNext == "2":
# self.logging.logger.debug("sPrevNext == %s" % sPrevNext)
# self.day_kiwoom_db(code=code, sPrevNext=sPrevNext)
# else:
## 여기서부터 탭
self.logging.logger.debug("총 일수 %s" % len(self.calcul_data))
## 총 일수는 25일로 고정
pass_success = False
## 일단 False 넣어두고 조건 통과되면 True
# 25(self.moving)일 이평선을 그릴만큼의 데이터가 있는지 체크
if self.calcul_data == None or len(self.calcul_data) < self.moving:
pass_success = False
else:
# 25(self.moving)일 이평선의 최근 가격 구함
total_price = 0
for value in self.calcul_data[:self.moving]:
total_price += int(value[1])
moving_average_price = total_price / self.moving
#
# # 오늘자 주가가 25(self.moving)일 이평선에 걸쳐있는지 확인
# bottom_stock_price = False
# check_price = None
# if int(self.calcul_data[0][7]) <= moving_average_price and \
# moving_average_price <= int(self.calcul_data[0][6]):
# self.logging.logger.debug("오늘의 주가가 %s 이평선에 걸쳐있는지 확인" % self.moving)
# bottom_stock_price = True
# check_price = int(self.calcul_data[0][6])
# # [0][6] 은 고가
# # [0][7] 은 저가
## 오늘자 저가가 25(self.moving)일 이평선 아래에 있는지 확인
bottom_stock_price = False
check_price = None
if int(self.calcul_data[0][7]) <= (moving_average_price * 0.85):
# 25일 평균가격이 현재 저가의 90% 이하일때 전일대비 가격이 상승추이인지 확인
# self.logging.logger.debug("오늘의 저가가 %s일 이평선아래에 있는지 확인" % self.moving)
bottom_stock_price = True
check_price = int(self.calcul_data[0][6])
# [0][6] 은 고가
# [0][7] 은 저가
## 여기서부터 새로 짠 코드
## 당일 저가가 25일 이평선 아래에 있는걸 확인한 후
## 당일 주가 추이가 전일 대비 상승세일 때 pass_success = True
# self.logging.logger.debug("당일 주가 추이가 전일 대비 상승세인지 확인")
QTest.qWait(5000)
# netchange = self.dynamicCall("GetCommRealData(QString, int)", code, 11)
# netchange = int(netchange)
#
# self.logging.logger.debug("te")
# self.logging.logger.debug("%s : netchange, %s : code", (self.netchange, code))
# self.getTemp(code=code)
self.logging.logger.debug("%s : code", (code))
self.logging.logger.debug("%s : net_chagne", (self.net_change))
self.net_change = int(self.net_change)
# self.logging.logger.debug("error Test")
# 여기서 에러남. RealData는 장중에만 되는지 확인해야할듯
# self.dynamicCall("CommRqData(QString, QString, int, QString)", "상승량", "opt10001", sPrevNext, sScrNo)
# self.logging.logger.debug("상승량 : %s", self.netchange)
## 전일대비 가격 불러옴
if self.net_change > 0:
pass_success = True
## 어제보다 올랐으면 True
# self.logging.logger.debug("tesat")
## 아래 코드 대신 새로 짜기
# # 과거 일봉 데이터를 조회하면서 25(self.moving)일 이평선보다 주가가 계속 밑에 존재하는지 확인
# prev_price = None
# if bottom_stock_price == True:
# moving_average_price_prev = 0
# price_top_moving = False
# idx = 1
#
# while True:
# if len(self.calcul_data[idx:]) < self.moving:
# # 25(self.moving)일 치가 계속 있는지 확인
# self.logging.logger.debug("%s일 치가 없음" % self.moving)
# break
# total_price = 0
# for value in self.calcul_data[idx:self.moving+idx]:
# total_price += int(value[1])
# moving_average_price_prev = total_price / self.moving
#
# if moving_average_price_prev <= int(self.calcul_data[idx][6]) and idx <= 5:
# self.logging.logger.debug("5일동안 주가가 %s일 이평선과 같거나 위에 있으면 조건 통과 못함" % self.moving)
# price_top_moving = False
# break
# elif int(self.calcul_data[idx][7]) > moving_average_price_prev and idx > 5:
# # 25(self.moving)일 이평선 위에 있는 구간 존재
# self.logging.logger.debug("%s일치 이평선 위에 있는 구간 확인됨" % self.moving)
# price_top_moving = True
# prev_price = int(self.calcul_data[idx][7])
# break
#
# idx += 1
#
# # 해당 부분 이평선이 가장 최근의 이평선 가격보다 낮은지 확인
# if price_top_moving == True:
# if moving_average_price > moving_average_price_prev and check_price > prev_price:
# self.logging.logger.debug("포착된 이평선의 가격이 오늘자 이평선 가격보다 낮은 것 확인")
# self.logging.logger.debug("포착된 부분의 일봉 저가가 오늘자 일봉의 고가보다 낮은지 확인")
# pass_success = True
# 다중 주석 단축키 Ctrl + /
if pass_success == True:
self.logging.logger.debug("조건부 통과됨")
code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)
f = open("files/condition_stock.txt", "a", encoding="utf8")
f.write("%s\t%s\t%s\n" % (code, code_nm, str(self.calcul_data[0][1])))
f.close()
# 여기에 슬랙 메시지 전송 추가하기
self.slack.notification(
pretext=str(code_nm),
title=str(code_nm),
fallback=str(code_nm),
text=str(code_nm)
)
###########################
elif pass_success == False:
self.logging.logger.debug("조건부 통과 못 함")
##### 여기까지 탭
self.calcul_data.clear()
self.calculator_event_loop.exit()
## 여기까지 탭
def stop_screen_cancel(self, sScrNo=None):
# self.logging.logger.debug("stop_screen_cancel")
self.dynamicCall("DisconnectRealData(QString)", sScrNo)
def detail_account_mystock(self, sPrevNext="0"):
# self.logging.logger.debug("detail_account_mystock")
self.dynamicCall("SetInputValue(QString, QString", "계좌번호", self.account_num)
self.dynamicCall("SetInputValue(QString, QString", "비밀번호", "0000")
self.dynamicCall("SetInputValue(QString, QString", "비밀번호입력매체구분", "00")
self.dynamicCall("SetInputValue(QString, QString", "조회구분", "1")
self.dynamicCall("CommRqData(QString, QString, int, QString", "계좌평가잔고내역요청",
"opw00018", sPrevNext, self.screen_my_info)
self.detail_account_info_event_loop.exec_()
def not_concluded_account(self, sPrevNext="0"):
# self.logging.logger.debug("미체결 종목 요청")
self.dynamicCall("SetInputValue(QString, QString)", "계좌번호", self.account_num)
self.dynamicCall("SetInputValue(QString, QString)", "체결구분", 1)
self.dynamicCall("SetInputValue(QString, QString)", "매매구분", 0)
self.dynamicCall("CommRqData(QString, QString, int, QString)",
"실시간미체결요청", "opt10075", sPrevNext, self.screen_my_info)
self.detail_account_info_event_loop.exec_()
def get_code_list_by_market(self, market_code):
# self.logging.logger.debug("get_code_list_by_market")
code_list = self.dynamicCall("GetCodeListByMarket(QString)", market_code)
code_list = code_list.split(';')[:-1]
return code_list
def calculator_fnc(self):
# self.logging.logger.debug("calculator_fnc")
code_list = self.get_code_list_by_market("0")
self.logging.logger.debug("코스피 갯수 %s " % len(code_list))
# 코스닥 갯수 1426
for idx, code in enumerate(code_list):
self.dynamicCall("DisconnectRealData(QString)", self.screen_calculation_stock)
# 스크린 연결 끊기
## 중간부터 할 때 사용
# if idx <= 1254:
# pass
# else:
# self.logging.logger.debug("%s %s : KOSPI Stock Code : %s is updating... " % (idx + 1, len(code_list), code))
# self.day_kiwoom_db(code=code)
code_nm = self.dynamicCall("GetMasterCodeName(QString)", code)
self.logging.logger.debug(
"%s %s : KOSPI Stock Code : %s 종목 : %s is updating... " % (idx + 1, len(code_list), code, code_nm))
self.day_kiwoom_db(code=code)
self.logging.logger.debug("KOSPI 분석 완료")
self.slack.notification(
pretext="KOSPI 분석 완료",
title="KOSPI 분석 완료",
fallback="KOSPI 분석 완료",
text="KOSPI 분석 완료"
)
def day_kiwoom_db(self, code=None, date=None, sPrevNext="0"):
# self.logging.logger.debug("day_kiwoom_db")
QTest.qWait(5000) # 3.6초마다 디레이를 준다.
self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
self.dynamicCall("CommRqData(QString, QString, int, QString)", "전일대비", "opt10001", "0", self.screen_ing_cal)
self.logging.logger.debug("전일대비 가격 불러오기")
self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
self.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", 1)
if date != None:
self.dynamicCall("SetInputValue(QString, QString)", "기준일자", date)
# self.logging.logger.debug("다시넘김1")
self.dynamicCall("CommRqData(QString, QString, int, QString)",
"주식일봉차트조회", "opt10081", sPrevNext, self.screen_calculation_stock)
# self.logging.logger.debug("다시넘김2")
self.calculator_event_loop.exec_()
# self.logging.logger.debug("이벤트루프종료")
def read_code(self):
# self.logging.logger.debug("read_code")
if os.path.exists("files/condition_stock.txt"):
# 해당 경로에 파일이 있는지 확인한다.
f = open("files/condition_stock.txt", "r", encoding="utf8")
lines = f.readlines() # 파일에 있는 내용들이 모두 읽어와진다.
for line in lines: # 줄바꿈 된 내용들이 한줄 씩 읽어와진다.
if line != "":
ls = line.split("\t")
stock_code = ls[0]
stock_name = ls[1]
stock_price = int(ls[2].split("\n")[0])
stock_price = abs(stock_price)
self.portfolio_stock_dict.update({stock_code: {"종목명": stock_name, "현재가": stock_price}})
f.close()
def merge_dict(self):
# self.logging.logger.debug("merge_dict")
self.all_stock_dict.update({"계좌평가잔고내역": self.account_stock_dict})
self.all_stock_dict.update({'미체결종목': self.not_account_stock_dict})
self.all_stock_dict.update({'포트폴리오종목': self.portfolio_stock_dict})
def screen_number_setting(self):
# self.logging.logger.debug("screen_number_setting")
screen_overwrite = []
# 계좌평가잔고내역에 있는 종목들
for code in self.account_stock_dict.keys():
if code not in screen_overwrite:
screen_overwrite.append(code)
# 미체결에 있는 종목들
for order_number in self.not_account_stock_dict.keys():
code = self.not_account_stock_dict[order_number]['종목코드']
if code not in screen_overwrite:
screen_overwrite.append(code)
# 포트폴리오에 있는 종목들
for code in self.portfolio_stock_dict.keys():
if code not in screen_overwrite:
screen_overwrite.append(code)
# 스크린 번호 할당
cnt = 0
for code in screen_overwrite:
temp_screen = int(self.screen_real_stock)
meme_screen = int(self.screen_meme_stock)
if (cnt % 50) == 0:
temp_screen += 1
self.screen_real_stock = str(temp_screen)
if (cnt % 50) == 0:
meme_screen += 1
self.screen_meme_stock = str(meme_screen)
if code in self.portfolio_stock_dict.keys():
self.portfolio_stock_dict[code].update({"스크린번호": str(self.screen_real_stock)})
self.portfolio_stock_dict[code].update({"주문용스크린번호": str(self.screen_meme_stock)})
elif code not in self.portfolio_stock_dict.keys():
self.portfolio_stock_dict.update({code: {"스크린번호": str(self.screen_real_stock),
"주문용스크린번호": str(self.screen_meme_stock)}})
cnt += 1
# self.logging.logger.debug(self.portfolio_stock_dict)
def real_event_slot(self):
# self.logging.logger.debug("real_event_slot")
self.OnReceiveRealData.connect(self.trdata_slot) # 전일대비 값을 불러오기 위함
self.OnReceiveRealData.connect(self.realdata_slot) # 실시간 이벤트 연결
self.OnReceiveChejanData.connect(self.chejan_slot) # 종목 주문체결 관련한 이벤트
# def timing_slot(self, sCode, sRealType, sRealData):
# self.logging.logger.debug(sRealType)
#
def realdata_slot(self, sCode, sRealType, sRealData):
# self.logging.logger.debug("realdata_slot")
# fid = self.realType.REALTYPE[sRealType]['장운영구분']
# value = self.dynamicCall("GetCommRealData(QString, int)", sCode, fid)
self.logging.logger.debug(sRealType)
if sRealType == "장시작시간":
self.logging.logger.debug("장시작시간")
fid = self.realType.REALTYPE[sRealType]['장운영구분']
# 0 : 장시작전, 2 : 장종료전(20분), 3: 장시작, 4or8 : 장종료(30분), 9 : 장마감
# 9 : 시간외 종료, a : 시간외종가매매 시작, b : 시간외종가매매 종료료
value = self.dynamicCall("GetCommRealData(QString, int)", sCode, fid)
if value == '0':
self.logging.logger.debug("장 시작 전")
elif value == '3':
self.logging.logger.debug("장 시작")
# 장 시작시간 코드
elif value == '2':
self.logging.logger.debug("장 종료, 동시호가로 넘어감")
elif value == '4':
self.logging.logger.debug("3시 30분 장 종료")
for code in self.portfolio_stock_dict.keys():
self.dynamicCall("SetRealRemove(QString, QString)",
self.portfolio_stock_dict[code]['스크린번호'], code)
QTest.qWait(5000)
self.file_delete()
self.calculator_fnc()
sys.exit()
elif sRealType == "주식체결":
# self.logging.logger.debug("주식체결")
a = self.dynamicCall("GetCommRealData(QString, int)",
sCode, self.realType.REALTYPE[sRealType]['체결시간']) # 출력 HHMMSS
b = self.dynamicCall("GetCommRealData(QString, int)",
sCode, self.realType.REALTYPE[sRealType]['현재가']) # 출력 +(-)2520
b = abs(int(b))
c = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['전일대비']) # 출력 : +(-)2520
c = abs(int(c))
d = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['등락율']) # 출력 : +(-)12.98
d = float(d)
e = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['(최우선)매도호가']) # 출력 : +(-)2520
e = abs(int(e))
f = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['(최우선)매수호가']) # 출력 : +(-)2515
f = abs(int(f))
g = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['거래량']) # 출력 : +240124 매수일때, -2034 매도일 때
g = abs(int(g))
h = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['누적거래량']) # 출력 : 240124
h = abs(int(h))
i = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['고가']) # 출력 : +(-)2530
i = abs(int(i))
j = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['시가']) # 출력 : +(-)2530
j = abs(int(j))
k = self.dynamicCall("GetCommRealData(QString, int)", sCode,
self.realType.REALTYPE[sRealType]['저가']) # 출력 : +(-)2530
k = abs(int(k))
# self.logging.logger.debug("각종 선언")
if sCode not in self.portfolio_stock_dict:
self.portfolio_stock_dict.update({sCode: {}})
self.portfolio_stock_dict[sCode].update({"체결시간": a})
self.portfolio_stock_dict[sCode].update({"현재가": b})
self.portfolio_stock_dict[sCode].update({"전일대비": c})
self.portfolio_stock_dict[sCode].update({"등락율": d}) # float형
self.portfolio_stock_dict[sCode].update({"(최우선)매도호가": e})
self.portfolio_stock_dict[sCode].update({"(최우선)매수호가": f})
self.portfolio_stock_dict[sCode].update({"거래량": g})
self.portfolio_stock_dict[sCode].update({"누적거래량": h})
self.portfolio_stock_dict[sCode].update({"고가": i})
self.portfolio_stock_dict[sCode].update({"시가": j})
self.portfolio_stock_dict[sCode].update({"저가": k})
# self.logging.logger.debug("self.portfolio_stock_dict업데이트")
if sCode in self.account_stock_dict.keys() and sCode not in self.jango_dict.keys():
asd = self.account_stock_dict[sCode]
meme_rate = (b - asd['매입가']) / asd['매입가'] * 100
# meme_rate = ((현재가-매입가) / 매입가) * 100
# 수익률인듯
if asd['매매가능수량'] > 0 and (meme_rate > 5 or meme_rate < -5):
# 수익률이 +- 5% 이상일때
order_success = self.dynamicCall(
"SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
["신규매도", self.portfolio_stock_dict[sCode]["주문용스크린번호"], self.account_num, 2,
sCode, asd['매매가능수량'], 0, self.realType.SENDTYPE['거래구분']['시장가'], ""]
)
# self.logging.logger.debug(2)
# 시장가 매도
# SendOrder는 1초에 5회만 주문 가능하며, 그 이상 주문요청하면 에러 -308 리턴
# 모의투자에서는 지정가 주문과 시장가 주문만 가능
if order_success == 0:
self.logging.logger.debug("매도주문 전달 성공")
del self.account_stock_dict[sCode]
else:
self.logging.logger.debug("매도주문 전달 실패")
elif sCode in self.jango_dict.keys():
jd = self.jango_dict[sCode]
meme_rate = (b - jd['매입단가']) / jd['매입단가'] * 100
if jd['주문가능수량'] > 0 and (meme_rate > 5 or meme_rate < -5):
order_success = self.dynamicCall(
"SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
["신규매도", self.portfolio_stock_dict[sCode]["주문용스크린번호"], self.account_num, 2, sCode, jd['주문가능수량'],
0, self.realType.SENDTYPE['거래구분']['시장가'], ""]
)
if order_success == 0:
self.logging.logger.debug("매도주문 전달 성공")
else:
self.logging.logger.debug("매도주문 전달 실패")
elif d > 2.0 and sCode not in self.jango_dict:
self.logging.logger.debug("매수조건 통과 %s " % sCode)
result = (self.use_money * 0.1) / e
quantity = int(result)
order_success = self.dynamicCall(
"SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
["신규매수", self.portfolio_stock_dict[sCode]["주문용스크린번호"], self.account_num, 1, sCode, quantity, e,
self.realType.SENDTYPE['거래구분']['지정가'], ""]
)
if order_success == 0:
self.logging.logger.debug("매수주문 전달 성공")
else:
self.logging.logger.debug("매수주문 전달 실패")
# self.logging.logger.debug("뭐지")
not_meme_list = list(self.not_account_stock_dict) # 미체결종목
for order_num in not_meme_list:
code = self.not_account_stock_dict[order_num]["종목코드"]
meme_price = self.not_account_stock_dict[order_num]['주문가격']
not_quantity = self.not_account_stock_dict[order_num]['미체결수량']
order_gubun = self.not_account_stock_dict[order_num]['주문구분']
if order_gubun == "매수" and not_quantity > 0 and e > meme_price:
order_success = self.dynamicCall(
"SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
["매수취소", self.portfolio_stock_dict[sCode]["주문용스크린번호"], self.account_num, 3, code, 0, 0,
self.realType.SENDTYPE['거래구분']['지정가'], order_num]
)
if order_success == 0:
self.logging.logger.debug("매수취소 전달 성공")
else:
self.logging.logger.debug("매수취소 전달 실패")
elif not_quantity == 0:
del self.not_account_stock_dict[order_num]
# sRealType == "주식체결" 마지막부분
# self.market_start()
# 여기가 이프 닫힘부분
# self.logging.logger.debug("주식체결 여기까지")
def chejan_slot(self, sGubun, nItemCnt, sFidList):
# self.logging.logger.debug("chejan_slot")
# 체결잔고
if int(sGubun) == 0: # 주문체결
account_num = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['계좌번호'])
sCode = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['종목코드'])[1:]
# A[장내주식], J[ELW종목], Q[ETN종목] 과 같이 A032900 형태로 나와서 032900 만 출력하기 위해 [1:]을 붙임
stock_name = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['종목명'])
stock_name = stock_name.strip()
origin_order_number = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['원주문번호']) # 출력 : defaluse : "000000"
order_number = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['주문번호']) # 출럭: 0115061 마지막 주문번호
# 신규매수/매도시엔 원주문번호가 000000이지만 정정주문시에 주문번호가 원주문번호가 되고,
# 정정주문의 주문번호가 주문번호가 된다.
order_status = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['주문상태']) # 출력: 접수, 확인, 체결
order_quan = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문수량']) # 출력 : 3
order_quan = int(order_quan)
order_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문가격']) # 출력: 21000
order_price = int(order_price)
not_chegual_quan = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['미체결수량']) # 출력: 15, default: 0
not_chegual_quan = int(not_chegual_quan)
order_gubun = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문구분']) # 출력: -매도, +매수
order_gubun = order_gubun.strip().lstrip('+').lstrip('-')
chegual_time_str = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['주문/체결시간']) # 출력: '151028'
chegual_price = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['체결가']) # 출력: 2110 default : ''
if chegual_price == '':
chegual_price = 0
else:
chegual_price = int(chegual_price)
chegual_quantity = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['체결량']) # 출력: 5 default : ''
if chegual_quantity == '':
chegual_quantity = 0
else:
chegual_quantity = int(chegual_quantity)
current_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['현재가']) # 출력: -6000
current_price = abs(int(current_price))
first_sell_price = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['(최우선)매도호가']) # 출력: -6010
first_sell_price = abs(int(first_sell_price))
first_buy_price = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['주문체결']['(최우선)매수호가']) # 출력: -6000
first_buy_price = abs(int(first_buy_price))
######## 새로 들어온 주문이면 주문번호 할당
if order_number not in self.not_account_stock_dict.keys():
self.not_account_stock_dict.update({order_number: {}})
self.not_account_stock_dict[order_number].update({"종목코드": sCode})
self.not_account_stock_dict[order_number].update({"주문번호": order_number})
self.not_account_stock_dict[order_number].update({"종목명": stock_name})
self.not_account_stock_dict[order_number].update({"주문상태": order_status})
self.not_account_stock_dict[order_number].update({"주문수량": order_quan})
self.not_account_stock_dict[order_number].update({"주문가격": order_price})
self.not_account_stock_dict[order_number].update({"미체결수량": not_chegual_quan})
self.not_account_stock_dict[order_number].update({"원주문번호": origin_order_number})
self.not_account_stock_dict[order_number].update({"주문구분": order_gubun})
self.not_account_stock_dict[order_number].update({"주문/체결시간": chegual_time_str})
self.not_account_stock_dict[order_number].update({"체결가": chegual_price})
self.not_account_stock_dict[order_number].update({"체결량": chegual_quantity})
self.not_account_stock_dict[order_number].update({"현재가": current_price})
self.not_account_stock_dict[order_number].update({"(최우선)매도호가": first_sell_price})
self.not_account_stock_dict[order_number].update({"(최우선)매수호가": first_buy_price})
elif int(sGubun) == 1: # 잔고
account_num = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['계좌번호'])
sCode = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['종목코드'])[1:]
stock_name = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['종목명'])
stock_name = stock_name.strip()
current_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['현재가'])
current_price = abs(int(current_price))
stock_quan = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['보유수량'])
stock_quan = int(stock_quan)
like_quan = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['주문가능수량'])
like_quan = int(like_quan)
buy_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['매입단가'])
buy_price = abs(int(buy_price))
total_buy_price = self.dynamicCall("GetChejanData(int)",
self.realType.REALTYPE['잔고']['총매입가']) # 계좌에 있는 종목의 총매입가
total_buy_price = int(total_buy_price)
meme_gubun = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['매도매수구분'])
meme_gubun = self.realType.REALTYPE['매도수구분'][meme_gubun]
first_sell_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['(최우선)매도호가'])
first_sell_price = abs(int(first_sell_price))
first_buy_price = self.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['(최우선)매수호가'])
first_buy_price = abs(int(first_buy_price))
if sCode not in self.jango_dict.keys():
self.jango_dict.update({sCode: {}})
self.jango_dict[sCode].update({"현재가": current_price})
self.jango_dict[sCode].update({"종목코드": sCode})
self.jango_dict[sCode].update({"종목명": stock_name})
self.jango_dict[sCode].update({"보유수량": stock_quan})
self.jango_dict[sCode].update({"주문가능수량": like_quan})
self.jango_dict[sCode].update({"매입단가": buy_price})
self.jango_dict[sCode].update({"총매입가": total_buy_price})
self.jango_dict[sCode].update({"매도매수구분": meme_gubun})
self.jango_dict[sCode].update({"(최우선)매도호가": first_sell_price})
self.jango_dict[sCode].update({"(최우선)매수호가": first_buy_price})
if stock_quan == 0:
del self.jango_dict[sCode]
def msg_slot(self, sScrNo, sRQName, sTrCode, msg):
self.logging.logger.debug("스크린: %s, 요청이름: %s, tr코드: %s --- %s" % (sScrNo, sRQName, sTrCode, msg))
def file_delete(self):
# self.logging.logger.debug("file_delete")
if os.path.isfile("files/condition_stock.txt"):
os.remove("files/condition_stock.txt")
def market_start(self):
# 장 시작시 실행될 함수
# 장시작시간부터 실시간 거래량 * 거래금액 으로 상위 종목들을 추려낸 후
# 전일 대비 5% 상승시 매수, 매수시점보다 5% 하락시 매도, 5% 상승시 매도
self.logging.logger.debug("나만의 조건 처리")
# def getTemp(self, code):
# # self.logging.logger.debug("getTemp")
# self.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)
# # self.logging.logger.debug("1")
# self.dynamicCall("CommRqData(QString, QString, int, QString)", "전일대비", "opt10001", "0", self.screen_ing_cal)
#
# self.logging.logger.debug("getTemp code = %s" % (code))
# # self.logging.logger.debug("2")
# # self.ing_event_loop.exec_()
# # self.logging.logger.debug("3")
# opt10001을 여기서 불러오자
# SetInputValue 후 GetComRqData를 하는데 여기서 sRQName 입력해서 trdata_slot에서 불러와야함
# 이걸 진행하고 나면 trdata_slot에서 GetCommData를 통해서 값을 가져올 수 있음
# self.dynamicCall("SetInputValue(QString, QString)", )
# 다중 주석 단축키 Ctrl + /
조금 더 완성도 높은 프로그램을 원하신다면 책을 참고하시면 좋을 것 같습니다.
감사합니다.
이 카테고리 게시글들은 아래 책을 따라하는 후기로, 자세한 내용은 아래 책과 저자의 유튜브를 참고하시고, 예제소스는 저자의 깃헙에서 확인하실 수 있습니다.
책 정보 : https://book.naver.com/bookdb/book_detail.nhn?bid=16330702
손가락 하나 까딱하지 않는 주식 거래 시스템 구축(위키북스 프로그래밍 & 프랙티스 시리즈 23)
매일 주식만 바라보던 일상에서 해방되어 가족 또는 친구들과 행복한 시간을 보내길 바랍니다!24시간 자동으로 주식을 분석하고 거래하는 시스템을 구축하는 방법을 배우고 나머지 시간은 더 ��
book.naver.com
저자 유튜브 : www.youtube.com/watch?v=1lHzaihEb48&list=PLDtzZPtOGenaSknTbsb6x6L39V0VPz_rS&index=2&t=0s
( 위 유튜브는 이 책을 설명해주는 재생목록의 첫 번째 영상입니다.)
저자 Github : https://github.com/programgarden
programgarden - Overview
책 : 24시간 운영 가능한 주식투자 시스템 구축하기[파이썬으로 만드는 자동 트레이딩 시스템] 유튜브: 프로그램 동산 학습코드 자료입니다. - programgarden
github.com
예제 소스는 저자의 깃허브에서 확인하실 수 있습니다.
위 3개의 링크는 매 게시글마다 올라갈 예정입니다.