[파이썬] 선물 분 차트 그리기(matplotlib)

2018. 2. 18. 19:13개발/파이썬

CYBOS PLUS/CREON PLUS API 를 이용하여 간단한 분차트를 그리는 예제 코드입니다 

차트는 matplotlib 라이브러리를 이용했습니다 

matplotlib 사용방법이 은근히 까다롭네요 

 

화면 좌 상단에는 파생 종목 코드 리스트를 콤보에 넣어 종목을 선택할 수 있게 했습니다. 

 

 

[전체 코드]

import datetime
import sys
import ctypes
import time
 
import numpy as np
from PyQt5.QtWidgets import *
import win32com.client
import pandas as pd
import os
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.finance as matfin
import matplotlib.ticker as ticker
 
C_DT = 0    # 일자
C_TM = 1    # 시간
C_OP = 2    # 시가
C_HP = 3    # 고가
C_LP = 4    # 저가
C_CP = 5    # 종가
C_VL = 6    # 거래량
C_MA5 = 7    # 5일 이동평균
C_MA10 = 8    # 10일 이동평균
C_MA20 = 9    # 20일 이동평균
 
 
 
g_objCodeMgr = win32com.client.Dispatch('CpUtil.CpCodeMgr')
g_objCpStatus = win32com.client.Dispatch('CpUtil.CpCybos')
g_objCpTrade = win32com.client.Dispatch('CpTrade.CpTdUtil')
g_objFutureMgr = win32com.client.Dispatch("CpUtil.CpFutureCode")
def InitPlusCheck():
    # 프로세스가 관리자 권한으로 실행 여부
    if ctypes.windll.shell32.IsUserAnAdmin():
        print('정상: 관리자권한으로 실행된 프로세스입니다.')
    else:
        print('오류: 일반권한으로 실행됨. 관리자 권한으로 실행해 주세요')
        return False
 
    # 연결 여부 체크
    if (g_objCpStatus.IsConnect == 0):
        print("PLUS가 정상적으로 연결되지 않음. ")
        return False
 
    # 주문 관련 초기화
    if (g_objCpTrade.TradeInit(0) != 0):
        print("주문 초기화 실패")
        return False
 
    return True
 
 
 
 
class CpFutureChart:
    def __init__(self):
        self.objFutureChart = win32com.client.Dispatch("CpSysDib.FutOptChart")
 
 
    # 차트 요청 - 분간, 틱 차트
    def RequestMT(self, code, dwm, count, caller):
        # 연결 여부 체크
        bConnect = g_objCpStatus.IsConnect
        if (bConnect == 0):
            print("PLUS가 정상적으로 연결되지 않음. ")
            return False
 
        self.objFutureChart.SetInputValue(0, code)  # 종목코드
        self.objFutureChart.SetInputValue(1, ord('2'))  # 개수로 받기
        self.objFutureChart.SetInputValue(4, count)  # 조회 개수
        self.objFutureChart.SetInputValue(5, [0, 1, 2, 3, 4, 5, 8])  # 요청항목 - 날짜, 시간,시가,고가,저가,종가,거래량
        self.objFutureChart.SetInputValue(6, dwm)  # '차트 주기 - 분/틱
        self.objFutureChart.SetInputValue(7, 1)  # 분틱차트 주기
        self.objFutureChart.SetInputValue(8, ord('1'))  # 갭보정
        self.objFutureChart.SetInputValue(9, ord('1'))  # 수정주가 사용
        self.objFutureChart.BlockRequest()
 
        rqStatus = self.objFutureChart.GetDibStatus()
        rqRet = self.objFutureChart.GetDibMsg1()
        print("통신상태", rqStatus, rqRet)
        if rqStatus != 0:
            exit()
 
        len = self.objFutureChart.GetHeaderValue(3)
 
        caller.chartData = {}
        caller.chartData[C_DT] = []
        caller.chartData[C_TM] = []
        caller.chartData[C_OP] = []
        caller.chartData[C_HP] = []
        caller.chartData[C_LP] = []
        caller.chartData[C_CP] = []
        caller.chartData[C_VL] = []
        caller.chartData[C_MA5] = []
        caller.chartData[C_MA10] = []
        caller.chartData[C_MA20] = []
 
        for i in range(len):
            caller.chartData[C_DT].insert(0, self.objFutureChart.GetDataValue(0, i))
            caller.chartData[C_TM].insert(0, self.objFutureChart.GetDataValue(1, i))
            caller.chartData[C_OP].insert(0, self.objFutureChart.GetDataValue(2, i))
            caller.chartData[C_HP].insert(0, self.objFutureChart.GetDataValue(3, i))
            caller.chartData[C_LP].insert(0, self.objFutureChart.GetDataValue(4, i))
            caller.chartData[C_CP].insert(0, self.objFutureChart.GetDataValue(5, i))
            caller.chartData[C_VL].insert(0, self.objFutureChart.GetDataValue(6, i))
 
        #print(self.objFutureChart.Continue)
 
 
        return
 
 
 
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
 
        # plus 상태 체크
        if InitPlusCheck() == False:
            exit()
 
 
        # 기본 변수들
        self.chartData = {}
        self.code = ''
        self.isRq = False
 
        self.objChart = CpFutureChart()
 
        self.sizeControl()
        # 선물 종목 코드 추가
        for i in range(g_objFutureMgr.GetCount()):
            code = g_objFutureMgr.GetData(0, i)
            self.comboStg.addItem(code)
        self.comboStg.setCurrentIndex(0)
 
 
 
    def sizeControl(self) :
        # 윈도우 버튼 배치
        self.setWindowTitle("PLUS API TEST")
 
        self.setGeometry(50, 50, 1200, 600)
        self.comboStg = QComboBox()
        #self.comboStg.move(20, nH)
        self.comboStg.currentIndexChanged.connect(self.comboChanged)
        #self.comboStg.resize(100, 30)
        self.label = QLabel('종목코드')
        #self.label.move(140, nH)
 
        # Figure 를 먼저 만들고 레이아웃에 들어 갈 sub axes 를 생성 한다.
        self.fig = plt.Figure()
        self.canvas = FigureCanvas(self.fig)
 
 
        # top layout
        topLayout = QHBoxLayout()
        topLayout.addWidget(self.comboStg)
        topLayout.addWidget(self.label)
        topLayout.addStretch(1)
        # topLayout.addSpacing(20)
 
        chartlayout = QVBoxLayout()
        chartlayout.addWidget(self.canvas)
 
 
        layout = QVBoxLayout()
        layout.addLayout(topLayout)
        layout.addLayout(chartlayout)
        layout.setStretchFactor(topLayout, 0)
        layout.setStretchFactor(chartlayout, 1)
 
        self.setLayout(layout)
 
    # 분차트 받기
    def RequestMinChart(self):
        if self.objChart.RequestMT(self.code, ord('m'), 100, self) == False:
            exit()
 
    def makeMovingAverage(self, maData, interval):
        #maData = []
        for i in range(0, len(self.chartData[C_DT])) :
            if (i < interval) :
                maData.append(float('nan'))
                continue
            sum = 0
            for j in range (0, interval) :
                sum += self.chartData[C_CP][i - j]
            ma = sum / interval
            maData.append(ma)
        # print(maData)
 
 
    def drawMinChart(self):
        # 기존 거 지운다.
        self.fig.clf()
 
        # 211 - 2(행) * 1(열) 배치 1번째
        self.ax1 = self.fig.add_subplot(2,1,1)
        # 212 - 2(행) * 1(열) 배치 2번째
        self.ax2 = self.fig.add_subplot(2,1,2)
 
        ###############################################
        # 봉차트 그리기
        # self.ax1.xaxis.set_major_formatter(ticker.FixedFormatter(schartData[C_TM]))
        matfin.candlestick2_ohlc(self.ax1, self.chartData[C_OP], self.chartData[C_HP], self.chartData[C_LP], self.chartData[C_CP],
                                  width=0.8, colorup='r', colordown='b')
 
 
        ###############################################
        # x 축 인덱스 만들기 - 기본 순차 배열 추가
        x_tick_raw =  [i for i in range(len(self.chartData[C_DT]))]
        # x 축 인덱스 만들기 - 실제 화면에 표시될 텍스트 만들기
        x_tick_labels = []
 
        startDate = 0
        dateChanged = True
        for i in range(len(self.chartData[C_DT])) :
            # 날짜 변경 된 경우 날짜 정보 저장
            date = self.chartData[C_DT][i]
            if (date != startDate) :
                yy, mm = divmod(date, 10000)
                mm,dd = divmod(mm, 100)
                sDate = '%2d/%d '%(mm,dd)
                print(sDate)
                startDate = date
                dateChanged = True
 
            # 0 분 또는 30분 단위로 시간 표시
            hhh, mmm = divmod(self.chartData[C_TM][i], 100)
            stime = '%02d:%02d' % (hhh, mmm)
            if (mmm == 0 or mmm == 30):
                if dateChanged == True:
                    sDate += stime
                    x_tick_labels.append(sDate)
                    dateChanged = False
                else:
                    x_tick_labels.append(stime)
            else:
                x_tick_labels.append('')
 
 
        ###############################################
        # 이동 평균 그리기
        self.ax1.plot(x_tick_raw, self.chartData[C_MA5], label='ma5')
        self.ax1.plot(x_tick_raw, self.chartData[C_MA10], label='ma10')
        self.ax1.plot(x_tick_raw, self.chartData[C_MA20], label='ma20')
 
 
        ###############################################
        # 거래량 그리기
        self.ax2.bar(x_tick_raw, self.chartData[C_VL])
 
        ###############################################
        # x 축 가로 인덱스 지정
        self.ax1.set(xticks=x_tick_raw, xticklabels=x_tick_labels)
        self.ax2.set(xticks=x_tick_raw, xticklabels=x_tick_labels)
 
        self.ax1.grid()
        self.ax2.grid()
        plt.tight_layout()
        self.ax1.legend(loc='upper left')
 
        self.canvas.draw()
 
 
    def comboChanged(self):
        if self.isRq == True:
            return
        self.isRq = True
        self.code = self.comboStg.currentText()
        self.name = g_objFutureMgr.CodetoName(self.code)
        self.label.setText(self.name)
        self.RequestMinChart()
        self.makeMovingAverage(self.chartData[C_MA5], 5)
        self.makeMovingAverage(self.chartData[C_MA10], 10)
        self.makeMovingAverage(self.chartData[C_MA20], 20)
        self.drawMinChart()
        self.isRq = False
 
 
        #self.requestStgID(cur)
 
 
 
 
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()
 

 

가장 까다로웠던 차트 인덱스 그리기 부분

matplotlib 는 X 축을 항상 연속으로 그리게 되어 있어 있습니다 

15:10분 이후에 거래가 없어서 15:15 분 데이터가 있으면 matplotlib 에서는 자동으로 X 축에 15:11~15:14 분을 채워넣네요 

이 부분을 방지하기 위해 가로축 기본 데이터는 그냥 0 ~ 데이터 개수 만큼 순차적으로 세팅하고 

라벨 부분만 원하는 문자열을 채워 넣었습니다.

관련 코드는 아래 부분 입니다.

       ###############################################
        # x 축 인덱스 만들기 - 기본 순차 배열 추가
        x_tick_raw =  [i for i in range(len(self.chartData[C_DT]))]
        # x 축 인덱스 만들기 - 실제 화면에 표시될 텍스트 만들기
        x_tick_labels = []
 
        startDate = 0
        dateChanged = True
        for i in range(len(self.chartData[C_DT])) :
            # 날짜 변경 된 경우 날짜 정보 저장
            date = self.chartData[C_DT][i]
            if (date != startDate) :
                yy, mm = divmod(date, 10000)
                mm,dd = divmod(mm, 100)
                sDate = '%2d/%d '%(mm,dd)
                print(sDate)
                startDate = date
                dateChanged = True
 
            # 0 분 또는 30분 단위로 시간 표시
            hhh, mmm = divmod(self.chartData[C_TM][i], 100)
            stime = '%02d:%02d' % (hhh, mmm)
            if (mmm == 0 or mmm == 30):
                if dateChanged == True:
                    sDate += stime
                    x_tick_labels.append(sDate)
                    dateChanged = False
                else:
                    x_tick_labels.append(stime)
            else:
                x_tick_labels.append('')

 

 

  • 프로필사진
    Psalms232018.05.19 15:37

    감사합니다
    Python을 공부하고있는 초보생입니다
    주식에 관련하여서 자료를 찾다보니 귀한 소스 코드를 올려주셔서 열공중입니다.

    또한 방문한 곳의 사진을 소소하게 올려주셔서 글도 잘읽고 있습니다
    저도 언제 저런곳을 여행해볼수있을지 동경의 대상이 되고있습니다~~^^ 부럽스니당~~ㅠ

    윗글을 보면서 궁금사항들이있어서 메일로 궁금사항을 보내드리고 싶은데.....
    못찾고있습니다.
    자세한 내용을 메일로 요청드리고 싶습니다.

    • 프로필사진
      BlogIcon esstory2018.05.19 21:40 신고

      블로그 댓글로 힘드신 내용인가요 ?
      우선 비밀댓글로라도 간단히 올려주시면 추가 답변 드리겠습니다
      그리고 파이썬 주식 트레이딩 카페에 좋은 코드와 고수들이 많으니 참고해 봐 주세요

      http://cafe.naver.com/pystock

  • 프로필사진
    BlogIcon 호바드2020.05.05 17:34 신고

    안녕하세요? python으로 주식자동거래하는 프로그램을 코딩해 볼려고 여기저기 공부하면서 자료를 참조하여 공부를 하고 있습니다.
    그래프 그리는 소스는 찾기 힘들었는데, 소스코드 공유해 주셔서 정말 감사하게 생각하고 있습니다.

    import matplotlib.finance as matfin 이 부분은 pycharm버젼이 올라가면서 library코드가 바뀐것 같아서
    import mplfinance as matfin
    import matplotlib.pyplot as plt

    이렇게 수정해서 1차 에러는 없앴습니다.
    추가로, 오류나는 부분이 있는데, 해결이 안되서 문의좀 드리겠습니다.
    오래전에 올리신 거라.. 저의 질문을 보실지 모르겠으나, 혹시 보시면, 답변해 주시면 정말 감사하겠습니다.

    g_objCodeMgr = win32com.client.Dispatch('CpUtil.CpCodeMgr')
    g_objCpStatus = win32com.client.Dispatch('CpUtil.CpCybos')
    g_objCpTrade = win32com.client.Dispatch('CpTrade.CpTdUtil')
    g_objFutureMgr = win32com.client.Dispatch("CpUtil.CpFutureCode";)

    이 부분에서 "CpUtil.CpCodeMgr" 이게 잘못된 문자열이라고 하는것 같은데,
    해결 방법을 모르겠습니다.

    C:\ProgramData\Anaconda3\envs\py37_32\python.exe C:/PycharmProjects/MyStockTrader/graph_test.py

    Traceback (most recent call last):
    File "C:\ProgramData\Anaconda3\envs\py37_32\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
    IDispatch = pythoncom.connect(IDispatch)
    pywintypes.com_error: (-2147221005, '잘못된 클래스 문자열입니다.', None, None)

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "C:/PycharmProjects/MyStockTrader/graph_test.py", line 29, in <module>
    g_objCodeMgr = win32com.client.Dispatch('CpUtil.CpCodeMgr')
    File "C:\ProgramData\Anaconda3\envs\py37_32\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
    File "C:\ProgramData\Anaconda3\envs\py37_32\lib\site-packages\win32com\client\dynamic.py", line 117, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
    File "C:\ProgramData\Anaconda3\envs\py37_32\lib\site-packages\win32com\client\dynamic.py", line 94, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
    pywintypes.com_error: (-2147221005, '잘못된 클래스 문자열입니다.', None, None)

    Process finished with exit code 1

  • 프로필사진
    BlogIcon 호바드2020.05.05 17:47 신고

    아, 내용을 다시 보니, CREON API를 사용하네요. 저는 키움API로 공부하고 있는데...
    키움 기준으로는 어떻게 고쳐야 할까요? T.T