이번에는 지금껏 만든 함수를 활용하여 잔고에 접근하는 방법이다.
오늘 만들 함수를 미리 보면 다음과 같다.
def get_balance_us(self):
path = "uapi/overseas-stock/v1/trading/inquire-present-balance"
url = f"{self.URL_BASE}/{path}"
headers = {
"content-type": "application/json",
"authorization": f"Bearer {self.ACCESS_TOKEN}",
"appkey": self.APP_KEY,
"appsecret": self.APP_SECRET,
"tr_id": "CTRP6504R"
}
params = {
'CANO': self.acc_no,
'ACNT_PRDT_CD': '01',
"WCRC_FRCR_DVSN_CD": "02",
"NATN_CD": "840",
"TR_MKET_CD": "00",
"INQR_DVSN_CD": "00"
}
res = requests.get(url, headers=headers, params=params)
return res.json()
오늘도 추가로 import해야하는 모듈은 없다.
PATH = "uapi/overseas-stock/v1/trading/inquire-present-balance"
URL = f"{self.URL_BASE}/{PATH}"
잔고를 받아올 URL을 작성한다. PATH는 uapi/overseas-stock/v1/trading/inquire-present-balance이며, URL_BASE는 이미 지난번에 __init__에 변수로 작성해 두었으므로, 그걸 활용하도록 하자.
참고로, KIS Developers에서 잔고 조회 파트를 확인해보면, PATH가 uapi/overseas-stock/v1/trading/inquire-balance라고 나와있고, 우리가 입력한 PATH는 체결기준 현재 잔고라고 되어있다. 이는 체결된 주식과 대금은 바로 입고되지 않기 때문인데, 우리의 자산은 체결기준으로 움직이기 때문에 본 글에서는 체결잔고만 다루었다.
headers = {
"content-type": "application/json",
"authorization": f"Bearer {self.ACCESS_TOKEN}",
"appkey": self.APP_KEY,
"appsecret": self.APP_SECRET,
"tr_id": "CTRP6504R"
}
이번에 필요한 headers는 다음과 같다. 특이한 점이 있다면 "authorization" 부분인데 우리가 생성한 ACCESS_TOKEN을 그냥 쓰지 않고 앞부분에 "Bearer "를 추가하였다. KIS에서 인증으로 사용하는 OAuth 2.0 방식이 제안하는 일종의 규약이니 그대로 따르도록 한다. Bearer는 물론 띄어쓰기라도 빠지면, Credential 에러가 발생한다.
"tr_id"에는 실전 투자인 경우 TTTC8434R, 모의 투자인 경우 VTTC8434R를 선택하여 입력한다.
다음은 params를 입력할 차례이나, 설명에 앞서 중요한 부분을 잠시 짚고 가자.
Request </> Header
Element한글명TypeRequiredLengthDescription
content-type | 컨텐츠타입 | String | Y | 40 | application/json; charset=utf-8 |
authorization | 접근토큰 | String | Y | 40 | OAuth 토큰이 필요한 API 경우 발급한 Access token 일반고객(Access token 유효기간 1일, OAuth 2.0의 Client Credentials Grant 절차를 준용) 법인(Access token 유효기간 3개월, Refresh token 유효기간 1년, Oauth 2.0의 Authorization Code Grant 절차를 준용) |
appkey | 앱키 | String | Y | 36 | 한국투자증권 홈페이지에서 발급받은 appkey (절대 노출되지 않도록 주의해주세요.) |
appsecret | 앱시크릿키 | String | Y | 180 | 한국투자증권 홈페이지에서 발급받은 appsecret (절대 노출되지 않도록 주의해주세요.) |
personalseckey | 고객식별키 | String | N | 180 | [법인 필수] 제휴사 회원 관리를 위한 고객식별키 |
tr_id | 거래ID | String | Y | 13 | [실전투자] CTRP6504R [모의투자] VTRP6504R |
tr_cont | 연속 거래 여부 | String | N | 1 | 공백 : 초기 조회 N : 다음 데이터 조회 (output header의 tr_cont가 M일 경우) |
custtype | 고객타입 | String | N | 1 | B : 법인 P : 개인 |
seq_no | 일련번호 | String | N | 2 | [법인 필수] 01 |
mac_address | 맥주소 | String | N | 12 | 법인고객 혹은 개인고객의 Mac address 값 |
phone_number | 핸드폰번호 | String | N | 12 | [법인 필수] 제휴사APP을 사용하는 경우 사용자(회원) 핸드폰번호 ex) 01011112222 (하이픈 등 구분값 제거) |
ip_addr | 접속 단말 공인 IP | String | N | 12 | [법인 필수] 사용자(회원)의 IP Address |
hashkey | 해쉬키 | String | N | 256 | [POST API 대상] Client가 요청하는 Request Body를 hashkey api로 생성한 Hash값 * API문서 > hashkey 참조 |
gt_uid | Global UID | String | N | 32 | [법인 필수] 거래고유번호로 사용하므로 거래별로 UNIQUE해야 함 |
</> Body
Element한글명TypeRequiredLengthDescription
CANO | 종합계좌번호 | String | Y | 8 | 계좌번호 체계(8-2)의 앞 8자리 |
ACNT_PRDT_CD | 계좌상품코드 | String | Y | 2 | 계좌번호 체계(8-2)의 뒤 2자리 |
WCRC_FRCR_DVSN_CD | 원화외화구분코드 | String | Y | 2 | 01 : 원화 02 : 외화 |
NATN_CD | 국가코드 | String | Y | 3 | 000 전체 840 미국 344 홍콩 156 중국 392 일본 704 베트남 |
TR_MKET_CD | 거래시장코드 | String | Y | 2 | [Request body NATN_CD 000 설정] 00 : 전체 [Request body NATN_CD 840 설정] 00 : 전체 01 : 나스닥(NASD) 02 : 뉴욕거래소(NYSE) 03 : 미국(PINK SHEETS) 04 : 미국(OTCBB) 05 : 아멕스(AMEX) [Request body NATN_CD 156 설정] 00 : 전체 01 : 상해B 02 : 심천B 03 : 상해A 04 : 심천A [Request body NATN_CD 392 설정] 01 : 일본 [Request body NATN_CD 704 설정] 01 : 하노이거래 02 : 호치민거래소 [Request body NATN_CD 344 설정] 01 : 홍콩 02 : 홍콩CNY 03 홍콩USD |
INQR_DVSN_CD | 조회구분코드 | String | Y | 2 | 00 : 전체 01 : 일반해외주식 02 : 미니스탁 |
위 테이블은 KIS Developers에서 제공하는 API 사용 메뉴얼 중 해외주식 잔고 조회시 requests에 필요한 headers와 params 관련 인자들이 정리된 부분이다.
(https://apiportal.koreainvestment.com/ → 로그인 → API문서 → 해외주식 주문 → 해외주식 007)
위의 문서를 참조하면 각 정보를 requests 할때, headers와 params를 어떻게 구성해야 하는지 알수 있다. Require 열에 Y 표시가 된 항목들은 반드시 작성해야하는 항목들이며, 본인의 필요에 따라 각 Element들을 Description 참조하여 작성하면 된다.
향후 작성될 함수들은 별도의 언급이 없다면, 필수항목만 작성할 예정이다.
params = {
'CANO': self.acc_no,
'ACNT_PRDT_CD': '01',
"WCRC_FRCR_DVSN_CD": "02",
"NATN_CD": "840",
"TR_MKET_CD": "00",
"INQR_DVSN_CD": "00"
}
해외계좌 잔고조회에 해당하는 파라미터는 총 6개로 선택항목 없이 모두 반드시 작성해야한다. 'CANO'는 계좌번호 입력란으로 본인의 계좌번호 앞 8자리를 str로 입력하면 되는데, 본인은 __init__에 변수로 넣어두었다. 누구나 쓸수 있는 함수를 만들고 싶다면 입력을 따로 받아도 되나, 그렇지 않다면 여러번 반복 활용될 숫자이므로 숫자를 직접 넣어두거나 변수로 활용하도록 하자.
나머지 항목들은 미국 주식 계좌 조회를 위한 param들로 세팅해 두었다. 혹여 다른 국가의 주식도 매매하는 분들이 있다면, Description을 참조하여 숫자를 바꿔보길 바란다. (전혀 어렵지 않다!)
res = requests.get(url, headers=headers, params=params)
return int(res.json()['output3']['tot_asst_amt'])
끝으로는 다른 함수들과 마찬가지로 requests로 정보를 받고, 필요한 부분을 발췌한다.
'체결기준잔고조회' 항목에서 얻을 수 있는 정보는 매우 다양하다. 원한다면 보유 종목과 평가금액을 뽑아 깔끔하게 테이블로 만들수도 있다.
이 함수에서는 외화 총자산이 알고싶었으므로, ['output3']['tot_asst_amt'] 부분을 발췌하였다. 숫자는 str보다는 int인 편이 여러모로 편리하므로 int값으로 리턴하도록 바꿔주는 것으로 balance함수 작성 완료.
지금까지 작성한 코드를 종합하면 아래와 같다.
import requests
import json
class ki():
def __init__(self):
self.APP_KEY = ""
self.APP_SECRET = ""
self.URL_BASE = "https://openapi.koreainvestment.com:9443"
self.headers = {"content-type": "application/json"}
self.body = {"grant_type": "client_credentials",
"appkey": self.APP_KEY,
"appsecret": self.APP_SECRET}
self.PATH = "oauth2/tokenP"
self.URL = f"{self.URL_BASE}/{self.PATH}"
self.res = requests.post(self.URL, headers=self.headers, data=json.dumps(self.body))
self.ACCESS_TOKEN = self.res.json()["access_token"]
def hashkey(self, data):
PATH = "uapi/hashkey"
URL = f"{self.URL_BASE}/{PATH}"
headers = {
'content-Type': 'application/json',
'appKey': self.APP_KEY,
'appSecret': self.APP_SECRET,
}
res = requests.post(URL, headers= headers, data=json.dumps(data))
hashkey = res.json()["HASH"]
return hashkey
def get_balance_us(self):
path = "uapi/overseas-stock/v1/trading/inquire-present-balance"
url = f"{self.URL_BASE}/{path}"
headers = {
"content-type": "application/json",
"authorization": f"Bearer {self.ACCESS_TOKEN}",
"appkey": self.APP_KEY,
"appsecret": self.APP_SECRET,
"tr_id": "CTRP6504R"
}
params = {
'CANO': self.acc_no,
'ACNT_PRDT_CD': '01',
"WCRC_FRCR_DVSN_CD": "02",
"NATN_CD": "840",
"TR_MKET_CD": "00",
"INQR_DVSN_CD": "00"
}
res = requests.get(url, headers=headers, params=params)
return int(res.json()['output3']['tot_asst_amt'])
참조한 사이트