2. 해외선물/2-4. 해외선물 API (사용)

(키움증권 해외선물 OpenAPI-W) 일괄청산 (2) 해외파상지정청산주문(opw10007)

봄이오네 2023. 2. 13. 08:09
반응형
목 차
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에서 받아온다.

그림1. opw10017(지정청산요청)에 필요한 데이터 내역

2) opw30023(지정청산대상조회)의 결과물 확인

opw30023(지정청산대상조회)에서 받아온 13가지 데이터 중 opw10017에서 활용한다.

그림2-1. WKOA Studio에서 확인가능한 지정청산대상 조회 내역

3) opw10017(지정청산주문) 내역

opw10017(지정청산주문)의 input / output 데이터를 각각 활용하자. 입력 인수가 많은 관계로 WKOA Studio의 설명을 확인하도록 하자.

그림2-2. WKOA Studio에서 확인가능한 지정청산요청 내역


3. 코드설명

활용 모듈 및 로그인에 대한 설명은 생략한다. 그림에서 짤린 부분은 5번(전체코드)에서 확인가능하다. 또한, opw30023의 내용이 일부 섞여 있는데, 이전 글에서 설명하였으므로 중복된 내용도 설명을 생략한다.

그림3-1. 활용 모듈 및 로그인 내역

1줄~29줄 : 활용모듈 및 로그인에 관한 내용이다. 21줄에서 사용자의 계좌번호 10자리를 입력한다.

그림3-2. opw10007의 데이터 입력/요청하는 내역


32줄~51줄 : 입력해야될 데이터가 상당히 많아 보인다. 다행인건, opw30023에서 13개 데이터를 받아올 것이기 때문에 크게 부담은 없을 것이다.

55줄~60줄 : 지정청산대상 조회에 대한 내용이다.

그림3-3. 지정청산대상 조회(opw30023) 및 청산(opw10017) 내역


65줄~66줄 : 51줄에서 요청한 TR목록이 "opw10017"이면 "0001"이 반환되면서, 계좌에 있는 종목을 청산한다.
이 원리는 키움서버에 데이터 입력(33줄~50줄, SetInputValue) 후 데이터 요청(51줄, CommRqData)을 하면 키움서버에서 이벤트가 발생할 때 청산이 되는 것이다. 멀티데이터에서 반환값이 있긴 하나, 종목청산(처리건수 0001)이 된다.

68줄~98줄 : 지정청산대상조회(opw30023)을 통해 받아오는 데이터이다. 한가지 주목할 것은 평가손익도 가져온다.
만약, 사용자가 청산할 때 sendorder이 아닌, 지정청산(opw10017)을 활용한다면, opw30023의 평가손익은 청산을 위한 기준이 될 것이다.

실제로, 필자도 청산시 opw10017을 활용하였는데, 이 때 opw30023의 평가손익이 20달러보다 크면 익절 지정청산, -20달러가 되면 손실 지정청산을 하였다.

그림3-4. 지정청산 실행을 위한 함수(103~105)를 확인할 수 있다.


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, "", "", "", "", "")

그림4. opw10017의 input 설정 내역


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 함수를 통해 청산하는 시스템을 구상하자!

반응형