대신증권 크레온(Creon) HTS 브리지 서버 만들기 (Django 편)

2020-05-31 • quant대신증권, creon, 크레온, hts, 서버, 장고, django • 4 min read

일전에 Flask로 크레온 HTS Bridge 서버 만드는 방법을 이 포스트에서 다뤘었습니다. 이번 포스트에서는 같은 HTS Bridge 서버를 Django로 만드는 방법을 다룹니다.

크레온에 요청을 보내는 creon.py는 재사용합니다.

먼저 Django를 설치합니다.

$ pip install django

설치 결과로 다음처럼 "Successfully installed" 메시지를 확인합니다.

Installing collected packages: asgiref, sqlparse, django
Successfully installed asgiref-3.2.7 django-3.0.6 sqlparse-0.3.1

그리고 한번 더 python 프롬프트에서 django를 임포트해봅니다.

Python 3.7.6 (default, Jan  8 2020, 19:59:22)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> print(django.get_version())
3.0.6

이렇게 버전을 확인하여 설치 및 임포트가 정상적으로 되는지 최종 확인합니다.

이제 프로젝트를 생성할 차례입니다. Flask에 비해서 Django는 초기 설정이 조금 더 복잡합니다. 문서에 따르면 Flask의 기본 내장 웹서버는 개발용으로만 사용하도록 권장하고 있습니다. Django는 Production 서버로 사용하기 적합합니다. (Django 튜토리얼)[https://docs.djangoproject.com/en/3.0/intro/tutorial01/]을 따라서 Django 초기 설정을 해보실길 추천드립니다.

다음과 같이 프로젝트를 생성할 수 있습니다.

$ django-admin startproject <project_name>
$ django-admin startproject systrader

여기서는 project_namesystrader로 정하여 프로젝트를 생성했습니다. 그럼 프로젝트 이름의 폴더가 생성되고 그 아래에 manage.py와 같은 프로젝트 이름의 폴더가 있습니다.

./systrader/systrader에는 다음과 같은 파일들이 생성되어 있을 것입니다.

  • __init__.py
  • asgi.py
  • settings.py
  • urls.py
  • wsgi.py

여기서 주로 수정해야 할 파일은 urls.py 입니다. 그리고 일반적으로 요청을 처리할 views.py 파일을 추가로 생성해야 합니다.

urls.py에서 웹서버가 처리할 요청 URL들을 정의합니다. 크레온 API와의 연결을 관리하기 위한 인터페이스 connection을 시작으로 종목코드, 종목상태, 종목차트, 시장차트, 종목 자질, 공매도, 투자자매매현황 데이터를 받기 위한 인터페이스들이 있습니다.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('connection', bridge.handle_connection),
    path('stockcodes', bridge.handle_stockcodes),
    path('stockstatus', bridge.handle_stockstatus),
    path('stockcandles', bridge.handle_stockcandles),
    path('marketcandles', bridge.handle_marketcandles),
    path('stockfeatures', bridge.handle_stockfeatures),
    path('short', bridge.handle_short), 
    path('investorbuysell', bridge.handle_investorbuysell), 
]

views.py는 이름과는 달리 MVC(Model-View-Controller) 모델에서 Controller 역할을 하는 파일입니다. Django의 views.py는 MVC에서의 View를 렌더링한다는 관점에서 이름을 정한 것으로 추측됩니다.

여기서는 views.py 대신 bridge.py로 이름을 정했습니다.

먼저 크레온 API를 호출하는 클래스인 Creon 인스턴스를 생성합니다.

import json
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from creon import Creon
import constants


c = Creon()

크레온 HTS API와의 연결 관련 기능을 처리하는 인터페이스를 생성합니다. RESTful 형식으로 GET 방식으로 요청하면 연결 상태를 반환하고 POST 방식은 ID, 비밀번호, 공인인증서 비밀번호를 id, pwd, pwdcert로 각각 받아서 크레온 API와의 연결을 시도합니다. DELETE 방식으로 요청하면 크레온 API와의 연결을 해제합니다.

@csrf_exempt 
def handle_connection(request):
    if request.method == 'GET':
        # check connection status
        return JsonResponse(c.connected(), safe=False)
    elif request.method == 'POST':
        # make connection
        data = json.loads(request.body)
        _id = data['id']
        _pwd = data['pwd']
        _pwdcert = data['pwdcert']
        return JsonResponse(c.connect(_id, _pwd, _pwdcert), safe=False)
    elif request.method == 'DELETE':
        # disconnect
        return JsonResponse(c.disconnect(), safe=False)

크레온 API와 연결되면 다양한 데이터를 획득할 수 있습니다. 다음은 주식시장 별 종목코드를 획득하기 위한 함수입니다. market 인자로 kospi 또는 kosdaq를 받아서 해당 시장에 소속된 종목코드를 반환합니다.

def handle_stockcodes(request):
    c.wait()
    market = request.GET.get('market')
    if market == 'kospi':
        return JsonResponse(c.get_stockcodes(constants.MARKET_CODE_KOSPI), safe=False)
    elif market == 'kosdaq':
        return JsonResponse(c.get_stockcodes(constants.MARKET_CODE_KOSDAQ), safe=False)
    else:
        return HttpResponse('"market" should be one of "kospi" and "kosdaq".', status_code=400)

다음은 종목 상태를 반환하는 함수입니다. 인자 code로 종목코드를 입력받아서 해당 종목의 상태를 반환받습니다. 상태에는 관리 종목 여부, 거래정지 여부 등이 포함됩니다.

def handle_stockstatus(request):
    c.wait()
    stockcode = request.GET.get('code')
    if not stockcode:
        return HttpResponse('"code" should be provided.', status_code=400)
    res = c.get_stockstatus(stockcode)
    return JsonResponse(res)

다음은 종목의 일봉차트를 획득하기 위한 함수입니다. 획득할 차트의 기간을 date_from, date_to인자로 지정할 수 있고 획득할 봉 개수를 n 인자로 지정할 수도 있습니다. 날짜는 yyyyMMdd 형태여야 합니다.

def handle_stockcandles(request):
    c.wait()
    stockcode = request.GET.get('code')
    n = request.GET.get('n')
    if n:
        n = int(n)
    date_from = request.GET.get('date_from')
    date_to = request.GET.get('date_to')
    if not (n or date_from):
        return HttpResponse('Need to provide "n" or "date_from" argument.', status_code=400)
    res = c.get_chart(stockcode, target='A', unit='D', n=n, date_from=date_from, date_to=date_to)
    return JsonResponse(res, safe=False)

다음은 시장의 일봉차트를 획득하기 위한 함수입니다. 위 종목차트 함수와 거의 같습니다. codekospi, kosdaq, kospi200을 입력할 수 있습니다.

def handle_marketcandles(request):
    c.wait()
    marketcode = request.GET.get('code')
    n = request.GET.get('n')
    if n:
        n = int(n)
    date_from = request.GET.get('date_from')
    date_to = request.GET.get('date_to')
    if marketcode == 'kospi':
        marketcode = '001'
    elif marketcode == 'kosdaq':
        marketcode = '201'
    elif marketcode == 'kospi200':
        marketcode = '180'
    else:
        return HttpResponse('"code" should be one of "kospi", "kosdaq", and "kospi200".', status_code=400)
    if not (n or date_from):
        return HttpResponse('Need to provide "n" or "date_from" argument.', status_code=400)
    res = c.get_chart(marketcode, target='U', unit='D', n=n, date_from=date_from, date_to=date_to)
    return JsonResponse(res, safe=False)

다음은 종목의 다양한 자질값들을 획득하는 함수입니다. 데이터에는 PER, 배당률, 공매도수량 등의 매우 다양한 자질들이 포함됩니다.

def handle_stockfeatures(request):
    c.wait()
    stockcode = request.GET.get('code')
    if not stockcode:
        return HttpResponse('"code" should be provided.', status_code=400)
    res = c.get_stockfeatures(stockcode)
    return JsonResponse(res)

다음은 종목의 일단위 공매도 데이터를 획득하는 함수입니다. 종목코드와 데이터 일수를 입력으로 받습니다.

def handle_short(request):
    c.wait()
    stockcode = request.GET.get('code')
    n = request.GET.get('n')
    if n:
        n = int(n)
    if not stockcode:
        return HttpResponse('"code" should be provided.', status_code=400)
    res = c.get_shortstockselling(stockcode, n=n)
    return JsonResponse(res, safe=False)

다음은 종목의 투자자별 매매현황을 획득하는 함수입니다.

def handle_investorbuysell(request):
    c.wait()
    stockcode = request.GET.get('code')
    n = request.GET.get('n')
    if n:
        n = int(n)
    if not stockcode:
        return HttpResponse('"code" should be provided.', status_code=400)
    res = c.get_investorbuysell(stockcode, n=n)
    return JsonResponse(res, safe=False)

이렇게 크레온 API 브리지 서버를 구축하고 다음과 같이 실행하여 사용할 수 있습니다.

$ python manage.py runserver 0.0.0.0:8000 --noreload

manage.pyrunserver 명령으로 웹서버를 실행할 수 있으며 인자로 웹서버의 listen IP, 포트를 입력할 수 있고 --noreload 옵션을 추가하여 웹서버 코드를 수정했을때 자동으로 리로드 되는 것을 막을 수 있습니다.

위에서 소개한 브리지 서버는 간단한 기능 몇개만 구현해 놓은 것이며 다양한 기능들을 추가해 나갈 예정입니다.