목 차
1. 들어가며
2. 사전설명
1) RSI 모수는 700개 종가 활용
2) CommRqData 1초당 2회 사용
3) 사용 TR 목록은 opc10002
3. 코드 설명
< 1차 계산(산술평균) >
< 2차 계산(지수가중평균) >
4. 전체코드
5. 마치며
1. 들어가며
지난 글에서는 RSI 개념 및 계산하는 방법을 알아보았다. 상승분 평균(au)과 하락분 평균(ad)의 상대적 강도를 의미하며, 최초 기준값 찾기(1차 계산) 및 지수가중평균(2차 계산)을 계산하는 방법을 설명하였다.
이번 글에서는 RSI 계산하는 방법을 코드로 구현해보자.
RSI 계산을 위한 마지막 설명이다.
2. 사전설명
1) RSI 모수는 700개 종가 활용
RSI 모수는 700개의 종가를 활용한다. 키움증권 영웅문G에서 RSI 계산을 위한 1분봉을 다운받아 보면 630~680개 정도의 종가를 받을 수 있다. 사용자에게 제공해주는 데이터가 일정하지 않다는 점이 마음에 걸렸다. 결론은 연속조회를 통해 700개의 종가를 가져올 것이다.
2) CommRqData 1초당 2회 사용
이번부터는 글을 쓸 때 1초당 CommRqData의 사용횟수를 체크를 할 것이다. 최초 기준값(1차 계산)을 위해 첫번째 사용할 것이고, 지수가중평균(2차 계산)을 위해 두번째 사용할 것이다.
3) 사용 TR 목록은 opc10002
사용할 TR 목록은 opc10002(해외선물옵션분차트조회)이다.
WKOA Studio에서 관련 내용을 확인하면 분 차트 조회를 위해 "종목코드"와 "시간단위"를 입력(input)한다.
수신받는 데이터(output)는 현재가 1개를 활용한다.
3. 코드 설명
opc10002로 종가 데이터 700개를 받아오고, 최초 기준값(au / ad)을 설정하고, 지수가중평균을 통해 RSI 값을 구한다.
반복 활용되는 모듈과 로그인 설명은 생략한다.
1줄~33줄 : 임포트한 모듈과 로그인 코드 내역이다.
20줄 : 56줄~86줄에서 받는 1분봉 종가데이터를 담는 리스트이며, 초기 값은 빈칸으로 설정한다.
21줄 : 81줄~84줄, RSI 최초값 설정을 위한 1차 계산을 위해 15개 종가를 담는 리스트이다.
22줄 : 85줄~94줄, 기준값 설정을 위해 종가 간의 차이를 담는 리스트이다.
24줄 : 68줄에서 연속조회로 받아온 100개 데이터를 받아오려면, for문에는 101값이 들어가야 한다.(101로 설정)
25줄 : 96줄에서 데이터의 지수가중평균을 위해 for문의 range의 끝값을 위해 700으로 설정한다.
36줄~41줄 : RSI 기준값(산술평균)을 구하기 위해 키움증권 서버에 종목코드와 시간단위를 입력/요청한다.
43줄~48줄 : RSI의 지수가중평균을 구하기 위해 종목코드와 시간단위를 입력/요청한다.
51줄~58줄 : RSI 기준값(산술평균)을 받아오는 함수이다.
52줄 : WKOA Studio에 의하면 GetRepeatCnt 함수는 페이지에 있는 데이터 갯수를 반환한다. (여기서는 숫자형 600이다.)
55줄 : 36줄의 함수를 실행하면, 키움증권 내 이벤트가 발생하는데 13줄의 "연결"을 통해 종가 600개를 제공받는다.
56줄 : 받아온 600개 데이터를 20줄의 self.price_rsi_total 에 빈 리스트에 추가해준다.(append)
60줄 : 연속조회(prenext)에 빈칸이 아니면 (= 내용이 있으면)
61줄 : 50줄에서 전달하는 prenext를 self.prenext에 넣는다.
63줄 : 43줄의 rq_data_opc10002_1 함수를 실행한다. 46줄의 연속조회에는 "self.prenext"를 넣는다.
46줄 : self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002_1", "opc10002", self.prenext, "1012")
66줄 : 수신받는 데이터(rqname)가 opc_10002_1이면,
68줄 : for문을 통해 100개 종가데이터를 받아온다. 이때 self.prenext_price_date는 24줄에서 101로 설정하였는데, for문의 range의 끝값을 101로 설정하면, for ~ range(101)이면, 0~100번까지 적용된다.
70줄 : 20줄의 self.price_rsi_total에 종가 데이터를 넣는다.
74줄 : 이 글의 핵심이다. 76줄의 rsi_searching 함수를 실행한다. 20줄의 self.price.price_rsi_total에 700개 종가 데이터를 넣었고, for문을 통해 기준값이 산출되면 첫번째 au / ad를 그 다음 au / ad에 적용하면 된다.
76줄 : 여기서부터는 설명이 조금 길어진다. 천천히 코드를 확인해보자.
56줄에서 현재가 ~ 600분의 종가 600개, 70줄에서 601분~700분의 종가 100개가 20줄의 self.price_rsi_total에 들어있다. 여기서 주목할 점은 현재가 ~ 701분전의 종가가 시간 반대순으로 들어있다. 이유는? 키움에서는 현재시간을 기준으로 과거로 데이터를 반환해준다. 즉, 최근의 데이터부터 제공해준다는 것이다.
77줄 : 20줄의 self.price_rsi_total에 시간 역순으로 제공된 데이터를 시간 순으로 정렬할 필요가 있다. 이유는? 과거부터 가중평균된 au / ad가 현재의 au / ad에 영향을 미치게 때문이다. 즉, 시간 역순에서 시간순으로 데이터를 정렬한다.
리스트 내의 요소들을 역순으로 정렬해 주는 명령어는 [::1]이다. a = [1, 2, 3]인데, a[::1]을 붙이면, [3, 2, 1]로 반환된다.
역순으로 정렬된 리스트를 77줄의 self.price_rsi_total_final로 정렬해준다. (700개 종가 데이터가 시간 순으로 정렬되었다)
1차 계산(산술평균)은 79줄~94줄에서 계산하며, 2차 계산(지수가중평균)은 96줄~119줄에서 계산한다.
< 1차 계산(산술평균) >
79줄~81줄 : 1차 계산(산술평균)을 위해 700개 데이터가 들어있는 self.price_rsi_total_final의 요소를 for문을 통해 self.close_price_rsi_change의 리스트에 넣는다.
83줄~84줄 : 종가 간 변화율(차이)를 계산하기 위해 self.close_price_rsi_change에서 종가를 2개씩 추출하여 차이를 계산한다.
85줄 : 각각 종가의 차이는 self.close_price_third의 리스트에 추가한다.(append)
87줄~88줄 : self.close_price_third 리스트 내 종가의 차이가 14개 들어있다.
87줄 : self.close_price_third 리스트에서 항목을 하나씩 추출하되, 0보다 작으면 0으로 표시하여 self.early_au1에 넣고, 0보다 크면 0보다 큰 값으로 self.earlt_au1 리스트에 넣는다.
88줄 : self.close_price_third 리스트 내 요소를 하나씩 추출하되, 0보다 작으면 0보다 작은값으로 표시하여 self.early_au1에 넣고, 0보다 크면 0으로 표기하여 self.earlt_au1 리스트에 넣는다.
90줄~92줄 : 상승분의 평균과 하락분의 평균을 구한다. 하락분의 평균은 abs(절대값)을 붙여 양수로 만든다.
이 글의 핵심이다. 여기서 구한 self.au7 및 self.ad7은 108줄과 109줄에서 활용한다.
94줄 : 메모리 절약을 위해 self.close_price_third의 리스트 내의 내역을 삭제한다.
< 2차 계산(지수가중평균) >
96줄 : 24줄의 self.prenext_price_date는 101로 설정한 이유가 여기에 있다. for ~ range(1, 700)의 내용이므로 1~699까지 699번 반복한다. 즉 연속조회를 통해 현재까지의 rsi를 구한다.
97줄~99줄 : "종가"와 "종가+1분"의 차이를 구해서, current_price_3에 넣는다.
101줄~103줄 : current_price_3(차이)가 양수이면 positive_price에 양의 값으로 넣고, negative_price에 0을 넣는다.
104줄~106줄 : current_price_3(차이)가 음수이면 positive_price에 0을 넣고, negative_price에 음의 값을 넣는다.
108줄~109줄 : 가중평균을 통해 au / ad를 구한다.
108줄 : 1차 계산에서 구한 au에 13을 곱하고 positive_price를 더한 후 14로 나눈다.
109줄 : 1차 계산에서 구한 ad에 13을 곱하고 negative_price를 더한 후 14로 나눈다.
111줄 : rs 값은 au8을 ad8로 나눈다.
113줄 : rsi 값을 구한다.
114줄 : rsi 값의 가독성 향상을 위해 소수 3자리에서 반올림한다.
116줄~117줄 : 2차 계산에서 산출된 au8와 ad8을 au7와 ad7에 넣는다. 향후 96줄의 for문에 의하여 au / ad 값이 과거 종가부터 계산되어 현재까지 빠르게 계산된다.
119줄 : 현재가의 current_rsi가 출력된다.
125줄 : 36줄의 함수(rq_data_opc10002)를 실행한다.
4. 전체코드
opc10002를 통해 RSI (상대강도지수)를 구하는 방법은 아래와 같다.
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
class btl_system():
def __init__(self):
self.kiwoom = QAxWidget("KFOPENAPI.KFOpenAPICtrl.1")
print("로그인 시작!")
self.kiwoom.OnEventConnect.connect(self.login_Connect)
self.kiwoom.OnReceiveTrData.connect(self.trdata_get)
self.kiwoom.dynamicCall("CommConnect(1)") # CommConnect() : 괄호 안에 자동(1)을 넣는다.
self.login_event_loop = QEventLoop() # from PyQt5.QtCore import * : qtcore가 임포트되어야 함
self.login_event_loop.exec_()
##### rsi의 현재 1분 데이터의 au/ad 구하기 #####
self.price_rsi_total = [] # 56~86줄 : 1분봉들의 종가데이터를 담는 리스트 모음
self.close_price_rsi_change = [] # 81~84줄 : 기준값(au/ad)를 세팅하기 위해, 최근 15개 값을 담는 리스트
self.close_price_third = [] # 85~94줄 : 기준값 15개의 각 종가들의 차이 모음 리스트
self.prenext_price_date = 101 # 68줄 : 추가 조회할 자료 / # for문에서의 끝자리에 해당하므로 100개를 추가하려면, 101을 넣어주면 됨
self.prenext_enging_price = 700 # 96줄 : 700개를 추출하기 위함
########## 로그인 관련 함수 ##########
def login_Connect(self, err_code):
if err_code == 0:
print('로그인 성공했습니다!')
self.login_event_loop.exit()
else:
print('로그인 실패했습니다!')
########## 키움서버에 TR 요청하는 함수 모음 ##########
def rq_data_opc10002(self, stock_code_num, time_unit):
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code_num)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "시간단위", time_unit)
self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002", "opc10002", "", "1012")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def rq_data_opc10002_1(self, stock_code_num, time_unit):
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code_num)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "시간단위", time_unit)
self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002_1", "opc10002", self.prenext, "1012")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
if rqname == "opc_10002":
getrepeatcnt = self.kiwoom.dynamicCall("GetRepeatCnt(QString,QString)", trcode, recordname)
for i in range(getrepeatcnt): # 현재가 → 오래된 값으로 출력된다.
self.current_price_rsi = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "현재가").strip())
self.price_rsi_total.append(self.current_price_rsi)
self.tr_event_loop.exit()
if prenext != "":
self.prenext = prenext
print("62줄")
btl.rq_data_opc10002_1("NQH23", "1")
time.sleep(1)
elif rqname == "opc_10002_1":
time.sleep(1)
for i in range(0, self.prenext_price_date):
self.current_price_rsi_1 = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002_1", "opc10002", i, "현재가").strip())
self.price_rsi_total.append(self.current_price_rsi_1)
self.tr_event_loop.exit()
btl.rsi_searching()
def rsi_searching(self):
self.price_rsi_total_final = self.price_rsi_total[::-1]
for i in range(0, 15):
current_price_recent = self.price_rsi_total_final[i]
self.close_price_rsi_change.append(current_price_recent)
for j in range(len(self.close_price_rsi_change) - 1):
self.close_price_second = self.close_price_rsi_change[j + 1] - self.close_price_rsi_change[j]
self.close_price_third.append(self.close_price_second)
self.early_au1 = [0 if i < 0 else i for i in self.close_price_third] # 음수를 0으로 표시
self.early_ad1 = [0 if i > 0 else i for i in self.close_price_third] # 양수를 0으로 표시
self.au7 = sum(self.early_au1) / len(self.early_au1)
self.ad7 = sum(self.early_ad1) / len(self.early_ad1)
self.ad7 = abs(self.ad7)
del self.close_price_third[:] # 향후 사용하지 않을 분봉 리스트는 초기화(삭제)하여 메모리 효율화 추진
for k in range(1, self.prenext_enging_price):
curren_price_rsi_1 = self.price_rsi_total_final[k]
curren_price_rsi_2 = self.price_rsi_total_final[k+1]
current_price_rsi_3 = curren_price_rsi_2 - curren_price_rsi_1
if current_price_rsi_3 >= 0:
positive_price = current_price_rsi_3
negative_price = 0
else:
positive_price = 0
negative_price = abs(current_price_rsi_3)
self.au8 = (self.au7 * 13 + positive_price) / 14
self.ad8 = (self.ad7 * 13 + negative_price) / 14
rs2 = self.au8 / self.ad8
self.rsi_second = rs2 / (1 + rs2) * 100
self.current_rsi = round(self.rsi_second, 2)
self.au7 = self.au8
self.ad7 = self.ad8
print(self.current_rsi)
if __name__ == "__main__":
app = QApplication(sys.argv)
btl = btl_system()
btl.rq_data_opc10002("NQH23", "1")
app.exec_()
5. 마치며
산술평균과 지수가중평균을 통해 현재시간의 RSI 값을 구하는 방법을 알아보았다. 여기서 RSI는 기준값 받기, 가중평균 계산을 위해 1초에 CommRqData를 2회 사용할 예정이다.
RSI가 30이하일 때 long, 70이상일 때 short으로 진입을 생각하되, 시뮬레이션을 돌릴 때 30 / 70의 적절성에 대해 좀더 고민해 보도록 하자.
'2. 해외선물 > 2-4. 해외선물 API (사용)' 카테고리의 다른 글
(키움증권 해외선물 OpenAPI-W) 진입을 위한 SendOrder 함수 (0) | 2023.02.06 |
---|---|
(키움증권 해외선물 OpenAPI-W) 상승/하락 추세 파악 (3) 추세에 맞는 진입 전략 (2) | 2023.02.05 |
(키움증권 해외선물 OpenAPI-W) 상승/하락 추세 파악 (2) 단기추세에서 양봉/음봉 구분을 통한 진입하는 방법 (2) | 2023.02.05 |
(키움증권 해외선물 OpenAPI-W) 상승/하락 추세 파악 (1) 양봉/음봉 개수 구하기 (0) | 2023.02.04 |
(키움증권 해외선물 OpenAPI-W) RSI 값 구하기 (2) RSI 개념 및 계산접근 방법 (0) | 2023.02.01 |
(키움증권 해외선물 OpenAPI-W) RSI 값 구하기 (1) 연속조회 및 과거데이터 받기 (2) | 2023.01.31 |
(키움증권 해외선물 OpenAPI-W) 볼린저밴드 상한선/하한선 값 구하기 (2) | 2023.01.30 |
(키움증권 해외선물 OpenAPI-W) CommRqDATA 및 SendOrder 함수의 호출 제한 (0) | 2023.01.29 |