목 차
1. 들어가며
2. 사전설명
1) 상황에 따른 진입/청산 패턴 실행하기(주문가능금액)
2) WKOA Studio에서 Tr 확인하기
3. 코드 설명
4. 전체코드
5. 마치며
1. 들어가며
지난 글에서는 opc10002(해외선물옵션 분차트조회)와 opw30012( 미결제내역 상세조회)를 통해 진입/청산하는 방법을 알아보았다.
이번 글에서는 opw30009(예수금및증거금현황조회)를 통해 진입/청산을 위한 주문가능금액 조회하는 방법을 알아보자. 아래 < 접은글 >에서도 확인하겠지만, 주문가능금액(=계좌잔고)의 조회를 통해 진입 패턴 or 청산 패턴을 각각 실행할 것이다.
2. 사전설명
1) 상황에 따른 진입/청산 패턴 실행하기(주문가능금액)
형광등을 생각해보자. 전기가 들어온다면, 형광등은 ON/OFF의 2가지 경우가 수가 존재한다. 해외선물도 마찬가지이다. 시스템이 돌아가는 동안, 진입/청산의 2가지 경우가 있다. (진입 후 횡보하더라도 시간설정을 해두었다면 언젠가는 청산될 것이다)
예글 들어보자. 주문가능금액이 20,000달러(A)이고, 나스닥 1계약 증거금이 18,000달러(B)이라고 하자.
- 나스닥 진입을 하지 않았다면, 주문가능금액(A)은 20,000달러이다. → 진입 패턴 실행
- 나스닥 진입을 했다면, 주문가능금액(A)은 2,000달러(20,000-18,000)이다. → 청산 패턴 실행
진입/청산의 각각의 패턴이 각각 다르게 설정되는 기준은 계좌에 있는 "주문가능금액"으로 설정하였다. 이 기준이 되는 주문가능금액을 어떻게 설정할 것이냐.... 여기서부터 이글을 보는 분들은 고민이 시작될 것이다.
참고로 필자는 모의계좌에서 18,000달러로 설정하였다. (이유는? 나스닥 1계약 진입 가능하다.)
※ 여기서 잠깐!
나스닥 선물거래를 위해 구축해야 하는 코드는 어떤것이 있을까? (글 작성 방향 설명)
1. 먼저 생각해보자.
진입 패턴에 해당하면 진입주문을 넣고, 청산 패턴에 해당하면 청산주문을 넣는다. 간단한것 같지만, 생각보다 고려해야 하는 요소들이 많다.
① long/short으로 어떻게 구분하여 진입할 것이며,
② 청산을 위한 기준으로 (진입가격-청산가격)을 사용할 것인가? 평가손익을 사용할 것인가?
③ long/short으로 청산하려면 어떻게 구축해야 할 것인가?
2. 코드 구성
ㅇ 진입 패턴 조회(opc10002) → 진입패턴 해당하면 진입주문(SendOrder) →
청산 패턴 조회(opw30012) → 청산패턴 해당하면 청산주문(SendOrder)
* Tr 목록의 2개(opc10002, opw30012)만 쓰이고, SendOrder만 사용된다.
3. 설명
ㅇ 추가 설명 예정 2개 : opw30009(주문가능금액), opw30011(주문가능 수량)
- 예를 들어보자. 계좌에 20,000달러가 있고 나스닥 1계약의 증거금이 18,000달러이면
진입을 하지 않았다면 계좌에 20,000달러가 있어서 → 진입 패턴 실행
- 진입이 되어 계좌의 잔고가 2,000달러(20,000-18,000)이면 → 청산 패턴 실행
4. 결론
ㅇ 필자는 계좌의 잔고를 10~15초마다 조회해서, 계좌의 잔고에 따라 진입/청산 패턴을 실행한다.
- 계좌잔고가 20,000달러 이상이면 진입패턴, 20,000달러 이하이면 청산패턴을 실행한다.
(계좌잔고가 20,000달러이면 아직 진입을 안했다는 뜻이니, 진입 패턴을 계속 조회할 것이고,
계좌잔고가 2,000달러이면 이미 진입을 했다는 뜻이니, 청산 패턴을 조회할 것이다.)
2) WKOA Studio에서 Tr 확인하기
주문가능금액 조회하는 방법은 예전에 설명하였다. (https://springcoming.tistory.com/160)
중복될 만한 내용은 최소화하면서 간략히 설명할 것이다.
영웅문G의 화면번호(4553)에서 조회한 필자의 모의계좌의 주문가능금액은 29,708달러이다. (다시 한번 말하지만, 모의계좌이다)
WKO Studio에서 조회한 필자의 모의계좌의 주문가능금액은 < 그림2-2 >에서 확인가능하듯, 00002970800이다.
< 그림2-1 >과 < 그림2-2 >에서 조회한 값이 각각 다르다. 이유는 알 수 없지만, WKOA Studio에서 제공하는 주문가능금액은 0002970800의 형태로 제공한다. 흠... 키움서버에서 제공한 문자형(str) 0002970800은 strip를 통해 앞의 000을 없애주고 나서, float을 붙여서 실수화시키고, 100으로 나누면 된다.
orderable_money = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30009", "opw30009", 0, "주문가능금액").strip())
self.orderable_money = orderable_money / 100
그리고, SetInputValue 3가지와 CommRqData 4가지에서 입력되어야 하는 데이터는 아래와 같다. (WKOA Studio에서 확인 가능)
- SetInputValue : 계좌번호(계좌번호 10자리), 비밀번호(계좌 비밀번호 4자리), 비밀번호입력매체(00)
- CommRqData : RQName(임의로 입력 가능), "opw30009", 빈칸(""), 화면번호(임의로 부여 가능. 필자는 3009로 설정)
3. 코드 설명
이전의 코드에서 추가된 3곳은 35~41줄, 58줄~67줄, 94줄이다.
추가되는 내용은 아래로 계속 기존 코드 바로 밑에 작성하려고 했으나, 가독성이 상당히 떨어지는 관계로 오름차순(작은수 → 큰수)으로 작성하였다. 나중에 코드가 길어지면 해당코드를 찾는게 생각보다 힘들다.
1줄~33줄 : 로그인 및 1분봉 데이터 조회에 대한 내용으로, 여기서는 설명을 생략한다.
35줄 : 94줄에서 실행명령에 대해, 계좌번호/비밀번호/비밀번호입력매체 3가지가 입력되었을 때 실행되는 함수를 정의한다.
36~38줄 : 계좌번호, 비밀번호, 비밀번호입력매체의 3가지를 OpenAPI-W에 입력한다.
39줄 : 36줄~38줄에서 입력한 내용 3가지를 OpenAPI-W를 통해 키움서버에 요청(CommRqData)한다.
58줄 : 키움서버에서 받아온 rqname가 "opw_30009"이면
59줄 : 주문가능금액 데이터의 내용을 orderable_money에 넣어라.
60줄~62줄 : 인출가능금액, 실현/미실현수익을 변수에 넣어라.
* 60줄~62줄은 반드시 필요한 코드는 아니다. 사용자 취향에 따라 삭제하여도 된다.
64줄 : 주문가능금액을 100으로 나눈다. 이유는? < 그림2-2 >에서 확인하였겠지만, 0002970800으로 받아온다. strip 및 float를 통해 앞의 000은 삭제가능하다. 2970800에서 00은 100으로 나누어서 없애준다. 결과는? 29,708달러로 주문가능금액이 나온다.
* 발생원인 : 영웅문G에서 주문가능금액이 29,708.00로 소수 둘째자리까지 나오는데 "소수점"이 사라지고, 소수 둘째자리까지 남았는데, 그 소수 둘째자리까지 끌어와서 이런 문제가 발생한다. → 조치방법은 59줄에서 float를 붙여주고, 64줄에서 100으로 나누어주자.
69줄 : 주문가능금액을 화면에 출력하라
73줄~88줄 : 계좌의 매도수구분(보유 position), 현재가격 등을 뜻하며, 앞에서 설명하였으므로 여기서는 설명을 생략한다.
94줄 : 35줄에서 정의한 rq_data_opw30009 함수에 전달한 변수를 여기에서 정해준다. 계좌번호/비밀번호/비밀번호입력매체를 여기에서 입력하여 실행하면, 35줄이 3개 변수를 받아주면서, 36줄~41줄까지의 내용이 실행된다.
4. 전체코드
아래 < 접은글 >의 코드를 실행하려면, 시세조회 이용료($185)를 지불하여야 하며, 94줄에서 사용자의 계좌번호 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_opw30009(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_30009", "opw30009", "", "3009")
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_30009": # 주문가능금액, 실현손익, 미실현손익
orderable_money = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30009", "opw30009", 0, "주문가능금액").strip())
withdrawal_money = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30009", "opw30009", 0, "인출가능금액").strip())
realized_pl = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30009", "opw30009", 0, "선물청산손익").strip())
unrealized_pl = int(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30009", "opw30009", 0, "선물평가손익").strip())
self.orderable_money = orderable_money / 100
self.withdrawal_money = withdrawal_money / 100
self.realized_pl = realized_pl / 100
self.unrealized_pl = unrealized_pl / 100
print(self.orderable_money)
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
self.tr_event_loop.exit()
if __name__ == "__main__":
app = QApplication(sys.argv)
btl = btl_system()
btl.rq_data_opw30009("계좌번호 10자리 입력", "비밀번호 4자리", "00")
app.exec_()
5. 마치며
이 글에서는 opw30009(예수금및증거금 현황조회)을 통해 주문가능금액 조회하는 방법을 알아보았다. 아래 코드와 같이 주문가능금액에 따라 실행되는 함수가 다르다는 것을 기억하자.
while True:
btl.rq_data_opw30009(btl.deposit_num, btl.deposit_password, "00") # 주문가능금액
if btl.orderable_money > btl.check_money:
### 진입 함수 실행
time.sleep(10)
else:
### 청산 함수 실행
time.sleep(10)
다음 글에는 Tr 목록 관련 마지막에 해당하는 opw30011(주문가능수량 조회)에 대해 알아보자. 그 다음 글에서는 SendOrder 함수를 알아보는 것을 끝으로 < 2부. tr을 통한 데이터요청 >은 마무리 지을 예정이다.
* opw30003(미체결잔고내역 조회)는 별도 설명하지 않을 예정이다. 원래 opw30003에서 평가손익, 진입/현재가격을 받아오는 것으로 당초 작성했는데, 평가손익, 진입/현재가격은 opw30012에서도 얻어올 수 있다. (결론은 중복설명 우려가 있으므로, opw30003은 별도 설명하지 않을 것이다)
'2. 해외선물 > 2-1. 해외선물 자동매매 연구' 카테고리의 다른 글
(키움증권 해외선물 자동매매 파이썬) 15. 보조지표 구하기 (볼린저밴드, MACD, 스토캐스틱) (0) | 2023.10.22 |
---|---|
(키움증권 해외선물 자동매매 파이썬) 14. rsi 구하기 (4) | 2023.10.21 |
(키움증권 해외선물 자동매매 파이썬) 13. 주문하기(SendOrder) (13) | 2023.10.06 |
(키움증권 해외선물 자동매매 파이썬) 12. 주문가능수량 조회 (opw30011) (4) | 2023.10.05 |
(키움증권 해외선물 자동매매 파이썬) 10. 매도수구분, 진입가격, 청산가격, 평가손익 알아보기(opw30012) (0) | 2023.09.29 |
(키움증권 해외선물 자동매매 파이썬) 9. 1분봉 데이터 받기(opc10002) (2) | 2023.09.28 |
(키움증권 해외선물 자동매매 파이썬) 8. tr 동작원리 (2) | 2023.09.27 |
(키움증권 해외선물 자동매매 파이썬) 7. 클래스의 변수화(인스턴스) (0) | 2023.09.15 |