본문 바로가기

파이썬으로 퀀트 프로그램 만들기 project

한국투자증권 api 사용하기_2

728x90

#사전준비

먼저 app key와 app secret을 keyring 패키지를 통해 저장해 둡니다. 그리고 base url을 설정하고

접근토큰과 해쉬키 발급 함수를 설정합니다. 이에 대한 자세한 내용은 이전 포스트에 있으니 참조하시기 바랍니다.

 

#주식 및 계좌 잔고 조회하기

#주식 잔고조회
path = "/uapi/domestic-stock/v1/trading/inquire-balance"
url = f"{url_base}/{path}"

headers = {"Content-Type":"application/json", 
           "authorization":f"Bearer {access_tocken}",
           "appKey":app_key,
           "appSecret":app_secret,
           "tr_id":"VTTC8434R"}

params = {
    "CANO": "50095584", # 계좌번호 앞 8 자리
    "ACNT_PRDT_CD": "84", # 계좌번호 뒤 2자리
    "AFHR_FLPR_YN": "N", #시간외 당일가 여부
    "OFL_YN": "", # 공란,
    "INQR_DVSN": "01", # 조회구분
    "UNPR_DVSN": "01", # 단가구분
    "FUND_STTL_ICLD_YN": "N", # 펀드 결제분 포함 여부
    "FNCG_AMT_AUTO_RDPT_YN": "N", # 융자금액 자동상환 여부
    "PRCS_DVSN": "00", #처리구분 (00 전일매매포함)
    "CTX_AREA_FK100": "", #연속조회검색조건100
    "CTX_AREA_NK100": "" #연속조회키100
}

res = requests.post(url, headers=headers, params=params)
res.json()

#res.json()['output1'] # 보유종목
#res.json()['output2'] # 계좌잔고

여기서 res.json()['output1']을 하면 보유종목에 대한 정보도 얻을 수 있습니다.

res.json()['output2'] 를 하면 계좌잔고 정보도 알 수 있습니다.

 

 

#스케줄링

포트폴리오를 교체하는 리밸런싱 작업은 몇 시간 혹은 몇 일에 걸쳐 나누어 해야 시장에 충격을 주지 않으면서 내가 원하는 만큼 매매를 할 수 있습니다.

이를 위해서는 정해진 시간에 정해진 수량을 매수 혹은 매도하도록 계획을 짠 뒤 실행하는 스케줄링에 대해 알 필요가 있습니다. 

스케줄링이란 정해진 시간에 파이썬 스크립트를 자동으로 실행하게 해줍니다. 일반적으로 'schedule'패키지를 이용합니다.

 

import datetime
import schedule
    
def job():
    print(datetime.datetime.now().strftime('%H:%M:%S'))
    print("=========================")
    


schedule.every(3).seconds.do(job) # 3초마다 job 함수 실행

#schedule.get_jobs() #등록된 스케줄 확인

#schedule.clear() #등록된 모든 스케줄 삭제

while True:
    schedule.run_pending()

3초마다 job함수가 실행됩니다. job함수는 현재 시간을 시:분:초 형태로 출력합니다.

특정 시간을 지정해서 함수를 실행할 수 도 있습니다.

 

schedule.clear()

startDt = datetime.datetime.now() + timedelta(seconds=60) # 현재보다 60초 뒤
endDt = datetime.datetime.now() + timedelta(seconds=80) # 현재보다 80초 뒤
time_list = pd.date_range(startDt, endDt, periods=5) #startDt 부터 endDt 까지 5구간으로 나눔

#시:분:초 형태로 변환
time_list_sec = [i.strftime('%H:%M:%S') for i in time_list]

[schedule.every().day.at(i).do(job) for i in time_list_sec]# at 을 통해 특정 시간에 함수 실행

while True:
    schedule.run_pending()
    if datetime.datetime.now() > endDt:
        print('END')
        schedule.clear()
        break

 

 

#포트폴리오 리밸런싱

지금까지 배웠던 내용을 바탕으로 포트폴리오 리밸런싱을 해보겠습니다.

거래하는 종목이나 금액이 얼마 되지 않을때는 지정가 주문 혹은 시장가 주문으로도 체결이 가능합니다.

그러나 종목수가 많고 금액이 커진다면 이러한 방법으로는 체결이 힘듭니다.

큰 금액을 한 번에 시장가로 주문하면 불리한 가격에 체결될 수 있습니다.

지정가로 주문하면 체결이 안될 가능성이 큽니다.

 

하루종일 수십 종목에 대해 계속해서 정정 주문을 내는것도 현실적으로 불가능합니다.

따라서 전문투자자들은 리밸런싱 작업을 할 때 종종 시분할 매매(TWAP)를 사용합니다.

국내에서는 흔히 CD주문이라고도 합니다.

 

예를 들어, 1000주를 매수해야 하면 주문 시간을 여러개로 나누어 조금씩 매수하는 것입니다. 

물량이 너무 많으면 며칠에 걸쳐 나눠 주문을 체결하기도 합니다.

-> 한 번에 주문을 냄으로 인해 발생할 수 있는 시장충격을 최소화할 수 있으며, 미체결될 확률도 줄일 수 있습니다.

 

저는 모의계좌에서 국내 대형주 10개로 구성된 포트폴리오를 시분할로 매수하겠습니다.

그리고 저는 시간을 나누어 1주 단위로 최유리지정가 주문을 하겠습니다. 주문을 계속해서 1주 단위로 나가므로 대부분 체결이 가능하며, 시장에 미치는 영향도 거의 없습니다. 매매는 9시 10분부터 3시까지 하는것을 추천합니다.

 

먼저 필요한 라이브러리를 불러와주시고 사전준비를 해줍시다.

#대형주 10개 종목 모의투자
import requests
import json
import keyring
import pandas as pd
import time
import numpy as np
import datetime
from datetime import timedelta
import schedule

#모의계좌
keyring.set_password('mock_app_key', 'NAME', 'app_key')
keyring.set_password('mock_app_secret', 'NAME', 'app_secret')

# API Key
app_key = keyring.get_password('mock_app_key', 'NAME')
app_secret = keyring.get_password('mock_app_secret', 'NAME')


#접근토큰 발급
url_base = "https://openapivts.koreainvestment.com:29443" #모의투자

headers = {"content-type":"application/json"}
path = "oauth2/tokenP"
body = {"grant_type":"client_credentials",
        "appkey":app_key, 
        "appsecret":app_secret}

url = f"{url_base}/{path}"
res = requests.post(url, headers=headers, data=json.dumps(body)) # url에 데이터 전송
access_tocken = res.json()['access_token']


#해시키 발급
def hashkey(datas):
  path = "uapi/hashkey"
  url = f"{url_base}/{path}"
  headers = {
    'content-Type' : 'application/json',
    'appKey' : app_key,
    'appSecret' : app_secret,
    }
  res = requests.post(url, headers=headers, data=json.dumps(datas))
  hashkey = res.json()["HASH"]

  return hashkey

 

 

현재가구하기, 매매하기, 계좌 잔고조회하기를 함수로 구현해둡시다.

#현재가 구하기
def get_price(ticker):
    path = "uapi/domestic-stock/v1/quotations/inquire-price"
    url = f"{url_base}/{path}"

    headers = {"Content-Type":"application/json", 
            "authorization": f"Bearer {access_tocken}",
            "appKey":app_key,
            "appSecret":app_secret,
            "tr_id":"FHKST01010100"}
    
    #tr_id는 거래 id에 해당하며, 원하는 작업마다 tr_id가 다름
    params = { "fid_cond_mrkt_div_code":"J", "fid_input_iscd": ticker}

    res = requests.get(url, headers=headers, params=params)
    price = res.json()['output']['stck_prpr']
    price = int(price)
    time.sleep(0.15)

    return price

#매수, 매도
def trading(ticker, tr_id):
    path = "uapi/domestic-stock/v1/trading/order-cash"
    url = f"{url_base}/{path}"

    data = {
        "CANO": "12345678", # 계좌번호 앞 8 자리
        "ACNT_PRDT_CD": "01", # 계좌번호 뒤 2자리
        "PDNO": ticker, # 종목코드
        "ORD_DVSN": "03", # 주문방법 | 시장가: 01 , 지정가 : 00 
        "ORD_QTY": "1", # 주문 수량
        "ORD_UNPR": "0", #주문 단가(시장가의 경우0)
    }

    headers = {"Content-Type":"application/json", 
            "authorization":f"Bearer {access_tocken}",
            "appKey":app_key,
            "appSecret":app_secret,
            "tr_id": tr_id,
            "custtype":"P",
            "hashkey" : hashkey(data)}

    res = requests.post(url, headers=headers, data=json.dumps(data))


#계좌 잔고 조회
#모의투자에서는 20종목, 실제계좌에서는 50종목까지 조회가 가능
def check_account():

    output1 = []
    output2 = []
    CTX_AREA_NK100 = "" #이전 조회 output값을 넣으면 다음페이지 조회 가능 -> 연속조회 가능

    while True:        
        path = "/uapi/domestic-stock/v1/trading/inquire-balance"
        url = f"{url_base}/{path}"

        headers = {"Content-Type":"application/json", 
                "authorization":f"Bearer {access_tocken}",
                "appKey":app_key,
                "appSecret":app_secret,
                "tr_id":"VTTC8434R"}
        
        params = {
            "CANO": "12345678", # 계좌번호 앞 8 자리
            "ACNT_PRDT_CD": "01", # 계좌번호 뒤 2자리
            "AFHR_FLPR_YN": "N", #시간외 당일가 여부
            "OFL_YN": "", # 공란,
            "INQR_DVSN": "01", # 조회구분
            "UNPR_DVSN": "01", # 단가구분
            "FUND_STTL_ICLD_YN": "N", # 펀드 결제분 포함 여부
            "FNCG_AMT_AUTO_RDPT_YN": "N", # 융자금액 자동상환 여부
            "PRCS_DVSN": "00", #처리구분 (00 전일매매포함)
            "CTX_AREA_FK100": "", #연속조회검색조건100
            "CTX_AREA_NK100": CTX_AREA_NK100 #연속조회키100
        }

        res = requests.post(url, headers=headers, params=params)
        output1.append(pd.DataFrame.from_records(res.json()['output1']))

        CTX_AREA_NK100 = res.json()['ctx_area_nk100'].strip()

        if CTX_AREA_NK100 == "":
            output2.append(res.json()['output2'][0])
            break
    
    if not output1[0].empty:
        res1 = pd.concat(output1)[['pdno', 'hldg_qty']].rename(columns={
                                                        'pdno': '종목코드',
                                                        'hldg_qty': '보유수량'
                                                    }).reset_index(drop=True)
    else:
        res1 = pd.DataFrame(columns=['종목코드, 보유수량'])

    res2 = output2[0]

    return [res1, res2]

 

 

이제 각 10개 종목을 몇 주씩 매수해야 하는지 계산해봅시다.

# 모델 포트폴리오
mp = pd.DataFrame({
    '종목코드': [
        '005930', # 삼성전자
        '373220', # LG에너지솔루션
        '000660', # Sk하이닉스
        '207940', # 삼성바이오로직스
        '051910', # LG화학
        '035420', # NAVER
        '005380', # 현대차
        '006400', # 삼성SDI
        '035720', # 카카오
        '105560', # KB금융
    ]
})

# 보유 종목과 aum 불러오기
ap, account = check_account()

# 주당 투자 금액
invert_per_stock = int(account['tot_evlu_amt']) * 0.98 / len(mp)

# 매매 구성
target = pd.DataFrame()
if ap.empty:
   target = mp
   target['보유수량'] = 0
   ap = target
else:
    target = mp.merge(ap, on = '종목코드', how='outer')



target['보유수량'] = target['보유수량'].fillna(0).apply(pd.to_numeric)

#현재가 확인
target['현재가'] = target.apply(lambda x: get_price(x.종목코드), axis = 1)

#목표수량 및 투자수량 입력
target['목표수량'] = np.where(target['종목코드'].isin(mp['종목코드'].tolist()),
                          round(invert_per_stock / target['현재가']), 0)
target['투자수량'] = target['목표수량'] - target['보유수량']

 

 

매매를 스케줄에 등록해줄게요

#시간 분할
startDt1 = datetime.datetime.now() + timedelta(minutes=10)
startDt2 = datetime.datetime.now().replace(hour=9,minute=10,second=0,microsecond=0)
startDt = max(startDt1, startDt2)
endDt = datetime.datetime.now().replace(hour=15,minute=0,second=0,microsecond=0) 

# 스케줄 초기화
schedule.clear()

# 스케줄 등록
for t in range(target.shape[0]) : 

    n = target.loc[t, '투자수량']
    position = 'VTTC0802U' if n > 0  else 'VTTC0801U'
    ticker = target.loc[t, '종목코드']

    time_list = pd.date_range(startDt, endDt, periods=abs(n))
    time_list = time_list.round(freq = 's').tolist()
    time_list_sec = [s.strftime('%H:%M:%S') for s in time_list]

    for i in time_list_sec:
        #스케줄러에 등록될 함수에 인자가 들어가는 경우
        #do(함수, 인자1, 인자2,...)
        schedule.every().day.at(i).do(trading, ticker, position)

 

스케줄, 즉 매매 실행하겠습니다.

#스케줄 실행
while True:
    schedule.run_pending()
    if datetime.datetime.now()> endDt:
        print('거래가 완료되었습니다.')
        schedule.clear()
        break