직접 한국투자증권 Open API를 이용한 ‘무언가’를 개발하기 이전에, mojito2 파이썬 라이브러리를 실습하고 어떤 방식으로 개발을 해야할지 감을 잡아보자.
개발관련 지식이 전무한 상황이기 때문에, 다 훔쳐서 내것으로 만들자

1. 객체 생성 및 토근 발행

한국투자증권 Open API를 이용하려면(mojito2를 통해…?) 앞서 생성한 API KEYAPI 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)

Reference