목 차
1. 들어가며
2. 사전설명
1) opw10007 실행에 필요한 input 데이터 현황
2) opw30023(지정청산대상조회)의 결과물 확인
3) opw10017(지정청산주문) 내역
3. 코드설명
4. 지정청산(opw10017) 실행을 위한 18가지 인수
5. 전체코드
6. 마치며
1. 들어가며
지난 글에서는 해외파생 지정청산대상조회(opw30023)에 대해 알아보았다. 이미 언급하였지만, API로는 보유하고 있는 종목의 1종목씩 지정청산을 해야하므로 상당히 불편할 것 같다. 일일히 하나씩 청산을 해줄 실익이 있을까? 차라리 sendorder 함수로 청산해 주는 게 더 나을 것 같다. 그래도 지정청산에 대한 수요는 분명히 있을 것이기 때문에 글을 이어가도록 한다.
이번 글에서는 지정청산주문(opw10007)에 대해 알아볼 것이다. 특이하게도 opw10017은 TR 요청만으로도 결과값이 반환된다. 무슨 말일까? TR요청은 일반적으로 4단계로 이루어진다. 입력(SetInputValue) - 요청(CommRqData) - 연결(OnReceiveTrData) - 수신(GetCommData)로 이루어지는데, 지정청산주문은 2단계(입력-요청)로 이루어진다. 물론 반환값이 "처리건수 = 0001"의 메시지가 있고, "멀티데이터"도 있긴한데, 테스트할 때 약간 특이하다고 느꼈다.
여기서는 이전 글인 opw30023(해외파생 지정청산대상조회)와 코드가 이어진다. 겹치는 내용은 생략하고 새롭게 추가된 코드 위주로 설명할 예정이다.
※ 주의
지정청산주문(opw10007)을 실행하고 "조회"를 누르면, 가지고 있는 종목이 청산된다. 종목을 가지고 있을 때 opw10007은 데이터 조회시 사용하는 것을 자제하는게 좋아보인다. ^^
2. 사전설명
1) opw10007 실행에 필요한 input 데이터 현황
opw10007 실행을 위해 필요한 input 데이터는 총 18가지이다. 이 중 계좌번호, 비밀번호 등 5가지는 사용자가 입력해주고, 13가지는 opw30027에서 받아온다.
2) opw30023(지정청산대상조회)의 결과물 확인
opw30023(지정청산대상조회)에서 받아온 13가지 데이터 중 opw10017에서 활용한다.
3) opw10017(지정청산주문) 내역
opw10017(지정청산주문)의 input / output 데이터를 각각 활용하자. 입력 인수가 많은 관계로 WKOA Studio의 설명을 확인하도록 하자.
3. 코드설명
활용 모듈 및 로그인에 대한 설명은 생략한다. 그림에서 짤린 부분은 5번(전체코드)에서 확인가능하다. 또한, opw30023의 내용이 일부 섞여 있는데, 이전 글에서 설명하였으므로 중복된 내용도 설명을 생략한다.
1줄~29줄 : 활용모듈 및 로그인에 관한 내용이다. 21줄에서 사용자의 계좌번호 10자리를 입력한다.
32줄~51줄 : 입력해야될 데이터가 상당히 많아 보인다. 다행인건, opw30023에서 13개 데이터를 받아올 것이기 때문에 크게 부담은 없을 것이다.
55줄~60줄 : 지정청산대상 조회에 대한 내용이다.
65줄~66줄 : 51줄에서 요청한 TR목록이 "opw10017"이면 "0001"이 반환되면서, 계좌에 있는 종목을 청산한다.
이 원리는 키움서버에 데이터 입력(33줄~50줄, SetInputValue) 후 데이터 요청(51줄, CommRqData)을 하면 키움서버에서 이벤트가 발생할 때 청산이 되는 것이다. 멀티데이터에서 반환값이 있긴 하나, 종목청산(처리건수 0001)이 된다.
68줄~98줄 : 지정청산대상조회(opw30023)을 통해 받아오는 데이터이다. 한가지 주목할 것은 평가손익도 가져온다.
만약, 사용자가 청산할 때 sendorder이 아닌, 지정청산(opw10017)을 활용한다면, opw30023의 평가손익은 청산을 위한 기준이 될 것이다.
실제로, 필자도 청산시 opw10017을 활용하였는데, 이 때 opw30023의 평가손익이 20달러보다 크면 익절 지정청산, -20달러가 되면 손실 지정청산을 하였다.
103줄~105줄 : 필자가 임의로 지칭한 goobye_exe 함수이다. 114줄이 실행되면, 18개 인수를 넣어 31줄의 rq_data_opw10017 함수를 실행한다.
112줄 : 지정청산대상조회(opw30023)을 통해 계좌에 보유중인 종목의 청산을 위한 데이터를 받는다.
114줄 : 103줄의 함수를 실행한다.
4. 지정청산(opw10017) 실행을 위한 18가지 인수
18가지 인수를 한번 살펴 보도록 하자. WKOA Studio 설명에 따라 설정한 결과이며, 필자가 실제로 적용한 세팅이다.
def goodbye_exe(self):
self.rq_data_opw10007("계좌번호 10자리", "비밀번호", "00", "AP", "1", self.fcm_code_30023, self.futures_code_30023, self.futures_code_name_30023,
self.sell_buy_gubun_30023, self.buy_price_30023, self.buy_date_30023, "1", self.order_qty_30023, "", "", "", "", "")
5. 전체코드
계좌에 종목이 있으면 청산되니, 실행 전 주의할 필요가 있다. 모의계좌를 통해 마이크로 나스닥 1종목을 매수해보고나서, 실행해 보는 것을 추천한다.
계좌번호, 비밀번호 등을 23줄, 104~105줄, 112줄에 각각 넣어주어야 실행된다.
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
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_()
self.kiwoom.dynamicCall("GetCommonFunc(QString, QString)", "ShowAccountWindow", "") # 계좌번호 입력창을 띄우는 내부함수
##### 계좌 번호, 종목코드, 익절/손절 타점 설정 #####
self.deposit_num = "계좌번호 10자리"
########## 로그인 관련 함수 ##########
def login_Connect(self, err_code):
if err_code == 0:
print('로그인 성공했습니다!')
self.login_event_loop.exit()
else:
print('로그인 실패했습니다!')
########## 키움서버에 TR 요청하는 함수 모음 (키움 OPENApi-w에서 제공) ##########
def rq_data_opw10007(self, var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, var11, var12, var13, var14, var15, var16, var17, var18): # 일괄청산(지정청산주문)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "계좌번호", var1)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "비밀번호", var2)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "비밀번호입력매체", var3)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "통신주문구분", var4)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "입력건수", var5)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "FCM코드", var6)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", var7)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목명", var8)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "매도수구분", var9)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "매입표시가격", var10)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "매입일자", var11)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "해외주문유형", var12)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "주문수량", var13)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "주문표시가격", var14)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "STOP표시가격", var15)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "주문번호", var16)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "에러코드", var17)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "처리메세지", var18)
self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opw_10007", "opw10007", "", "1007")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def rq_data_opw30023(self, deposit_num1, password_2, password_enter3, tongsin_gubun_4): # 계좌 현재가, 매도수구분, 보유수량, 매입단가, 계좌현재가, 평가손익
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "계좌번호", deposit_num1)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "비밀번호", password_2)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "비밀번호입력매체", password_enter3)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "통신주문구분", tongsin_gubun_4)
self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opw_30023", "opw30023", "", "3023")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
if rqname == "opw_10007": # 일괄청산
pass # 반환되는 값은 처리건수 = 0001
elif rqname == "opw_30023": # 계좌 현재가, 매도수구분, 보유수량, 매입단가, 계좌현재가, 평가손익
self.fcm_code_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "FCM코드").strip()
self.futures_code_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "종목코드").strip()
self.futures_code_name_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "종목명").strip()
self.sell_buy_gubun_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "매도수구분").strip()
self.buy_price_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "매입표시가격").strip()
self.buy_date_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "매입일자").strip()
self.order_type_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "해외주문유형").strip() # 해외주문유형이 "아무것도 반환되지 않음"에 따라, 수기로 "1"을 opw10007에 넣어준다.
self.order_qty_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "주문수량").strip()
# self.order_price_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "주문표시가격").strip()
# self.stop_price_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "STOP표시가격").strip()
# self.order_num_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "주문번호").strip()
# self.error_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "에러코드").strip()
# self.message_30023 = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "처리메세지").strip()
self.pl_check_30023 = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30023", "opw30023", 0, "평가손익").strip()) / 100
# print(self.fcm_code_30023)
self.tr_event_loop.exit()
print("=======================================================")
print(self.deposit_num)
print(self.deposit_num)
print(self.fcm_code_30023)
print(self.futures_code_30023)
print(self.futures_code_name_30023)
print(self.sell_buy_gubun_30023)
print(self.buy_price_30023)
print(self.buy_date_30023)
print(self.order_type_30023)
print(self.order_qty_30023)
print(self.pl_check_30023)
print("=======================================================")
# print(self.sell_buy_gubun_30023) # 진입이 매수일 때, sell_buy_gubun_30023은 2가 반환
def goodbye_exe(self):
self.rq_data_opw10007("계좌번호 10자리", "비밀번호", "00", "AP", "1", self.fcm_code_30023, self.futures_code_30023, self.futures_code_name_30023,
self.sell_buy_gubun_30023, self.buy_price_30023, self.buy_date_30023, "1", self.order_qty_30023, "", "", "", "", "")
if __name__ == "__main__":
app = QApplication(sys.argv)
btl = btl_system()
btl.rq_data_opw30023("계좌번호 10자리", "비밀번호", "00", "AP") # 통신주문은 WKOA Studio에서 "AP"로 입력
btl.goodbye_exe()
app.exec_()
6. 마치며
일괄청산(지정청산)에 대해 알아보았다. API 거래로는 계좌의 모든 종목을 한번에 청산할 수 없어서 약간은 실망한 것도 사실이다. 또한 조회제한의 기준이 되는 CommRqData를 opw30023과 opw10017를 각각 1회씩 1초에 총 2회 사용하여야 하는 부담이 발생한다. (참고로 CommRqData는 1초당 5회 초과하여 조회할 경우, 조회수 제한이 발생한다)
결론은 sendorder 함수를 통해 청산하는 게 나아보인다. 물론 for문을 돌려 지정청산을 하면 될거 같긴 한데, "일괄청산"을 사용하는 이유가 "한번에 청산"에 있을 것이기 때문에 API의 일괄청산이 크게 매력적이지 않아 보인다.
그래도 일괄청산(지정청산)이 돌아가는 원리와 단점도 명확히 알았으니, 일괄청산에 신경을 안 써도 될거 같다.ㅎㅎ
sendorder 함수를 통해 청산하는 시스템을 구상하자!
'2. 해외선물 > 2-4. 해외선물 API (사용)' 카테고리의 다른 글
(키움증권 해외선물 OpenAPI-W) 스토캐스틱(Stochastic) 값 구하기 (1) 스토캐스틱 개념 및 계산하는 방법 (4) | 2023.10.01 |
---|---|
(키움증권 해외선물 OpenAPI-W) MACD 값 구하기 (3) 파이썬에서 작성하기 (0) | 2023.08.08 |
(키움증권 해외선물 OpenAPI-W) MACD 값 구하기 (2) MACD 엑셀에서 계산하기 (0) | 2023.08.07 |
(키움증권 해외선물 OpenAPI-W) MACD 값 구하기 (1) MACD 개념 및 지수이동평균 (0) | 2023.08.06 |
(키움증권 해외선물 OpenAPI-W) 일괄청산 (1) 해외파생지정청산대상조회(opw30023) (3) | 2023.02.12 |
(키움증권 해외선물 OpenAPI-W) 현재시간 표현 및 시간에 따라 다른 패턴 적용 등 시간 다루기 (datetime 모듈) (0) | 2023.02.10 |
(키움증권 해외선물 OpenAPI-W) 청산을 위해 계좌에서 평가손익 등 받기 (opw30003) (2) | 2023.02.07 |
(키움증권 해외선물 OpenAPI-W) 진입을 위한 SendOrder 함수 (0) | 2023.02.06 |