직접 한국투자증권 Open API를 이용한 ‘무언가’를 개발하기 이전에, mojito2 파이썬 라이브러리를 실습하고 어떤 방식으로 개발을 해야할지 감을 잡아보자.
개발관련 지식이 전무한 상황이기 때문에, 다 훔쳐서 내것으로 만들자
1. 객체 생성 및 토근 발행
한국투자증권 Open API를 이용하려면(mojito2를 통해…?) 앞서 생성한 API KEY 와 API SECRET을 이용해서 토근을 발급해야 한다.
mojito2의 KoreaInvestment 클래스 객체를 호출하면 생성자를 통해 자동으로 토큰(token.dat)을 생성하게 되는데 유효 기간이 1일이며 하루가 지나면 다시 생성해야 한다.
KoreaInvestment 인스턴스 생성
투자계좌를 넣는 acc_no 부분에 자신의 계좌번호를 기입, 모의투자계좌라면 - 하이픈 뒤가 없다. 하이픈(-)과 임의의 숫자를 넣지 않으면 에러가 발생.
import mojito
key = "PStmW7....t"
secret = "7gPSHO....="
acc_no = "12345678-0"
# 객체 생성
broker = mojito.KoreaInvestment(
api_key=key,
api_secret=secret,
acc_no=acc_no # 모의투자계좌라면 mock=True 추가
)
KoreaInvestment 인스턴스를 생성하는 관련 코드는 다음과 같다.
++ Java나 C에서는 Class와 동일한 이름으로 Method명을 지정하면 생성자로 선언
++ Python의 생성자는 def init(self, a, b, …):로 선언한다.
class KoreaInvestment:
'''
한국투자증권 REST API
'''
def __init__(self, api_key: str, api_secret: str, acc_no: str,exchange: str = "서울", mock: bool = False):
"""생성자
Args:
api_key (str): 발급받은 API key
api_secret (str): 발급받은 API secret
acc_no (str): 계좌번호 체계의 앞 8자리-뒤 2자리
exchange (str): "서울", "나스닥", "뉴욕", "아멕스", "홍콩", "상해", "심천", "도쿄", "하노이", "호치민"
mock (bool): True (mock trading), False (real trading)
"""
self.mock = mock
self.set_base_url(mock)
self.api_key = api_key
self.api_secret = api_secret
# account number
self.acc_no = acc_no
self.acc_no_prefix = acc_no.split('-')[0]
self.acc_no_postfix = acc_no.split('-')[1]
self.exchange = exchange
# access token
self.access_token = None
if self.check_access_token():
self.load_access_token()
else:
self.issue_access_token()
def set_base_url(self, mock: bool = True):
""" 생략 """
def issue_access_token(self):
# OAuth인증/접근토큰발급
path = "oauth2/tokenP"
url = f"{self.base_url}/{path}"
headers = {"content-type": "application/json"}
data = {
"grant_type": "client_credentials",
"appkey": self.api_key,
"appsecret": self.api_secret
}
resp = requests.post(url, headers=headers, data=json.dumps(data))
resp_data = resp.json()
self.access_token = f'Bearer {resp_data["access_token"]}'
# add extra information for the token verification
now = datetime.datetime.now()
resp_data['timestamp'] = int(now.timestamp()) + resp_data["expires_in"]
resp_data['api_key'] = self.api_key
resp_data['api_secret'] = self.api_secret
# dump access token
with open("token.dat", "wb") as f:
pickle.dump(resp_data, f)
def check_access_token(self):
# Returns: Bool: True: token is valid, False: token is not valid
try:
f = open("token.dat", "rb")
data = pickle.load(f)
f.close()
expire_epoch = data['timestamp']
now_epoch = int(datetime.datetime.now().timestamp())
status = False
if ((now_epoch - expire_epoch > 0) or
(data['api_key'] != self.api_key) or
(data['api_secret'] != self.api_secret)):
status = False
else:
status = True
return status
except IOError:
return False
def load_access_token(self):
with open("token.dat", "rb") as f:
data = pickle.load(f)
self.access_token = f'Bearer {data["access_token"]}'
위 코드에서 다음의 코드를 자세히 살펴보자.
# access token
self.access_token = None
if self.check_access_token():
self.load_access_token()
else:
self.issue_access_token()
self.access_token = None 를 통해서 access_token에 대한 객체를 None으로 초기화 하고 있다. 현재는 access_token 객체 혹은 변수가 값을 가지지 않고 메모리 공간을 할당. 개발에 있어 가독성을 높이고 일관성 유지에 유리함.
이후, check_access_token메서드를 통해 token.dat파일이 존재하는지 확인을 한 후, 있다면 load_access_token를 통해 토큰을 사용하고 없다면 issue_access_token를 통해 token.dat새로 생성한다.
2. 기능 확인하기
2-1. 잔고조회
import pprint
resp = broker.fetch_balance()
pprint.pprint(resp)
** output2에 대한 **
| 키 값 | 의미 |
|---|---|
| asst_icdc_amt | 자산의 총 금액 (일반적으로 특정 자산의 평가 금액) |
| asst_icdc_erng_rt | 자산의 수익률 (수익을 나타내는 비율) |
| bfdy_buy_amt | 이전 거래일의 매수 금액 |
| bfdy_sll_amt | 이전 거래일의 매도 금액 |
| bfdy_tlex_amt | 이전 거래일의 이체 금액 (전환 금액) |
| bfdy_tot_asst_evlu_amt | 이전 거래일의 총 자산 평가 금액 |
| cma_evlu_amt | CMA(종합자산관리계좌)의 평가 금액 |
| d2_auto_rdpt_amt | D2 자동 상환 금액 |
| dnca_tot_amt | DNCA(직접자산관리계좌)의 총 금액 |
| evlu_amt_smtl_amt | 평가 금액 관련 특정 금액 |
| evlu_pfls_smtl_amt | 평가 손실 관련 특정 금액 |
| fncg_gld_auto_rdpt_yn | 금융 골드 자동 상환 여부 (Y/N) |
| nass_amt | 특정 자산의 총 금액 |
| nxdy_auto_rdpt_amt | 다음 거래일의 자동 상환 금액 |
| nxdy_excc_amt | 다음 거래일의 초과 금액 |
| pchs_amt_smtl_amt | 구매 관련 특정 금액 |
| prvs_rcdl_excc_amt | 이전 기록의 초과 금액 |
| scts_evlu_amt | 특정 증권의 평가 금액 |
| thdt_buy_amt | 오늘 거래일의 매수 금액 |
| thdt_sll_amt | 오늘 거래일의 매도 금액 |
| thdt_tlex_amt | 오늘 거래일의 이체 금액 |
| tot_evlu_amt | 총 평가 금액 |
| tot_loan_amt | 총 대출 금액. |
| tot_stln_slng_chgs | 총 스탠딩 대출 수수료 |
| rt_cd | 응답 코드 |
+++ output1의 경우에는 별도로로 보유 종목에 대한 정보를 확인할 수 있는 것 같다.
+++ 현재는 보유하고 있는 정보가 없기 때문에 사용 방법만 기재… 테스트로 주식을 구매한 후 업데이트 예정
resp = broker.fetch_balance()
for comp in resp['output1']:
print(comp['pdno'])
print(comp['prdt_name'])
print(comp['hldg_qty'])
print(comp['pchs_amt'])
print(comp['evlu_amt'])
print("-" * 40)