본문 바로가기

xingAPI/자동 매매 프로그램 만들기 (PYQT5)

[xingAPI, 파이썬, PYQT5 - QtChart] 주식 이동평균선 만들기

 

GitHub - mujomboy/xingProject

Contribute to mujomboy/xingProject development by creating an account on GitHub.

github.com

 

 

 

[Git] Github 에 있는 파이썬 프로젝트 파이참(PyCharm) 으로 가져오기

1. 파이참에 GitHub Repository URL 연결하기 파이참을 열어 줍니다... 만약 파이참을 열었는데 바로 프로젝트가 오픈 되었다면... 프로젝트를 닫아 주세요. Get From VCS 버튼을 클릭해 줍니다. URL 입력 후

bysik1109.tistory.com

 

 

  • 이동평균선 조회하기  [t8412] 전체 코드
더보기
import sys
from math import isnan

import pythoncom
import win32com.client
from PyQt5.QtChart import QChart, QChartView, QCandlestickSeries, QCandlestickSet, QDateTimeAxis, \
    QValueAxis, QLineSeries
from PyQt5.QtCore import QSize, Qt, QDateTime
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
from win32api import Sleep
from pandas import DataFrame


# 세션 클래스
class SessionEvents:

    def __init__(self):
        self.state = ""
        self.msg = ""

    def OnLogin(self, code, msg):
        self.state = code
        self.msg = msg
        print('OnLogin', code, msg)

    def OnLogout(self):
        print("OnLogout")

    def OnDisconnect(self):
        print("OnDisconnect")


# 쿼리 클래스
class QueryEvents:

    def __init__(self):
        self.state = False
        self.error = False
        self.code = ""
        self.msgCode = ""
        self.msg = ""

    def OnReceiveData(self, code):
        print('OnReceiveData', code)
        self.code = code
        self.state = True


    def OnReceiveMessage(self, error, msgCode, msg):
        print('OnReceiveMessage', error, msgCode, msg)
        self.error = error
        self.msgCode = msgCode
        self.msg = msg
        self.state = True
        if error != 0:
            self.error = True


class MainScreen(QMainWindow):

    def __init__(self, wid, hei):
        super().__init__()

        self.setWindowTitle("TEST SCREEN")  # 프로젝트 타이틀 설정
        self.setGeometry(0, 0, int(wid * .5), int(hei * .5))  # 화면 사이즈 설정

        self.mainWidget = QWidget()                     # 메인 위젯 생성
        self.mainLayout = QGridLayout(self.mainWidget)  # 메인 레이아웃 생성 및 메인 위젯 연결
        self.mainLayout.setSpacing(100)
        self.setCentralWidget(self.mainWidget)          # 메인윈도우 센트럴위젯에 메인 위젯 연결

        # 로그인
        self.login()

        # 쿼리 이벤트 객체 가져오기
        self.query = win32com.client.DispatchWithEvents("XA_DataSet.XAQuery", QueryEvents)

        # 데이터 요청
        self.req_chart_data('079550')

        # 차트 만들기
        self.create_line_chart('LIG넥스원', 0, 0)  # 삼성전기
        self.create_candle_chart('LIG넥스원', 0, 1)  # 삼성전기
        self.create_chart('LIG넥스원', 0, 2)  # 삼성전기

        # 데이터 요청
        self.req_chart_data('322000')

        # 차트 만들기
        self.create_line_chart('현대에너지솔루션', 1, 0)  # 삼성전기
        self.create_candle_chart('현대에너지솔루션', 1, 1)  # 삼성전기
        self.create_chart('현대에너지솔루션', 1, 2)  # 삼성전기


        self.showMaximized()

    def login(self):
        # 세션 객체 요청 및 객체 생성
        session = win32com.client.DispatchWithEvents("XA_Session.XASession", SessionEvents)

        url = 'demo.ebestsec.co.kr'  # 모의 투자
        # url = 'hts.ebestsec.co.kr'  # 실제 투자

        session.ConnectServer(url, 200001)
        session.Login("아이디", "비번", "", 0, 0)

        while session.state == "":
            # 로그인 상태 변경 메시지 채크
            pythoncom.PumpWaitingMessages()

    def create_chart(self, name, r, c):

        chart = QChart()  # 차트 생성
        chart.legend().hide()
        chart.setTitle(name)    # 타이틀 설정

        # X축 좌표 설정
        axis_x = QDateTimeAxis()
        axis_x.setFormat("hh:mm:ss")

        # Y축 좌표 설정
        axis_y = QValueAxis()
        axis_y.setLabelFormat("%i")

        # 라인 시리즈 생성
        lineSeries = QLineSeries()

        # 캔들 시리즈 생성
        candleSeries = QCandlestickSeries()  # 캔들스틱 시리즈
        candleSeries.setIncreasingColor(Qt.red)  # 상승시 색상
        candleSeries.setDecreasingColor(Qt.blue)  # 하락시 색상

        # 조회 시 리턴 파라미터 타입 명 t8412InBlock
        outblock1 = "%sOutBlock1" % "t8412"

        # 종교 저장용 리스트
        arr = []
        # 시간 저장용 리스트
        tm = []

        for i in range(self.query.GetBlockCount(outblock1)):
            date = self.query.GetFieldData(outblock1, "date", i).strip()  # 날짜
            time = self.query.GetFieldData(outblock1, "time", i).strip()  # 시간
            open = self.query.GetFieldData(outblock1, "open", i).strip()  # 시가
            high = self.query.GetFieldData(outblock1, "high", i).strip()  # 고가
            low = self.query.GetFieldData(outblock1, "low", i).strip()  # 저가
            close = self.query.GetFieldData(outblock1, "close", i).strip()  # 종가
            dt = QDateTime.fromString(date + time, "yyyyMMddhhmmss")

            # 종가 저장
            arr.append(int(close))
            # 시간 저장
            tm.append(dt.toMSecsSinceEpoch())

            # 캔들스틱 생성하여 캔들시리즈에 저장
            candleSeries.append(QCandlestickSet(float(open), float(high), float(low), float(close), dt.toMSecsSinceEpoch()))

        # 종가가 저장된 배열을 DataFrame 으로 변환
        dt = DataFrame(arr)

        # 5개 기준 이동평균값 계산
        rolling: DataFrame = dt.rolling(5).mean()

        # 생성한 이동평균값을 라인시리즈에 저장
        idx = 0
        for row in rolling.iterrows():
            if isnan(row[1][0]):
                lineSeries.append(tm[idx], arr[idx])
            else:
                lineSeries.append(tm[idx], row[1][0])
            idx += 1


        # 시리즈 연결
        chart.addSeries(lineSeries)
        chart.addSeries(candleSeries)

        # 차트에 좌표 설정
        chart.addAxis(axis_x, Qt.AlignBottom)  # x축 좌표 설정
        chart.addAxis(axis_y, Qt.AlignLeft)  # y축 좌표 설정

        # 라인시리즈 좌표 설정
        lineSeries.attachAxis(axis_x)
        lineSeries.attachAxis(axis_y)

        # 캔들시리즈 좌표 설정
        candleSeries.attachAxis(axis_x)
        candleSeries.attachAxis(axis_y)

        # 차트뷰 차트 연결
        chart_view = QChartView(chart)  # 차트뷰

        # 부드러운 곡선 설정
        chart_view.setRenderHint(QPainter.Antialiasing)

        self.mainLayout.addWidget(chart_view, r, c)  # 메인레이아웃에 차트 배치

    def create_line_chart(self, name, r, c):
        chart = QChart()  # 차트
        chart.legend().hide()
        chart.setTitle(name)

        # X축 좌표 설정
        axis_x = QDateTimeAxis()
        axis_x.setFormat("hh:mm:ss")

        # Y축 좌표 설정
        axis_y = QValueAxis()
        axis_y.setLabelFormat("%i")

        # 라인 시리즈 생성
        series = QLineSeries()

        # 조회 시 리턴 파라미터 타입 명 t8412InBlock
        outblock1 = "%sOutBlock1" % "t8412"

        # 종교 저장용 리스트
        arr = []
        # 시간 저장용 리스트
        tm = []

        for i in range(self.query.GetBlockCount(outblock1)):
            date = self.query.GetFieldData(outblock1, "date", i).strip()  # 날짜
            time = self.query.GetFieldData(outblock1, "time", i).strip()  # 시간
            close = self.query.GetFieldData(outblock1, "close", i).strip()  # 종가
            dt = QDateTime.fromString(date + time, "yyyyMMddhhmmss")

            # 종가 저장
            arr.append(int(close))
            # 시간 저장
            tm.append(dt.toMSecsSinceEpoch())

        # 종가가 저장된 배열을 DataFrame 으로 변환
        dt = DataFrame(arr)

        # 5개 기준 이동평균값 계산
        rolling: DataFrame = dt.rolling(5).mean()

        # 생성한 이동평균값을 라인시리즈에 저장
        idx = 0
        for row in rolling.iterrows():
            if isnan(row[1][0]):
                series.append(tm[idx], arr[idx])
            else:
                series.append(tm[idx], row[1][0])
            idx += 1

        chart.addSeries(series)  # 시리즈 연결
        chart.addAxis(axis_x, Qt.AlignBottom)  # x축 좌표 설정
        chart.addAxis(axis_y, Qt.AlignLeft)  # y축 좌표 설정

        # 라인시리즈 좌표 설정
        series.attachAxis(axis_x)
        series.attachAxis(axis_y)

        chart_view = QChartView(chart)  # 차트뷰
        chart_view.setRenderHint(QPainter.Antialiasing)

        self.mainLayout.addWidget(chart_view, r, c)  # 메인레이아웃에 차트 배치

    def create_candle_chart(self, name, r, c):

        chart = QChart()  # 차트
        chart.legend().hide()
        chart.setTitle(name)

        axis_x = QDateTimeAxis()
        axis_x.setFormat("hh:mm:ss")

        axis_y = QValueAxis()
        axis_y.setLabelFormat("%i")

        series = QCandlestickSeries()  # 캔들스틱 시리즈
        series.setIncreasingColor(Qt.red)  # 상승시 색상
        series.setDecreasingColor(Qt.blue)  # 하락시 색상

        # 조회 시 리턴 파라미터 타입 명 t8412InBlock
        outblock1 = "%sOutBlock1" % "t8412"

        for i in range(self.query.GetBlockCount(outblock1)):
            date = self.query.GetFieldData(outblock1, "date", i).strip()  # 날짜
            time = self.query.GetFieldData(outblock1, "time", i).strip()  # 시간
            open = self.query.GetFieldData(outblock1, "open", i).strip()  # 시가
            high = self.query.GetFieldData(outblock1, "high", i).strip()  # 고가
            low = self.query.GetFieldData(outblock1, "low", i).strip()  # 저가
            close = self.query.GetFieldData(outblock1, "close", i).strip()  # 종가
            dt = QDateTime.fromString(date + time, "yyyyMMddhhmmss")
            series.append(QCandlestickSet(float(open), float(high), float(low), float(close), dt.toMSecsSinceEpoch()))

        chart.addSeries(series)  # 시리즈 연결
        chart.addAxis(axis_x, Qt.AlignBottom)  # x축 좌표 설정
        chart.addAxis(axis_y, Qt.AlignLeft)  # y축 좌표 설정

        series.attachAxis(axis_x)
        series.attachAxis(axis_y)

        chart_view = QChartView(chart)  # 차트뷰
        chart_view.setRenderHint(QPainter.Antialiasing)

        self.mainLayout.addWidget(chart_view, r, c)  # 메인레이아웃에 차트 배치

    def req_chart_data(self, code):

        # 주식챠트(N분)데이터 조회에 해당되는 t8412.res 파일이 있는 경로 가져오기
        resfile_path = "C:\\Users\\bysik\\PycharmProjects\\xingProject\\res\\t8412.res"

        # 조회 시 입력 파라미터 타입 명 t8412InBlock
        inblock = "%sInBlock" % "t8412"

        # 경로를 통해 res 파일을 로드.
        self.query.LoadFromResFile(resfile_path)

        # 입력 파리미터를 초기화 합니다.
        self.query.SetFieldData(inblock, "shcode", 0, code)  # 종목코드
        self.query.SetFieldData(inblock, "ncnt", 0, '5')  # (N)분  5분 챠트 데이터
        self.query.SetFieldData(inblock, "sdate", 0, '20220808')  # 시작날짜 2022년 8월 8일
        self.query.SetFieldData(inblock, "edate", 0, '20220808')  # 종료날짜 2022년 8월 8일
        self.query.SetFieldData(inblock, "comp_yn", 0, "N")  # 압축여부

        # 조회 요청
        self.query.Request(0)

        while not self.query.state:
            # 응답 대기
            pythoncom.PumpWaitingMessages()

        self.query.state = False

        if self.query.error:
            self.query.state = False
            self.query.error = False
            print('REQUEST')
            Sleep(900)
            self.req_chart_data(code)



if __name__ == '__main__':

    app = QApplication(sys.argv)
    size: QSize = app.primaryScreen().size()    # 모니터 사이즈

    main = MainScreen(size.width()/2, size.height())
    sys.exit(app.exec_())

 

 

 


 

소스 코드의 길이가 조금 길다 보니
이번 글에서는 코드의 편한 이해를 돕기 위한 약간의 도움글을 추가하도록 하겠습니다.

 

 

 

 

 

1. 이벤트 클래스

# 세션 클래스
class SessionEvents:

    def __init__(self):
        self.state = ""
        self.msg = ""

    def OnLogin(self, code, msg):
        self.state = code
        self.msg = msg
        print('OnLogin', code, msg)

    def OnLogout(self):
        print("OnLogout")

    def OnDisconnect(self):
        print("OnDisconnect")


# 쿼리 클래스
class QueryEvents:

    def __init__(self):
        self.state = False
        self.error = False
        self.code = ""
        self.msgCode = ""
        self.msg = ""

    def OnReceiveData(self, code):
        print('OnReceiveData', code)
        self.code = code
        self.state = True


    def OnReceiveMessage(self, error, msgCode, msg):
        print('OnReceiveMessage', error, msgCode, msg)
        self.error = error
        self.msgCode = msgCode
        self.msg = msg
        self.state = True
        if error != 0:
            self.error = True

요청 및 응답을 위한 이벤트 클래스 입니다.

일종의 다리 역할을 한다고 보시면 됩니다.

클래스의 이름은 원하시는 이름으로 정하시면 됩니다. 

 

중요한 것은 함수들의 이름입니다.

함수들을 통해 정보를 요청 하고 응답을 해주기 때문에 함수의 이름은 변경하시면 안됩니다.

 

SessionEvents 클래스는 로그인과 관련된 요청 및 응답을 받기 위한 클래스 입니다.

QueryEvents 클래스는 증권 관련 데이터 요청 및 응답을 받기 위한 클래스 입니다.

 

 

 

 

2. 로그인

def login(self):
    # 세션 객체 요청 및 객체 생성
    session = win32com.client.DispatchWithEvents("XA_Session.XASession", SessionEvents)

SessionEvents 클래스 객체 생성을 요청합니다. 생성이 완료되면 객체를 리턴 해줍니다.

 

 

    url = 'demo.ebestsec.co.kr'  # 모의 투자
    # url = 'hts.ebestsec.co.kr'  # 실제 투자

    session.ConnectServer(url, 200001)
    session.Login("아이디", "비번", "", 0, 0)

생성된 session 객체를 통해 로그인을 위한 변수들을 입력하고 함수를 호출해 줍니다.

ConnectServer 함수를 통해 필요한 파라미터를 전달합니다. 파라미터에 대한 설명은 아래와 같습니다.

파라미터명 설명
szServerIP 연결할 서버주소
nServerPort 연결할 서버포트

 

 

Login 함수를 통해 필요한 파라미터를 전달합니다. 파라미터에 대한 설명은 아래와 같습니다.

파라미터명 설명
szID 로그인 아이디
szPwd 로그인 비밀번호
szCertPwd 공인인증 비밀번호(모의투자 접속시에는 사용안함)
nServerType 사용안함
bShowCertErrDlg 공인인증 과정에서 발생한 에러에 대해 미리 정의된 Dialog를 표시할지 여부

 

 

    while session.state == "":
        # 로그인 상태 변경 메시지 채크
        pythoncom.PumpWaitingMessages()

로그인 진행이 종료 되면  SessionEvents  클래스의 OnLogin 함수가 호출 됩니다. 

state 값이 채워지면서 while 문을 탈출 합니다.

 

 

 

 

3. 차트 데이터 요청

def req_chart_data(self, code):

    # 주식챠트(N분)데이터 조회에 해당되는 t8412.res 파일이 있는 경로 가져오기
    resfile_path = "C:\\Users\\bysik\\PycharmProjects\\xingProject\\res\\t8412.res"

    # 조회 시 입력 파라미터 타입 명 t8412InBlock
    inblock = "%sInBlock" % "t8412"

    # 경로를 통해 res 파일을 로드.
    self.query.LoadFromResFile(resfile_path)

t8412.res 파일은 차트 데이터 요청에 쓰입니다. 경로를 지정해주고 로드해줍니다.

 

    # 입력 파리미터를 초기화 합니다.
    self.query.SetFieldData(inblock, "shcode", 0, code)  # 종목코드
    self.query.SetFieldData(inblock, "ncnt", 0, '5')  # (N)분  5분 챠트 데이터
    self.query.SetFieldData(inblock, "sdate", 0, '20220808')  # 시작날짜 2022년 8월 8일
    self.query.SetFieldData(inblock, "edate", 0, '20220808')  # 종료날짜 2022년 8월 8일
    self.query.SetFieldData(inblock, "comp_yn", 0, "N")  # 압축여부

    # 조회 요청
    self.query.Request(0)

요청을 위해 필요한 파라미터 값들을 세팅해 줍니다.

 

    while not self.query.state:
        # 응답 대기
        pythoncom.PumpWaitingMessages()

    self.query.state = False

요청이 완료 되면 QueryEvents 클래스의 OnReceiveData 함수가 호출됩니다.

state 값이 True 로 바뀌면서 while 문을 탈출 합니다.

 

state 값은 다시 리셋 해줍니다.

 

if self.query.error:
    self.query.state = False
    self.query.error = False
    print('REQUEST')
    Sleep(900)
    self.req_chart_data(code)

요청에 대한 에러 여부를 채크 합니다.

에러가 있을 시 Sleep 함수를 통해 약간의 시차를 둡니다.

그 후 다시 요청 합니다.

 

에러가 발생한 이유는 요청과 요청사이의 딜레이 시간이 짧으면 조회 자체를 하지 않고 에러를 리턴해 주기 때문입니다.

그래서 약간의 시차를 두고 다시 요청을 시도 합니다.

딜레이를 강제한 이유는 서버의 과부하를 막기 위함 이라고 합니다. 

 

 

 

 

4. 이동 평균선 생성

이동평균선 생성부분은 아래 링크를 참조 하시기 바랍니다.

2022.07.17 - [PYQT5/QtChart] - [파이썬, PYQT5 - QtChart] LineChart 생성, 데이터 삽입, 타이틀 설정, 색상 변경

 

[파이썬, PYQT5 - QtChart] LineChart 생성, 데이터 삽입, 타이틀 설정, 색상 변경

전체 소스 더보기 import sys from PyQt5.QtChart import QLineSeries, QChart, QChartView from PyQt5.QtCore import QSize, Qt from PyQt5.QtGui import QPainter, QColor from PyQt5.QtWidgets import QApplica..

bysik1109.tistory.com

 

 

5. 캔들 차트 생성

캔들차트 생성부분은 아래 링크를 참조하시기 바랍니다.

2022.07.20 - [PYQT5/QtChart] - [파이썬, PYQT5-QtChart] 캔들스틱차트 생성, 타이틀 설정, 데이터 삽입, 색상 변경, 좌표축 설정

 

[파이썬, PYQT5-QtChart] 캔들스틱차트 생성, 타이틀 설정, 데이터 삽입, 색상 변경, 좌표축 설정

전체 소스 더보기 import sys from PyQt5.QtChart import QChart, QChartView, QCandlestickSeries, QCandlestickSet, QDateTimeAxis, \ QValueAxis from PyQt5.QtCore import QSize, Qt, QDateTime from PyQt5.Qt..

bysik1109.tistory.com