목 차
1. 들어가며
2. 사전설명
1) 데이터 요청 방법
2) 주문정보를 입력하기 위해 필요한 정보
3) 주문넣는 코드
3. 코드 설명
4. 전체 코드
5. 마치며
1. 들어가며
지난 글에서는 opw30011(주문가능수량 조회)을 통해 사용자가 주문할 수 있는 수량을 받아오는 방법을 알아보았다. 주문가능수량을 조회하는 이유는? long이든 short이든 해외선물에 진입(주문)하기 위해서이다.
이번 글에서는 SendOrder 함수를 통해 long/short 주문하는 방법을 알아볼 것이다. 아래의 내용은 과거에 작성한 글이며 참고하자. (기존 코드에 추가하여 작성한다)
※ 주문하는 글은 국내주식/해외선물에서 이미 설명하였다.
ㅇ 국내주식 주문하기 : https://springcoming.tistory.com/31
ㅇ 해외선물 주문하기 : https://springcoming.tistory.com/177
2. 사전설명
1) 데이터 요청 방법
1분봉 등의 데이터를 키움증권에 요청할 때는 4단계(데이터 입력 - 데이터 요청 - 연결 - 데이터 수신)에 의하여 데이터를 수신받을 수 있다. 이 과정은 SetInputValue - CommRqData - OnReceiveTrData - GetCommData의 코드로 작성한 것은 이전 글의 TR목록을 통해 데이터를 수신받았다. 이에 비해, 주문하기(SendOrder)은 2단계(데이터 입력 - 데이터 요청)로 이루어진다.
간단하게 생각해보자. 영웅문G에서 주문을 넣을 때, "종목, 가격, 수량" 등 기본적인 정보를 사용자가 입력하고, "주문하기"를 누르면 주문이 들어간다는 것은 충분히 알 수 있다.
우리가 주문하기 위해 해야될 일은 무엇일까? 종목, 가격, 수량 등 기본적인 정보를 입력하고, "주문하기"를 요청하는 코드만 만들면 된다. 시스템이 작동되고 있는 동안에 계속 활용할 것이므로 함수 형태로 만들어서 "계속 활용"해주면 된다.
2) 주문정보를 입력하기 위해 필요한 정보
해외선물을 주문하기를 위해 입력해야 되는 데이터는 총 10개이다.
- rqname : (문자형) "요구 데이터 명"이라는 뜻인데, 사용자가 임의로 정한다.
- scrno : (문자형) "화면번호"를 뜻하며, 사용자기 임의로 정한다.
- accno : (문자형) 사용자의 계좌번호 10자리를 입력
- ordertype : (정수형) 주문유형 (1:신규매도, 2:신규매수, 3:매도취소, 4:매수취소, 5:매도정정, 6:매수정정)
- code : (문자형) 해외선물 종목코드명
- qty : (정수형) 주문수량
- price : (문자형) 주문단가
- stop : (문자형) stop 단가
- hogagb : (문자형) 거래구분(1:시장가, 2:지정가, 3:stop, 4:stop limit)
※ 문자형은 데이터 앞에 따옴표(" ")를 붙여주고, 정수형은 따옴표 없이 숫자를 써주면 된다.
※ 필자는 "시장가"로 진입한다.
※ 또한, 매매정정/매수정정은 왠만하면 사용하지 않는다. 원하지 않는 가격으로 주문을 넣었으나 체결되지 않는 경우, 매매정정보다는, 기존 매매를 취소하고 진입하는게 좋다.
3) 주문넣는 코드
"주문하기"는 요청의 행위가 있다. 송/수신하는 기능의 함수는 무엇이 있었을까? PyQt5의 QAxContainer 내에 있는 dynamicCall을 통해 데이터를 송/수신한다.
아래 링크에서 확인하겠지만, PyQt5의 QAxContainer 라이브러리는 2가지 기능을 하는데, 사용자 pc에 깔린 레지스터리를 통해 OpenAPI-W에 접근하는 방법 및 dynamicCall을 통해 데이터를 송/수신하는 기능을 하는 함수이다.
* 자료 설명(라이브러리 설명) : https://springcoming.tistory.com/202
아래 코드는 라이브러리를 불러오는 코드(import)이다.
from PyQt5.QAxContainer import *
아래 코드는 주문하기 함수의 형태이다.
self.kiwoom.dynamicCall("SendOrder(Qstring, Qstring, Qstring, int, Qstring, int, Qstring, Qstring, Qstring, Qstring)",
[rqname, scr, acc, ordertype, futures_code, qty, futures_price, s_stop, hogagb, orgno])
위 코드를 쪼개서 분석을 해보면,
- self.kiwoom : 해외선물 OpenAPI-W의 레지스트리인 "KFOPENAPI.KFOpenAPICtrl.1"에 접근한다.
- dynamicCall : 데이터를 송신한다.
- SendOrder : dynamicCall을 통해 키움에 송신(요청)하는 내용은 "SendOrder"이다.
- 데이터 형태 : 주문하기 함수는 10개 인수로 이루어져 있는데 문자형(Qstring) 8개, 정수형(int) 2개이다.
- 데이터 이름 : 향후 입력될 10개 데이터를 받아줄 10개 변수(rqname, scr, acc 등)의 자리이다.
위의 형태에 익숙해지자. 나중에 정말 헷갈린다.
3. 코드 설명
코드를 누적해서 작성하고 있는 점을 고려하여, 중복된 코드 및 내용은 생략한다. 주문하기 위주로 알아보자.
※ < 그림1 >에서 짤린 내용은 4번(전체코드)에서 확인할 수 있다.
116줄: 임의로 sendorder_func 함수를 만들었다. 143줄에서 입력된 10개 변수를 116줄에서 받아줄 10개 변수를 받아준다.
117줄 : 키우증권 레지스터리에 접근하여 sendorder 함수를 키움서버에 송신(dynamicCall)한다.
118줄 : 143줄에서 입력된 10개 변수 → 116줄에서 받아주고 → 118줄에 다시 넣어주어서 → 117줄에서 요청한다.
* 복잡하게 이야기했지만, 143줄의 10개 변수를 118줄에 넣어주어서, 117줄에서 키움서버에 요청(dynamicCall)한다.
* 주문하기 형태는 익숙해지기 전까지 정말 헷갈린다. 120줄~137줄은 WKOA Studio에 있는 내용이다. "주문하기" 함수 아래에 참고로 적어두자.
143줄 : 116줄에서 입력해줄 10개 변수를 143줄에서 작성한다. → 지금은 "short 1계약 진입하라"는 의미이다.
4. 전체 코드
아래 < 접은글 >의 코드를 실행하려면, 시세조회 이용료($185)를 지불하여야 하며, 143줄에서 사용자의 계좌번호 10자리를 넣어주어야 한다. 또한 131줄~136줄의 계좌는 키움증권 WKOA Studio에 적힌 예시로 적힌 계좌이다.
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_opw30011(self, deposit_num1, password_2, password_enter3, futures_code4, sell_buy_gubunm5, order_type6, order_price7): # 주문가능수량
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)", "종목코드", futures_code4)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "매도수구분", sell_buy_gubunm5)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "해외주문유형", order_type6)
self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "주문표시가격", order_price7) # "시장가"로 주문할 경우, 함수 실행시 빈칸("")으로 요청
self.kiwoom.dynamicCall("CommRqData(QString, QString, QString,QString)", "opw_30011", "opw30011", "", "3011")
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_30011": # 주문가능수량
orderable_qty = int(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30011", "opw30011", 0, "주문가능수량").strip())
liquidable_qty = int(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30011", "opw30011", 0, "청산가능수량").strip())
orderable_money_30011 = float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30011", "opw30011", 0, "주문가능금액").strip())
currency_code = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opw_30011", "opw30011", 0, "통화코드").strip()
self.orderable_qty = orderable_qty
self.liquidable_qty = liquidable_qty
self.orderable_money_30011 = orderable_money_30011 / 100
self.currency_code = currency_code
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()
########## 주문요청하는 함수 (키움 OPENApi-w에서 제공) ##########
def sendorder_func(self, rqname, scr, acc, ordertype, futures_code, qty, futures_price, s_stop, hogagb, orgno):
self.kiwoom.dynamicCall("SendOrder(Qstring, Qstring, Qstring, int, Qstring, int, Qstring, Qstring, Qstring, Qstring)",
[rqname, scr, acc, ordertype, futures_code, qty, futures_price, s_stop, hogagb, orgno])
"""
1) BSTR sRQName,
2) BSTR sScreenNo,
3) BSTR sAccNo,
4) LONG nOrderType, *주문유형(1: 신규매도, 2: 신규매수, 3: 매도취소, 4: 매수취소, 5: 매도정정, 6: 매수정정)
5) BSTR sCode,
6) LONG nQty,
7) BSTR sPrice,
8) BSTR sStop, * stop 단가
9) BSTR sHogaGb, * 거래구분(1: 시장가, 2: 지정가, 3: STOP, 4: STOP LIMIT)
10) BSTR sOrgOrderNo)
*(예시) ## openApi.SendOrder("RQ_1", "1000", "5077000072", 1, "6AZ20", / 1, "0.7900", "0", "2", ""); // 지정가 매도
openApi.SendOrder("RQ_1", "1000", "5077000072", 1, "6AZ20", / 1, "0", "0", "1", ""); // 시장가 매도
## openApi.SendOrder("RQ_1", "1000", "5077000072", 1, "6AZ20", / 1, "0", "0.7900", "3", ""); // STOP 매도
## openApi.SendOrder("RQ_1", "1000", "5077000072", 1, "6AZ20", / 1, "0.7850", "0.8000", "4", ""); // STOP LIMIT 매도
## openApi.SendOrder("RQ_1", "1000", "5077000072", 5, "6AZ20", / 1, "0.7850", "0", "2", "500060"); // 정정 매도
## openApi.SendOrder("RQ_1", "1000", "5077000072", 3, "6AZ20", / 1, "0", "0", "2", "500060"); // 취소 매도
"""
if __name__ == "__main__":
app = QApplication(sys.argv)
btl = btl_system()
btl.sendorder_func("short 진입", "1001", "계좌번호 10자리 입력", 1, "NQZ23", 1, "0", "0", "1", "")
app.exec_()
5. 마치며
OpenAPI-W를 통해 주문하기(SendOrder)를 알아보았다. TR목록 중 4개와 주문하기 1개까지 총 5가지 데이터 입력/요청/수신 받는 방법은 알고 있어야, 자동매매를 위한 기본세팅이 준비가 끝났다고 볼 수 있다.
SetInputValue, CommRqData, GetCommData 등 OpenAPI-W를 통해 자료의 송/수신하는 함수에 대해 낯설게 느껴질 것이다. 위 3개 함수가 어떤 순서로 작동되는지 꼼꼼하게 확인하면 데이터를 손쉽게 제공받을 수 있을 것이다.
SendOrder을 끝으로 < 2. tr을 통한 데이터 요청 > 부분의 설명을 마무리한다. 다음 글부터는 < 3. 보조지표 구하기 >에 대해 알아보자.
'2. 해외선물 > 2-1. 해외선물 자동매매 연구' 카테고리의 다른 글
(키움증권 해외선물 자동매매 파이썬) 17. 시간별 다른 코드 적용하기 (3) | 2023.10.24 |
---|---|
(키움증권 해외선물 자동매매 파이썬) 16. 현재시간 출력하기 (5) | 2023.10.23 |
(키움증권 해외선물 자동매매 파이썬) 15. 보조지표 구하기 (볼린저밴드, MACD, 스토캐스틱) (0) | 2023.10.22 |
(키움증권 해외선물 자동매매 파이썬) 14. rsi 구하기 (4) | 2023.10.21 |
(키움증권 해외선물 자동매매 파이썬) 12. 주문가능수량 조회 (opw30011) (4) | 2023.10.05 |
(키움증권 해외선물 자동매매 파이썬) 11. 주문가능금액 조회 (opw30009) (4) | 2023.09.30 |
(키움증권 해외선물 자동매매 파이썬) 10. 매도수구분, 진입가격, 청산가격, 평가손익 알아보기(opw30012) (0) | 2023.09.29 |
(키움증권 해외선물 자동매매 파이썬) 9. 1분봉 데이터 받기(opc10002) (2) | 2023.09.28 |