퀀트투자

한국투자증권 OpenAPI 사용하기(2) - KIS Developers

Like_Me 2024. 2. 25. 15:48

지난 글에서는 한국투자증권의 api를 쉽게 사용할 수 있게 만들어진 mojito 사용법을 알아보았습니다. 하지만 아쉽게도 mojito에 모든 기능들이 다 구현되어 있지는 않아서 추가로 필요한 기능들은 스스로 추가해 주어야 합니다. 그래서 이번에는 현재(2024/02/25) mojito 버전(0.1.6)에 구현되어 있지 않은 기능을 추가하는 방법을 알아보겠습니다. 그러기 위해서는 먼저 mojito를 가상환경에 설치해 주시고, KIS developers에 접속하시면 됩니다. 이번 글에서는 '호가 정보'와 '주문 체결 조회'를 하는 메서드를 추가해 보겠습니다.

우선 호가 정보를 불러오는 함수를 먼저 만들어보겠습니다. API 문서의 국내주식시세->주식현재가 호가/예상 체결에 들어가 보면 호가 정보를 불러오는 url 정보를 볼 수 있습니다(참고로 실전 domain, 모의 domain url 정보가 다른데 이는 이미 mojito에 반영되어 있으므로 그 밑의 URL 정보만 가져오면 됩니다.). 또한 request에 필요한 header와 query parameter를 볼 수 있는데, header에 들어가는 정보는 대부분 mojito 내부에서 이미 처리되어 그냥 넘기기만 하면 되는 정보들이 대부분이고 'tr_id' 정도만 따로 같이 넘기면 됩니다. 한편 query parameter에 해당하는 'FID_COND_MRKT_DIV_CODE'와 ' FID_INPUT_ISCD'는 따로 처리해 주면 되는데, 여기에서는 삼성전자에 대한 정보를 불러오는 것을 예시로 코드를 작성해 보면 아래와 같습니다.

# mojito의 KoreaInvestment 클래스의 메서드로 추가.
def fetch_hoga(self, code:str='005930'):
    # code에 대한 호가창 정보를 return 하는 함수.
    path = "/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn"
	# self.base_url은 모의투자(mock)인지 실전투자인지에 따라 달라진다.
	url = f"{self.base_url}/{path}"
    headers = {
        "content-type": "application/json",
        "authorization": self.access_token,
        "appkey": self.api_key,
        "appsecret": self.api_secret,
        "tr_id": "FHKST01010200" # 주식 현재가호가 에상체결.
    }
    params = {
        "FID_COND_MRKT_DIV_CODE": 'J',
        "FID_INPUT_ISCD": code
    }
    resp = requests.get(url, headers=headers, params=params)
    resp = resp.json()
    return resp

이를 사용해서 정보를 프린트해 보는 코드는 아래와 같습니다.

import mojito
import yaml

print(mojito.__version__)

with open("data/koreainvestment.yaml") as f:
    info_data = yaml.load(f, Loader=yaml.FullLoader)

key = info_data['key']
secret = info_data['secret']
acc_no = info_data['acc_no']

broker = mojito.KoreaInvestment(
    api_key=key,
    api_secret=secret,
    acc_no=acc_no
)

code = '005930' # 삼성전자
hoga_info = broker.fetch_hoga(code)

if hoga_info['rt_cd'] != '0':
    print(f"{code}, 호가 호출 실패 msg : {hoga_info['msg1']}")
# 매수 호가1
maesu_hoga1 = int(hoga_info['output1']['bidp1'])
# 매수 호가 잔량1
maesu_hoga_qnt1 = int(hoga_info['output1']['bidp_rsqn1'])
# 매도 호가1
maedo_hoga1 = int(hoga_info['output1']['askp1'])
# 매도 호가 잔량1
maedo_hoga_qnt1 = int(hoga_info['output1']['askp_rsqn1'])

호가 정보로 리턴되는 값은 크게 output1/output2로 나뉘는데, output1에는 매수/매도 호가 가격과 그 잔량에 대한 정보 등을 제공해 주고, output2에는 주식 최고가, 시가, 체결가 등이 제공됩니다. 저는 output2에서는 현재 체결가에 대한 정보를 쓰고 있는데 이는 동시호가 때 사용하면 유용합니다. 참고로 모든 값이 string으로 리턴되므로 정수로 변환하고 싶으면 위와 같이 int로 감싸주어야 합니다!

다음으로 주식 일별 주문 체결 조회를 하는 방법을 알아보겠습니다. 이는 매수/매도 주문을 하고 해당 주문으로 실제 체결이 얼마큼 됐는지 미체결량은 얼마인지 파악하기 위해서 사용합니다. API 문서의 국내주식주문->주식일별주문체결조회에 들어가면 어떻게 요청을 해야 하는지 나와 있습니다. 호가 정보 요청 때와 같이 url 정보를 가져오고, header에 들어가는 'tr_id' 정보를 가져오는데 여기서는 모의 투자와 실전 투자가 구분되어 들어간다는 것을 유의해야 합니다(또한 당일 주문에 대한 정보만 요청할 거라 3개월 이내에 대한 정보만 고려합니다. 그 이전 정보를 고려하시는 분들은 해당 정보를 다르게 작성하여 요청하시면 됩니다.). Query parameter에는 많은 정보들이 들어가게 되는데 Description에 나온 설명을 읽어보면 쉽게 파악이 됩니다. 모두 고려하여 예시 코드를 작성해 보면 아래와 같습니다.

import datetime
def fetch_chegyul(self, code: str = '005930', ODNO: str = '', CCLD_DVSN: str = '체결',
                SLL_BUY_DVSN_CD: str = '전체'):
    """주식 일별 주문 체결 조회
    Args:
        SLL_BUY_DVSN_CD (str): 매도매수구분코드, (전체, 매도, 매수)
        code (str): 상품번호, 공란: 전체 조회
        CCLD_DVSN (str): 체결구분, (전체, 체결, 미체결)
        ODNO (str): 주문번호, "" (Null 값 설정)
    """
    CCLD_DVSN = {"전체": "00", "체결": "01", '미체결': '02'}[CCLD_DVSN]
    SLL_BUY_DVSN_CD = {"전체": "00", "매도": "01", '매수': '02'}[SLL_BUY_DVSN_CD]
    path = "/uapi/domestic-stock/v1/trading/inquire-daily-ccld"
    url = f"{self.base_url}/{path}"
    headers = {
        "content-type": "application/json",
        "authorization": self.access_token,
        "appkey": self.api_key,
        "appsecret": self.api_secret,
        "tr_id": 'VTTC8001R' if self.mock else "TTTC8001R"  # 주식 일별 주문 체결 조회(3개월이내)
    }
    params = {
        'CANO': self.acc_no_prefix,
        'ACNT_PRDT_CD': self.acc_no_postfix,
        'INQR_STRT_DT': datetime.datetime.now().strftime('%Y%m%d'),  # YYYYMMDD
        'INQR_END_DT': datetime.datetime.now().strftime('%Y%m%d'),  # YYYYMMDD
        'SLL_BUY_DVSN_CD': SLL_BUY_DVSN_CD,  # 매도매수구분코드, 00:전체, 01:매도, 02:매수
        'INQR_DVSN': '00',  # 조회구분, 00:역순, 01:정순
        'PDNO': code,  # 상품번호, 공란: 전체 조회
        'CCLD_DVSN': CCLD_DVSN,  # 체결구분, 00:전체, 01:체결, 02:미체결
        'ORD_GNO_BRNO': '',  # 주문채번지점번호, 	"" (Null 값 설정)
        'ODNO': ODNO,  # 주문번호, "" (Null 값 설정)
        'INQR_DVSN_3': '00',  # 조회구분3, 00:전체, 01:현금, 02:융자, 03:대출, 04:대주
        'INQR_DVSN_1': '',
        # 연속조회검색조건100, 공란 : 최초 조회시, 이전 조회 Output CTX_AREA_FK100 값 : 다음페이지 조회시(2번째부터)
        "CTX_AREA_FK100": "",
        # 연속조회키100, 공란 : 최초 조회시, 이전 조회 Output CTX_AREA_NK100 값 : 다음페이지 조회시(2번째부터)
        "CTX_AREA_NK100": ""
    }
    resp = requests.get(url, headers=headers, params=params)
    resp = resp.json()
    return resp

코드가 길긴 하지만, 설명이 많아서 그런 것이고 실제 중요한 변수는 함수에 넣어주는 code, ODNO(주문번호), CCLD_DVSN(체결 구분), SLL_BUY_DVSN_CD(매수/매도 구분 코드)입니다. 이는 주문한 정보를 잘 가지고 있다가 체결 정보가 궁금할 때 전달하여 정보를 받으면 됩니다. 메서드 사용 예시 코드는 아래와 같습니다.

code = "005930" # 삼성전자 코드
# 매수 주문
order = broker.create_limit_buy_order(
    symbol=code, 
    price=60000,  # 지정가 매수시 가격단위를 고려해야 함.
    quantity=5
)

# 체결 정보 조회
chegyul_info = broker.fetch_chegyul(
    code, order['output']['ODNO'], '체결', '전체')
# 총체결금액
tot_ccld_amt = int(chegyul_info['output1'][0]['tot_ccld_amt'])
# 총체결수량
tot_ccld_qty = int(chegyul_info['output1'][0]['tot_ccld_qty'])

체결 정보 조회도 리턴되는 값이 크게 output1/output2로 나뉘는데, output1이 조회한 주문번호(ODNO)에 대해서 체결된 정보를 전달해 주고, output2는 해당 종목(code)에 대해 체결된 다른 정보까지 합산해서 보여줍니다. 그러므로 똑같은 ' tot_ccld_amt'여도 output1, output2에서 나오는 값이 다를 수 있다는 점을 주의해야 합니다.