2. 해외선물/2-1. 해외선물 자동매매 연구

(키움증권 해외선물 자동매매 파이썬) 10. 매도수구분, 진입가격, 청산가격, 평가손익 알아보기(opw30012)

봄이오네 2023. 9. 29. 08:05
반응형
목 차
1. 들어가며
2. 사전설명
   1) 청산을 위한 기준 설정(평가손익 → 현재가격)
   2) 매도수구분을 위해 opw30012 활용
   3) WKOA Studio에서 확인
3. 코드 설명
4. 전체 코드
5. 마치며

 

1. 들어가며

지난 글에서는 opc10002(해외선물옵션 분차트조회)를 통해 1분봉의 시가, 종가 등의 데이터 수신받는 방법을 알아보았다. 필자는 1분봉 데이터를 기반으로 진입 패턴을 만들 것이다. 그래서 위와 같이 1분봉 데이터 수신방법을 설명하였다.

 

이번 글에서는 opw30012(미결제내역 상세조회)을 통해 (진입 후) 매도수구분, 평가금액, 진입가격, 현재가격 등을 알아보도록 하자.

 

더보기

※ 여기서 잠깐!

Tr 목록에서 opc와 opw의 차이는 잘 모르겠다. 다만 그 뒤의 숫자의 첫번째 자리의 차이는 간단하게 설명한다.

opc10002 / opw30012에서 1과 3이 각각 보일 것이다.

ㅇ 5자리의 첫번째 숫자가 1이면 시가, 종가 등 일반 데이터를 조회한다.

ㅇ 5자리의 첫번째 숫자가 3이면 계좌 내용을 조회한다. (조회시 계좌번호, 계좌비밀번호 등의 데이터의 입력 필요)

 


2. 사전설명

1) 청산을 위한 기준 설정(평가손익 → 현재가격)

진입 후 수익이 났으면 수익청산, 손실이 났으면 손실청산을 할 것이다. 어떤 기준에서 청산할지 상당히 고민이 되었다.

원래는 평가손익으로 청산을 하였다. 진입 후 내 계좌에 +200달러만 찍혀도 안절부절 못하며 청산해야 하나... 수없이 고민했던거 같다. ;;;;

 

그래서 처음에는 청산의 기준을 "평가손익" 항목으로 정했는데, 문제가 하나 발생한다. 마이크로 미니 나스닥을 하는 경우, 미니나스닥에 비해 증거금이 낮아서인지 주문가능수량이 수시로 바뀌었다. 그때마다 평가손익을 바꾸어주는 불편이 있었다. 자동매매인데, 미니 나스닥 거래시 수시로 평가손익을 바꾸어주는 건 아닌것 같아서, "현재가격"으로 바꾸었다.

 

예를 들어, 진입가격 대비 현재가격의 변화에 따른 수익/손실 실현으로 거래를 하니 생각보다 편했다. 예전처럼 계좌의 평가손익으로 할때는 마이크로 미니 나스닥 2계약시 수익 얼마? 5계약시 수익 얼마?로 고민하는게 의미가 없어보였다.

 

거래수량에 상관없이, 나스닥 1계약으로 long 진입 후 10p 오르면 수익 200달러가 나오니 깔끔하게 계산된다. 2계약시 long 진입 후 10p 떨어지면 400달러 손실이 나온다. 계약수에 상관없이 진입가격 대비 현재가격 설정은 이렇듯 수량 변경에 영향을 받지 않는 편안함이 있다.

 

결론은 필자는 청산시 opw30012에서 받아오는 내용 중 평가손익이 아닌, "현재가격"을 활용할 것이다.

 

2) 매도수구분을 위해 opw30012 활용

국내주식 OpenAPI를 할때는 의식하지 못했는데, 해선 OpenAPI-W를 이용할 때 생각해주어야 하는 것이 있다. long(매수) / short(매도)로 어떻게 진입하고, 어떻게 청산할 것인가?

 

국내주식의 경우 매수 후 매도를 하는 구조라면, 해외선물은 long으로 진입하면 short으로 청산해야하며, short으로 진입하면 long으로 청산해야 한다.

 

이 구분을 어떻게 할 것인가? 영웅문G와 WKOA Studio에서 각각 확인해보자.

 

먼저 임의로 모의계좌에서 long으로 1계약 진입하였다. (체결 가격 14742.25)

그림1-1. 영웅문G에서 임의로 1계약 long 진입

 

영웅문G에서는 화면번호 4541로 계좌의 실시간 잔고를 확인할 수 있다.

< 그림1-2 >에서 구분은 "매수"로 되어 있다. 매입가,현재가,평가손익도 확인 가능하다.

그림1-2. 영웅문G에서 확인한 "구분"은 '매수"이다.

 

< 그림 1-3 >에서 < 그림 1-2 >의 내용을 확인할 수 있다. 매입가격(체결가격)은 14742.25, 현재가격 14741.50으로 동일하고 평가손익도 1500이다. (물론 평가손익은 나중에 100으로 나누어주어야 한다)

그림1-3. WKOA Studio에서 확인가능한 opw300012 데이터

 

여기서 설명이 빠진 부분이 있다. < 그림 1-2 >의 영웅문G에서 "구분"카테고리 long 진입은 "매수"라고 표시되는 건 모두가 아는 사실이다. 그렇다면 WKOA Studio를 통해 OpenAPI-W를 활용하여 키움서버에서 받아오는 정보는 어떻게 될까?

 

long 매수 진입이라면, OpenAPI-W에서는 매도수구분은 2로 표기되는 걸 확인할 수 있다. 이 내용은 정말 중요하다. 필자도 처음에 헷갈려서 계속 돌려보고 있었다. 그렇다면 short 매도 진입은 어떻게 표기될까? OpenAPI-W에서 매도수구분은 1로 표기된다.

 

3) WKOA Studio에서 확인

opw30012는 바로 위에서 확인했으니, 그 결과물은 따로 설명하지 않을 것이다.

 


3. 코드 설명

로그인 및 opc10002에 대한 내용은 설명을 생략한다. 앞의 글에서 추가된 부분은 35줄~41줄, 50줄~71줄, 77줄 3곳이다.

그림2-1. 로그인 및 opc10002에 대한 내용이다.

 

1줄~34줄 : 로그인 및 opc10002(1분봉 데이터 받기)에 대한 내용으로 설명을 생략한다.

 

그림2-2. opw30012(미결제내역 상세조회)에 대한 내용

 

35줄 : 77줄에서 3개 변수(계좌번호, 비밀번호, 비밀번호입력매체)를 담아서 실행할 함수를 정의한다.

36줄~38줄 : 계좌번호, 비밀번호, 비밀번호입력매체의 3가지를 키움서버에 입력(SetInputValue)한다.

39줄 : 계좌번호, 비밀번호, 비밀번호입력매체의 3가지에 요청(CommRqData)한다.

 

50줄 : 39줄의 요청에 의해 수신받은 rqname가 opw30012이면,

52줄~57줄 : < 그림2-2 >에서 짤린 부분은 4번(전체코드)에서 확인가능하다. 계좌에 있는 종목코드, 매도수구분(sell_buy_gubun_d), 보유수량, 진입가격(my_price_d), 현재가격, 평가수익(estimate_pl_d) 등을 받을 수 있다.

   ※ 여기서 각 변수의 끝에 붙인 d는 필자가 임의로 붙였다. 처음에 코드를 작성할 때, 너무 헷갈려서 계좌(deposit) 데이터를 구분하기 위해 기재한 것이니, 익숙해졌다면 d는 삭제해도 된다.

  ※ 키움측에서 데이터를 받아올 때는 최초로 데이터 형태는 모두 문자형(str)이다. 52줄~57줄의 int, float는 각각 정수화, 실수화 시켜준 것이다. (향후 사칙연산으로 활용하기 위함)

 

59줄~64줄 : 함수 밖에서 활용하기 위해 self를 앞에 붙인다. 64줄은 앞에서 설명하였듯이 00002940700, 뒤의 소수점 2개를 없애주기 위함이다.

 

그림2-3. 키움서버에서 받아온 내용을 화면에 출력한다.

 

66줄~69줄 : 키움서버에서 받아온 각각의 데이터를 화면에 출력(83줄~86줄)한다.

77줄 : 35줄의 함수(rq_data_opw30012)에 변수 3개를 넣어주어서 실행한다.

 


4. 전체 코드

전체 코드는 아래와 같다.

아래 코드를 활용하려면 77번째 줄에 해외선물 계좌 10자리와 비밀번호 4자리를 넣어주어야 한다.

그리고 계좌에 진입된 수량도 있어야 한다. (계좌의 잔고수량, 금액 등을 조회하는 화면이다)

 

더보기
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.dynamicCall("CommConnect(1)")  # CommConnect() : 괄호 안에 자동(1)을 넣는다.
        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)

        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.kiwoom.dynamicCall("GetCommonFunc(QString, QString)", "ShowAccountWindow", "")  # 계좌번호 입력창을 띄우는 내부함수

    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
        else:
            print('로그인 실패했습니다!')
        self.login_event_loop.exit()

    ########## 키움서버에 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", "", "1002")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    def rq_data_opw30012(self, deposit_num1, password_2, password_enter3):  # 계좌 현재가, 매도수구분, 보유수량, 매입단가, 계좌현재가, 평가손익
        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("CommRqData(QString, QString, QString, QString)", "opw_30012", "opw30012", "", "3012")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    ########## OnReceiveTrData을 통해 수신받은 데이터 함수  ##########
    def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
        if rqname == "opc_10002":
            self.open_price_one_ago = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", 1, "시가").strip()))
            self.close_price_one_ago = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", 1, "현재가").strip()))
            self.tr_event_loop.exit()

        elif rqname == "opw_30012":  # 계좌 현재가, 매도수구분, 보유수량, 매입단가, 계좌현재가, 평가손익

            stock_code_d = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "종목코드").strip()
            sell_buy_gubun_d = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "매도수구분").strip()
            my_qty_d = int(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "수량").strip())
            my_price_d = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "매입가격").strip())
            current_price_d = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "현재가격").strip()))
            estimate_pl_d = int(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30012", "opw30012", 0, "평가손익").strip())

            self.stock_code_d = stock_code_d
            self.sell_buy_gubun_d = sell_buy_gubun_d
            self.my_qty_d = my_qty_d
            self.my_price_d_30012 = my_price_d
            self.current_price_d_30012 = current_price_d
            self.estimate_pl_d_30012 = estimate_pl_d / 100

            print(self.sell_buy_gubun_d)
            print(self.my_qty_d)
            print(self.my_price_d_30012)
            print(self.current_price_d_30012)

            self.tr_event_loop.exit()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()

    btl.rq_data_opw30012("계좌번호 10자리", "비밀번호 4자리", "00")

    app.exec_()
    
    
(expected result)
### 2
### 1
### 14742.25
### 14742.25

 


5. 마치며

opw30012(미결제내역 상세조회)를 통해 계좌에 있는 종목의 매도수구분(=진입 position), 진입/청산가격, 평가손익 등을 알아보았다. 매도수구분에서 많이 착각이 될 수도 있다. 하나만 기억하자. long 진입인 경우 매도수구분은 2이다.! 필자도 너무 헷갈려서 아예 파이참 코드 옆에 적어두었다. (short 진입의 매도수구분은 1이다)

if self.sell_buy_gubun_d == "2":  # 진입이 매수인 경우

 

다음 글에서는 주문가능금액 조회를 위해 opw30009(예수금 및 증거금 현황조회)에 대해 알아보자.

 

반응형