이전 이야기
2020/06/06 - [Python/Stock] - Python으로 보통주, 우선주 괴리율을 이용한 투자전략 - 1
지난글에는 우선주-보통주 매핑을 구했습니다.
이를 바탕으로 가격정보를 얻어와 괴리율을 구해보겠습니다.
바로 코드로 보면 다음과 같습니다.
def calc_disparity_rate_signal(self):
print('calc_disparity_rate_signal start')
self.get_all_codes_names()
for preferred_stock_name in self.stock_map.keys():
time.sleep(1.5)
try:
common_stock_name = self.stock_map[preferred_stock_name]
preferred_stock_code = self.code_dict[preferred_stock_name]
common_stock_code = self.code_dict[common_stock_name]
preferred_stock_df = self.get_price_data(preferred_stock_code, '20200604')
common_stock_df = self.get_price_data(common_stock_code, '20200604')
common_stock_df = common_stock_df.rename(
columns={'open': 'open_common', 'high': 'high_common', 'low': 'low_common', 'close': 'close_common',
'volume': 'volume_common'})
concat_df = pd.concat([preferred_stock_df, common_stock_df], axis=1)
concat_df['disparity_rate'] = (concat_df['close_common'] - concat_df['open']) / concat_df['open'] * 100
concat_df['close_ma200'] = concat_df['close'].rolling(window=200).mean()
concat_df['close_common_ma20'] = concat_df['close_common'].rolling(window=20).mean()
concat_df['disparity_rate_ma200'] = concat_df['disparity_rate'].rolling(window=200).mean()
concat_df['signal'] = np.where((concat_df['disparity_rate'] > concat_df['disparity_rate_ma200']) & (
concat_df['close'] < concat_df['close_ma200']) & (concat_df['close_common'] > concat_df['close_common_ma20']), 1, 0)
buy_signal = concat_df[-1:]['signal'].values[0]
if buy_signal:
print(preferred_stock_name, common_stock_name)
print(concat_df[-1:]['signal'].values[0], end='\n\n')
concat_df.to_excel(preferred_stock_name+'.xlsx')
except Exception as e:
print('error on ', preferred_stock_name)
print(traceback.format_exc())
print('ends')
이름을 대충 알아볼 수 있도록 지었는데 이걸 보는 누군가도 그렇게 느낄지는 모르겠습니다.
아무튼 이 전략은 추출한 111건의 우선주-보통주 매핑 중 다음 조건에 해당하는 기업을 추출합니다.
1. 괴리율이라는 수치를 대상으로 200일 이동평균을 구하고 현재 괴리율이 이 값보다 큰 경우
> 보통주가 많이 올라서 차이가 발생했다고 가정
2. 우선주 가격이 200일 이동평균보다 작을 때
> 가격 하락 상태
3. 보통주 가격이 20일 이동평균보다 클 때
> 보통주가 상승 추세일 때
전체 코드는 다음과 같습니다.
지난번에 말씀드린 것처럼 기본 베이스가 되는 코드들은 class101 수업에서 사용한 코드이고
우선주 추출 및 위의 calc_disparity_rate_signal 함수만 제가 만들었습니다.
import sys
import traceback
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
import re
from datetime import date, timedelta
import time
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class Kiwoom(QAxWidget): # QAxWidget 클래스로부터 dynamicCall, setControl, OnEventConnect 를 상속받음
def __init__(self):
super().__init__()
self._create_kiwoom_instance() # 키움증권 OpenAPI 를 정상적으로 사용할 수 있도록 처리함
self._set_signal_slots() # 이벤트 처리메소드 등록
self.code_dict = {} # key: 종목명 - value: 종목코드 매핑
self.stock_map = {} # key: 우선주 - value: 보통주 매핑
self.day = (date.today() - timedelta(1)).strftime('%Y%m%d')
self.stock_info = pd.DataFrame(columns=("Open","High","Low","Close","Volume"))
def _create_kiwoom_instance(self):
self.setControl("KHOPENAPI.KHOpenAPICtrl.1") # 이거 대신 obj = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1") 가능
def _set_signal_slots(self):
self.OnEventConnect.connect(self._event_connect) # 콜백함수 등록
self.OnReceiveTrData.connect(self._receive_tr_data)
def _receive_tr_data(self, screen_no, rqname, trcode, record_name, next, unused1, unused2, unused3, unused4):
# print("_receive_tr_data!!!")
if next == '2':
self.remained_data = True
else:
self.remained_data = False
if rqname == "opt10081_req":
self._opt10081(rqname, trcode)
elif rqname == "opw00001_req":
print("opw00001_req!!!")
print("Get an de_deposit!!!")
# self._opw00001(rqname, trcode)
elif rqname == "opw00018_req":
print("opw00018_req!!!")
print("Get the possessed item !!!!")
# self._opw00018(rqname, trcode)
elif rqname == "opt10074_req":
print("opt10074_req!!!")
print("Get the profit")
# self._opt10074(rqname, trcode)
elif rqname == "opw00015_req":
print("opw00015_req!!!")
print("deal list!!!!")
# self._opw00015(rqname, trcode)
elif rqname == "opt10076_req":
print("opt10076_req")
print("chegyul list!!!!")
# self._opt10076(rqname, trcode)
elif rqname == "opt10073_req":
print("opt10073_req")
print("Get today profit !!!!")
# self._opt10073(rqname, trcode)
elif rqname == "opt10080_req":
print("opt10080_req!!!")
print("Get an de_deposit!!!")
# self._opt10080(rqname, trcode)
try:
self.tr_event_loop.exit()
except AttributeError:
pass
def _event_connect(self, err_code):
if err_code == 0:
print("로그인 성공")
else:
print("로그인 에러코드 : " + str(err_code))
self.login_event_loop.exit()
def comm_connect(self):
self.dynamicCall("CommConnect()") # 로그인창 띄우기
self.login_event_loop = QEventLoop() # 이벤트 루프 생성
self.login_event_loop.exec_() # 프로그램 흐름을 일시중지하고 이벤트만 처리할 수 있는 상태로 만듬
# tr 입력값을 서버 통신 전에 입력
# ex. SetInputValue("종목코드","000660")
def set_input_value(self, id, value):
self.dynamicCall("SetInputValue(QString,QString)", id, value)
def get_all_codes_names(self):
ret = self.dynamicCall("GetCodeListByMarket(QString)", ["0"]) # 맨뒤 인자는 시장구분, 모든 코드들을 가져옴
kospi_code_list = ret.split(';')
for code in kospi_code_list:
name = self.dynamicCall("GetMasterCodeName(QString)", [code]) # 맨뒤는 종목코드, 코드에 따른 종목명을 가져옴
regex = re.compile('[우][a-zA-Z]{0,1}$')
match = regex.search(name)
self.code_dict[name] = code
if match:
# 우선주 이름
preferred_stock_name = name
# 우선주에서 보통주 이름 구하기
common_stock_name = re.sub('\d{0,1}[우]{0,1}[a-zA-Z]{0,1}$', '', name)
# 우선주 이름 <> 보통주 매핑
self.stock_map[preferred_stock_name] = common_stock_name
# 네이밍 검증 전 출력
print(len(self.stock_map), self.stock_map)
# 네이밍 검증, '보통주 + 우' 형태가 아닌 경우는 걸러냄
del_list = []
for code in self.stock_map.keys():
common_stock_name = self.stock_map[code]
if common_stock_name not in self.code_dict.keys():
del_list.append(code)
for del_code in del_list:
print( self.stock_map[del_code])
del self.stock_map[del_code]
# 네이밍 검증 이후 출력
print(len(self.stock_map), self.stock_map)
# 사용방법
# code: 종목코드(ex. '005930' )
# start : 기준일자. (ex. '20200424') => 20200424 일자 까지의 모든 open, high, low, close, volume 데이터 출력
def get_price_data(self, code, start):
print('get_price_data!', code, start)
self.ohlcv = {'date': [], 'open': [], 'high': [], 'low': [], 'close': [], 'volume': []}
self.set_input_value("종목코드", code)
self.set_input_value("기준일자", start)
self.set_input_value("수정주가구분", 1)
self.comm_rq_data("opt10081_req", "opt10081", 0, "0101")
# 이 밑에는 한번만 가져오는게 아니고 싹다 가져오는거다.
while self.remained_data == True:
# time.sleep(TR_REQ_TIME_INTERVAL)
self.set_input_value("종목코드", code)
self.set_input_value("기준일자", start)
self.set_input_value("수정주가구분", 1)
self.comm_rq_data("opt10081_req", "opt10081", 2, "0101")
time.sleep(1)
# data 비어있는 경우
if self.ohlcv['date'][0] == '':
return []
if self.ohlcv['date'] == '':
return []
df = pd.DataFrame(self.ohlcv, columns=['open', 'high', 'low', 'close', 'volume'], index=self.ohlcv['date'])
# print(df)
return df
def _get_repeat_cnt(self, trcode, rqname):
ret = self.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
return ret
def _opt10081(self, rqname, trcode):
# print('_opt10081')
# 몇번 반복 실행 할지 설정
ohlcv_cnt = self._get_repeat_cnt(trcode, rqname)
# 하나의 row씩 append
for i in range(ohlcv_cnt):
date = self._get_comm_data(trcode, rqname, i, "일자")
open = self._get_comm_data(trcode, rqname, i, "시가")
high = self._get_comm_data(trcode, rqname, i, "고가")
low = self._get_comm_data(trcode, rqname, i, "저가")
close = self._get_comm_data(trcode, rqname, i, "현재가")
volume = self._get_comm_data(trcode, rqname, i, "거래량")
self.ohlcv['date'].append(date)
self.ohlcv['open'].append(int(open))
self.ohlcv['high'].append(int(high))
self.ohlcv['low'].append(int(low))
self.ohlcv['close'].append(int(close))
self.ohlcv['volume'].append(int(volume))
def _get_comm_data(self, code, field_name, index, item_name):
ret = self.dynamicCall("GetCommData(QString, QString, int, QString", code, field_name, index, item_name)
return ret.strip()
# tr을 서버에 전송한다
def comm_rq_data(self, rqname, trcode, next, screen_no):
self.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, next, screen_no)
time.sleep(1)
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def calc_disparity_rate_signal(self):
print('calc_disparity_rate_signal start')
for preferred_stock_name in self.stock_map.keys():
time.sleep(1.5)
try:
common_stock_name = self.stock_map[preferred_stock_name]
preferred_stock_code = self.code_dict[preferred_stock_name]
common_stock_code = self.code_dict[common_stock_name]
preferred_stock_df = self.get_price_data(preferred_stock_code, '20200604')
common_stock_df = self.get_price_data(common_stock_code, '20200604')
common_stock_df = common_stock_df.rename(
columns={'open': 'open_common', 'high': 'high_common', 'low': 'low_common', 'close': 'close_common',
'volume': 'volume_common'})
concat_df = pd.concat([preferred_stock_df, common_stock_df], axis=1)
concat_df['disparity_rate'] = (concat_df['close_common'] - concat_df['open']) / concat_df['open'] * 100
concat_df['close_ma200'] = concat_df['close'].rolling(window=200).mean()
concat_df['close_common_ma20'] = concat_df['close_common'].rolling(window=20).mean()
concat_df['disparity_rate_ma200'] = concat_df['disparity_rate'].rolling(window=200).mean()
concat_df['signal'] = np.where((concat_df['disparity_rate'] > concat_df['disparity_rate_ma200']) & (
concat_df['close'] < concat_df['close_ma200']) & (concat_df['close_common'] > concat_df['close_common_ma20']), 1, 0)
buy_signal = concat_df[-1:]['signal'].values[0]
if buy_signal:
print(preferred_stock_name, common_stock_name)
print(concat_df[-1:]['signal'].values[0], end='\n\n')
concat_df.to_excel(preferred_stock_name+'.xlsx')
except Exception as e:
print('error on ', preferred_stock_name)
print(traceback.format_exc())
print('ends')
if __name__ == "__main__":
app = QApplication(sys.argv)
kiwoom = Kiwoom()
kiwoom.comm_connect()
# kiwoom.get_price_data('005930', '20200603')
kiwoom.get_all_codes_names()
kiwoom.calc_disparity_rate_signal()
sys.exit(app.exec_())
문제는 이 코드를 실행하다보면 에러가 납니다.
아무리 sleep을 넣어도 Too many request 에러가 납니다.
너무 많은 요청을 보낸다는데 그렇게까지 많은지는 모르겠습니다..
따라서 전체가 다 돌아가지는 않고 중간에 에러가 납니다.
이 문제는 Kiwoom 사용법에 익숙해지면 해결할 수 있을 것 같아 우선은 문제인 상태로 올립니다.
코드를 실행하면 위 매수조건에 해당하는 기업들(우선주명)이 나옵니다
하지만 재무정보를 보면 다 사고 싶은 기업들은 아닙니다.
몇 개만 추려보면 다음과 같았습니다. (개인적인 견해라 주식 추천이 아닙니다. 저만 살겁니다.)
추가로 코드를 작성한 시점은 06-05이라 06-04 종가를 기준으로 추출했고
첨부한 캡쳐는 06-06에 한거라 06-05 종가입니다.
1. 대신증권우
2. 미래에셋대우우
3. 삼성화재우
4. 현대차우
삼성화재를 제외하면 거래량도 좀 있고 배당도 좋고 실적도 나쁘지 않은 기업만 추려봤습니다.
훌륭한 배당 및 시세차익을 기대해볼 수 있는 우선주-보통주 괴리율 투자 전략이었습니다.
가정대로 실제로 돈을 벌지는 두고 봐야겠습니다!
제 블로그에 방문해주셔서 감사합니다.
좋아요, 댓글은 제가 글을 쓰는데 큰 힘이 됩니다.
아래는 쿠팡 링크이고 쿠팡 파트너스 활동을 통해 일정 수익이 발생할 수 있음을 알려 드립니다.
'파이썬 > 주식' 카테고리의 다른 글
한국신용평가 등급별금리 크롤링 python bs4 (0) | 2020.11.07 |
---|---|
Python으로 S-RIM 계산 적정주가 계산하기 (9) | 2020.06.23 |
Python으로 보통주, 우선주 괴리율을 이용한 투자전략 - 1 (0) | 2020.06.06 |
Python으로 주식 알파 찾기, Open-to-Close 전략 수행 With Backtrader 백테스팅 (0) | 2020.01.16 |
Python,Backtrader 다중 데이터 백테스팅 Python sqlite3 to backtrader / Mutliple Data Feeds / Pandas DataFrame to Backtrader (5) | 2020.01.07 |
댓글