GIL's LAB

오픈 API를 이용한 주식 데이터 수집하기 (2) 일별 코스피/코스닥 데이터 수집 방법 본문

퀀트 투자/데이터 수집

오픈 API를 이용한 주식 데이터 수집하기 (2) 일별 코스피/코스닥 데이터 수집 방법

GIL~ 2021. 9. 3. 21:48

이번 포스팅에서는 이전 포스팅에서 소개한 Cybos Plus를 가지고 일별 코스피/코스닥 데이터를 수집하는 방법을 소개한다. 이 방법을 바탕으로 수집한 데이터는 다양한 퀀트 실험에 활용할 예정이다.

 

가장 먼저, Cybos Plus를 실행해서 로그인한다. 

그리고나서 주피터를 32bit 환경에서 관리자권한으로 실행한다. 

즉, 아나콘다 프롬프트를 관리자권한으로 실행한 뒤, 아래 명령어를 순차적으로 입력하여 주피터를 실행한다.

 

set CONDA_FORCE_32BIT=1

conda activate py36_32

jupyter notebook

 

당연하지만, 이전 포스팅에서 py36_32 환경을 구축하고 그 환경에 주피터 노트북을 설치해야만 위 명령어가 정상적으로 작동한다. 이제 본격적으로 Cybos Plus를 사용해보자.

 

가장 먼저, 아래 코드를 실행하여 정상적으로 CyBos에 연결되었는지 확인한다.

출력 결과가 1이 나오면 정상적으로 연결된 것이다. 0이 나오거나 오류가 나오면 이전 포스팅을 참고하기 바란다.

# 연결 확인
import win32com.client
instCpCybos = win32com.client.Dispatch("CpUtil.CpCybos")
print(instCpCybos.IsConnect)

 

그 다음으로 코스피와 코스닥 종목의 코드를 가져올 것이다. 이 코드는 추후 데이터를 가져오는데 입력으로 사용될 것이다. 코드 내 주요 라인 (참고로 라인 번호는 수작업으로 입력했습니다. 어떻게하는게 좋을지 알려주시면 감사하겠습니다)을 설명하면 다음과 같다.

  • 라인 1: 이 코드는 win32com.client의 Dispatch 함수를 사용하여 CpUtil.CpCodeMgr이라는 COM 객체를 codemgr라는 변수에 저장한다. 어렵게 생각할 것 없이 누군가 잘 만들어놓은 클래스에서 인스턴스를 만들었다고 생각하면 편하다. 
  • 라인 2 ~ 3: GetStockListByMarket 함수를 사용하여 코드 목록을 가져온다. 1을 입력하면 코스피의 종목이, 2를 입력하면 코스닥의 종목이 반환된다.
# 종목 코드 가져오기
1 codemgr = win32com.client.Dispatch("CpUtil.CpCodeMgr")

2 kospi_code = codemgr.GetStockListByMarket(1) # 1: 코스피
3 kosdaq_code = codemgr.GetStockListByMarket(2) # 2: 코스닥

4 print(kospi_code[:5])
5 print(kosdaq_code[:5])

# 리스트로 형변환
6 kospi_code = list(kospi_code)
7 kosdaq_code = list(kosdaq_code)

이제 종목 코드를 종목명으로 나타낼 것이다. 사실 필수적인 작업은 아니지만, 데이터 이름을 종목명으로 저장하는 것이 더 적절할 것 같아 이 단계를 수행한다. 코드를 종목명으로 바꾸는 것은 앞서 생성한 인스턴스의 CodeToName이라는 함수를 사용하면 손쉽게 바꿀 수 있다. 

# CpStockCode를 사용하여 종목 코드를 종목명으로 나타내기
kospi_name = [codemgr.CodeToName(code) for code in kospi_code]
kosdaq_name = [codemgr.CodeToName(code) for code in kosdaq_code]

print(kospi_name[:5])
print(kosdaq_name[:5])

이제 본격적으로 데이터를 요청하여 가져오는 작업을 수행해보자. 설명을 위해, 핵심적인 코드 구조만 먼저 설명한다. 가장 먼저 CpSysDib.StockChart 객체를 아래와 같이 생성한다.

instance = win32com.client.Dispatch("CpSysDib.StockChart")

그 후에 SetInputValue를 이용하여 가져올 데이터에 대한 정보를 전달한다.

이 함수는 인자로 튜플을 받는데, 튜플의 첫 번째 요소는 항목, 두 번째 요소는 그 값이다.

예를 들어, (0, code)는 종목코드에 code라는 값을 전달한 것이며, (1, ord('1'))은 데이터를 기간으로 입력(예: 20200101~20210101)하여 받을 것인지, 개수로 입력(예: 최근 500개)하여 받을 것인지 여부를 전달한 것이다. 참고로 이 API에 대한 도움말이 더 이상 제공되지 않아, 체계적으로 정리하기는 어려운 상황이다.

아무튼 아래와 같이 입력하면, code라는 종목을 2016년 9월 1일부터 2021년 8월 31일까지, 날짜, 시가, 고가, 저가, 종가, 거래량을 일별로 수집하도록 설정된다. 이제 마지막으로 BlockRequest를 사용하여 데이터를 받아온다.

    instance.SetInputValue(0, code)  # 종목코드
    instance.SetInputValue(1, ord('1'))  # 기간으로 받기
    instance.SetInputValue(2, 20210831) # to 
    instance.SetInputValue(3, 20160901) # from
    instance.SetInputValue(5, [0, 2, 3, 4, 5, 8])  # 날짜, 시가, 고가, 저가, 종가, 거래량
    instance.SetInputValue(6, ord('D'))  # 차트 주기 - 일간 차트
    instance.SetInputValue(9, ord('1'))  # 수정주가 사용
    instance.BlockRequest() # 요청 수행

위 코드가 구동되면, instance에서 사용할 수 있는 메서드가 추가된다. 

먼저, instance.GetHeaderValue(3)로는 수집한 데이터 개수를 알 수 있다 (참고: 일반적인 배열로 데이터가 나오는 것이 아니기에, 그 개수를 알기 위한 함수가 따로 필요함).

또, instance.GetDataValue(열 인덱스, 행 인덱스)를 이용하여 데이터 값을 가져올 수 있다. 특이한 점은 열 인덱스가 앞에 나온다는 점이다. 예를 들어, 위 코드를 구동한 뒤 instance.GetDataValue(1, 10)을 하면 수집한 데이터의 10번째 레코드의 시가가 나오게 된다. 

 

이제 이 내용을 종합하여 코스피의 모든 종목의 데이터를 수집하는 코드를 살펴보자.

이 코드는 code와 name이라는 변수가 kospi_code와 kospi_name을 순회하면서, 데이터를 요청하고 그 데이터를 지정된 path에 name이라는 이름의 csv 파일로 저장한다. 

코드에 대한 자세한 설명은 주석으로 대체하고, 몇 가지 포인트만 짚고 이 포스팅을 마치고자 한다.

 

  • 32bit에서 잘 구동되지 않는 pandas를 사용하지 않기 위해, open 함수를 사용하여 데이터를 직접 라이팅했다.
  • 리스트 타입의 레코드에 있는 요소를 라이팅하기 위해, map 함수와 join 함수를 사용했다. 굉장히 자주 사용되는 테크닉이기에 언급하고 넘어간다.
  • time.sleep 함수를 사용하여 데이터 하나를 저장하고 5초간 쉰다. 그 이유는 쉬지 않고 요청이 들어가는 경우에는 아무리 API여도 차단될 위험이 있기 때문이다. 
import time
instance = win32com.client.Dispatch("CpSysDib.StockChart")
path = "C:/Users/Gilseung/Desktop/Jupyter/GILLAB/QUANT_DATA/일별주가/KOSPI/"
col_name = ["날짜", "시가", "고가", "저가", "종가", "거래량"]

for code, name in zip(kospi_code, kospi_name):
    print(code, name)
    ## 파일 생성 (Pandas 설치에 오류가 나는 경우가 많아서 직접 라이팅)
    f = open(path + name + ".csv", "w")
    f.write(",".join(col_name))
    f.write("\n")
    
    ## 데이터 요청
    instance.SetInputValue(0, code)  # 종목코드
    instance.SetInputValue(1, ord('1'))  # 기간으로 받기
    instance.SetInputValue(2, 20210831) # to 
    instance.SetInputValue(3, 20160901) # from
    instance.SetInputValue(5, [0, 2, 3, 4, 5, 8])  # 날짜, 시가, 고가, 저가, 종가, 거래량
    instance.SetInputValue(6, ord('D'))  # 차트 주기 - 일간 차트
    instance.SetInputValue(9, ord('1'))  # 수정주가 사용
    instance.BlockRequest() # 요청 수행
    
    numData = instance.GetHeaderValue(3) # 요청한 데이터 개수
    
    ## 데이터 저장: instance.GetDataValue(열 인덱스, 행 인덱스)
    for row_ind in range(numData):
        record = [] # record: 데이터 내 하나의 로우 / 초기화하여 컬럼 값을 가져오고 이를 data에 추가
        for col_ind in range(len(col_name)): 
            record.append(instance.GetDataValue(col_ind, row_ind))
            
        f.write(','.join(list(map(str, record)))) # record에 있는 모든 요소를 문자열로 만들어서 조인하여 라이팅
        
        if row_ind != numData - 1: # 마지막에는 줄바꿈을 입력하지 않음
            f.write('\n')
    
    f.close()
    time.sleep(5)

 

전체 완성된 코드는 아래 주피터 파일을 확인하길 바란다. 완성된 코드에서는 코스닥 데이터도 수집한다.

코스피_코스닥_일별데이터수집.ipynb
0.01MB

 

수집하고 싶은 금융 데이터나 실험하고 싶은 퀀트 관련 아이디어가 있으면 댓글로 남겨주세요! 
관련 포스팅을 준비하도록 하겠습니다!

 

Comments