PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 차트 데이터 조회하기 (3)

  • -

지난 게시글에서 차트 데이터를 요청하고 연속 조회를 구현하는 방법까지 모두 다루었으니, 이번 게시글에서는 해당 데이터를 데이터프레임(DataFrame)화 시켜서 보기 편하게 만드는 목적도 있겠지만 데이터베이스에 저장하기 용이한 형태로 데이터형을 변환하는 방법에 대해 알아볼 예정이다.

 

 

판다스(Pandas)의 데이터프레임(DataFrame)

데이터프레임(DataFrame)이란 단순하게 엑셀과 같은 형태라 이해하면 된다. 즉, 데이터 하나 하나마다 각자의 행과 열에 알맞은 값이 입력되어 있는 자료형이다. 이런 구조의 데이터여야만 추후 데이터에 접근하기 용이하다. 데이터프레임을 만들어내는 방법에는 다양한 방법들이 있는데, 가장 흔하게 사용되는 방법은 튜플 형태의 자료형을 만들어서 데이터프레임화(pd.DataFrame) 시키는 것이고, 또 하나는 리스트 형태의 자료형을 만들어서 데이터프레임에 합치는 것(pd.concat)이다. 

개념이 어려워보이지만 겁먹지 말고, 그냥 따라하다 보면 어느 샌가 튜플 형태의 자료형은 무엇이고 데이터프레임 형태의 자료형은 무엇이며 각각의 자료형에 담겨 있는 데이터에 어떻게 접근할 수 있는지 등에 대해 자연스럽게 알게 될 것이다.

 

 

차트 데이터를 데이터프레임화 시켜보자.

이전 게시글에서 언급했듯이, self 변수는 class 내부의 모든 함수에서 참조될 수 있는 변수이기 때문에 어디서 정의하든지 간에 상관없긴 하지만 그래도 따져보자면 데이터를 요청하는 시점에서 변수를 생성해주는 것이 좋다. 그래야만 데이터를 요청할 때마다 변수를 새롭게 생성해서 비어 있는 변수로 전달하게 되고, 해당 변수에 데이터를 입력하게 되면서 깔끔한 데이터를 얻어올 수 있기 때문이다. 여기서 이야기하는 데이터를 요청하는 시점이란   def request_opt10080()  에 해당한다.

그렇다면 차트 데이터를 데이터프레임화시키기 위해서는 튜플 형태의 자료형을 생성해줘야 하고, 데이터를 입력하는 것은 요청한 데이터를 전달받은   def receive_trdata()  함수 내에서 해당 변수의 알맞은 데이터를 입력해주면 된다.

일단 현재 시점에서 구현한 데이터 요청 함수는 3분봉 차트 데이터뿐이니, 3분봉 차트 데이터 조회 요청의 결과로 얻어온 데이터들만 해당 변수에 입력해주도록 하겠다. (지난 게시글에서 print()문에서 출력하도록 구축했던 변수(Line 12 참조)는   time, now, open, high, low, yclose  의 여섯 개이다. 하지만 결과물에서 살펴본 결과 ,YCLOSE 뒤에는 출력물이 없다는 점을 고려하여 yclose를 제외한 나머지 다섯 개의 데이터만 입력하도록 하자.)
※ Line : 17

    """데이터 처리 구간"""
    def opt10080(self, TrCode, RecordName):
        data_len = self.__getrepeatcnt(TrCode, RecordName)

        for index in range(data_len):
            time = self.__getcommdata(TrCode, RecordName, index, "체결시간").strip(" ")
            now = self.__getcommdata(TrCode, RecordName, index, "현재가").strip(" ")
            open = self.__getcommdata(TrCode, RecordName, index, "시가").strip(" ")
            high = self.__getcommdata(TrCode, RecordName, index, "고가").strip(" ")
            low = self.__getcommdata(TrCode, RecordName, index, "저가").strip(" ")
            yclose = self.__getcommdata(TrCode, RecordName, index, "전일종가").strip(" ")
            print(f"[{time}] NOW:{now}, OPEN:{open}, HIGH:{high}, LOW:{low}, YCLOSE:{yclose}")


    """opt10080 : 분봉차트조회요청"""
    def request_opt10080(self):
        self.chart_data = {'time':[], 'now':[], 'open':[], 'high':[], 'low':[]}
        self.__setinputvalue("종목코드", "005930")
        self.__setinputvalue("틱범위", "3")
        self.__setinputvalue("수정주가구분", "1")
        self.__commrqdata("분봉차트조회요청", "opt10080", 0, "0600")
        self.event_loop = QEventLoop()
        self.event_loop.exec_()

        while self.remained_data == True:
            self.__setinputvalue("종목코드", "005930")
            self.__setinputvalue("틱범위", "3")
            self.__setinputvalue("수정주가구분", "1")
            self.__commrqdata("분봉차트조회요청", "opt10080", 2, "0600")
            self.event_loop = QEventLoop()
            self.event_loop.exec_()

 

그렇다면 데이터 요청 지점(  def request_opt10080  )에서 생성한 튜플 형태의 자료형 변수(  self.chart_data  ) 안에 데이터를 입력해주면 되는데, 데이터를 입력하는 절차는   def receive_trdata  함수 내에서 이루어지면 되며, 데이터를 입력하는 메서드는   .append()  이다.
※ Line : 14~18

    """데이터 처리 구간"""
    def opt10080(self, TrCode, RecordName):
        data_len = self.__getrepeatcnt(TrCode, RecordName)

        for index in range(data_len):
            time = self.__getcommdata(TrCode, RecordName, index, "체결시간").strip(" ")
            now = self.__getcommdata(TrCode, RecordName, index, "현재가").strip(" ")
            open = self.__getcommdata(TrCode, RecordName, index, "시가").strip(" ")
            high = self.__getcommdata(TrCode, RecordName, index, "고가").strip(" ")
            low = self.__getcommdata(TrCode, RecordName, index, "저가").strip(" ")
            yclose = self.__getcommdata(TrCode, RecordName, index, "전일종가").strip(" ")
            print(f"[{time}] NOW:{now}, OPEN:{open}, HIGH:{high}, LOW:{low}, YCLOSE:{yclose}")

            self.chart_data['time'].append(time)
            self.chart_data['now'].append(now)
            self.chart_data['open'].append(open)
            self.chart_data['high'].append(high)
            self.chart_data['low'].append(low)


    """opt10080 : 분봉차트조회요청"""
    def request_opt10080(self):
        self.chart_data = {'time':[], 'now':[], 'open':[], 'high':[], 'low':[]}
        self.__setinputvalue("종목코드", "005930")
        self.__setinputvalue("틱범위", "3")
        self.__setinputvalue("수정주가구분", "1")
        self.__commrqdata("분봉차트조회요청", "opt10080", 0, "0600")
        self.event_loop = QEventLoop()
        self.event_loop.exec_()

        while self.remained_data == True:
            self.__setinputvalue("종목코드", "005930")
            self.__setinputvalue("틱범위", "3")
            self.__setinputvalue("수정주가구분", "1")
            self.__commrqdata("분봉차트조회요청", "opt10080", 2, "0600")
            self.event_loop = QEventLoop()
            self.event_loop.exec_()

 

 


728x90

 

 

이제   self.chart_data  라는 변수 안에 데이터가 입력되어 있으니 해당 변수의 데이터들을 데이터프레임으로 만들어주면 되는데, 데이터프레임으로 변경하는 방법을 의미하는 메서드는   pd.DataFrame(데이터, columns=[칼럼들])  이다. 즉, "데이터" 자리에는   self.chart_data  라는 변수가 들어갈 것이고 columns= 뒤의 "칼럼들"에는 리스트 형태로   time, now, open, high, low  가 들어갈 것이다. 

따라서   pd.DataFrame()  이라는 메서드는   self.chart_data  라는 변수 안에 모든 데이터를 입력하는 절차가 완료된 시점에서 이루어져야만 제대로 된 데이터를 데이터프레임화시키는 것이다. 그렇다면 데이터를 입력하는 모든 절차가 완료된 시점은   def request_opt10080()  함수가 모두 완료된 시점이 될 것이다.
※ Line : 1, 21

import pandas as pd

    """opt10080 : 분봉차트조회요청"""
    def request_opt10080(self):
        self.chart_data = {'time':[], 'now':[], 'open':[], 'high':[], 'low':[]}
        self.__setinputvalue("종목코드", "005930")
        self.__setinputvalue("틱범위", "3")
        self.__setinputvalue("수정주가구분", "1")
        self.__commrqdata("분봉차트조회요청", "opt10080", 0, "0600")
        self.event_loop = QEventLoop()
        self.event_loop.exec_()

        while self.remained_data == True:
            self.__setinputvalue("종목코드", "005930")
            self.__setinputvalue("틱범위", "3")
            self.__setinputvalue("수정주가구분", "1")
            self.__commrqdata("분봉차트조회요청", "opt10080", 2, "0600")
            self.event_loop = QEventLoop()
            self.event_loop.exec_()

        chart_data = pd.DataFrame(self.chart_data, columns=['time', 'now', 'open', 'high', 'low'])
        print(chart_data)

 

코드를 위와 같이 제작한 후 실행해본 결과, 이상하게 단일 조회 시에는 잘 조회되던 아이가 연속 조회로 코드를 수정해서 실행하니 제대로 동작하지 않는 오류가 발생했다. 이에 따라 이벤트 루프가 시작되는 지점과 이벤트 루프가 종료되는 지점에 print()문을 넣어 실행과 종료 여부를 구분하는 문구를 추가한 후에 테스트를 해보니, 아래와 같은 출력물을 얻을 수 있었다.

이벤트 루프를 시작합니다.
RQNAME:분봉차트조회요청, TRCODE:opt10080, RECORDNAME:, PRENEXT:2
이벤트 루프를 종료합니다.
이벤트 루프를 종료했습니다.
이벤트 루프를 시작합니다.
RQNAME:분봉차트조회요청, TRCODE:opt10080, RECORDNAME:, PRENEXT:2
이벤트 루프를 종료합니다.
이벤트 루프를 종료했습니다.
이벤트 루프를 시작합니다.
RQNAME:분봉차트조회요청, TRCODE:opt10080, RECORDNAME:, PRENEXT:2
이벤트 루프를 종료합니다.
이벤트 루프를 종료했습니다.
이벤트 루프를 시작합니다.
RQNAME:분봉차트조회요청, TRCODE:opt10080, RECORDNAME:, PRENEXT:2
이벤트 루프를 종료합니다.
이벤트 루프를 종료했습니다.
이벤트 루프를 시작합니다.
RQNAME:분봉차트조회요청, TRCODE:opt10080, RECORDNAME:, PRENEXT:2
이벤트 루프를 종료합니다.
이벤트 루프를 종료했습니다.
이벤트 루프를 시작합니다.

이벤트루프를 사용함으로 인해 발생하는 가장 흔한 문제점 중 하나인 무한 루프에 갇힌 경우 위와 같은 결과물을 얻게 된다. 그렇다면 현재 무한 루프에 걸리게 되면서 차트 데이터를 제대로 불러오지 못하는 문제점이 발생하고 있다는 것인데, 왜 무한루프에 걸리게 되는 것일까? 단순하게 이야기해보자면 이벤트루프가 종료되는 구문까지 동작하지 못했기 때문이다.

이벤트루프가 종료되는 지점까지 코드가 동작하지 않았다는 것은 여러 가지 요인이 있겠지만, 현 구조의 코드 하에서는 결과 데이터를 모두 얻어오기 전에 다음 코드를 실행하기 위한 구조로 진행되면서 코드가 정상적으로 동작하지 않는 것이라 볼 수 있다. 따라서 이벤트루프가 시작된 후에 데이터를 받아오기까지 어느 정도의 시간을 보장해주는 방법을 통해 문제를 해결할 수 있다. 여기서 데이터를 받아오는 함수는   def opt10080(self, TrCode, RecordName)  을 의미한다.

이제 아래와 같이   time.sleep(0.5)  를 통해 이벤트 루프가 시작된 후 다음 코드를 실행하기까지 그 사이의 시간에 0.5초를 기다리도록 한 후에 이후의 코드를 실행하도록 수정한 후에, 다시 실행해보도록 하자.
※ Line : 6, 13, 22

import pandas as pd
import time

    """opt10080 : 분봉차트조회요청"""
    def request_opt10080(self):
        self.chart_data = {'time':[], 'now':[], 'open':[], 'high':[], 'low':[]}
        self.__setinputvalue("종목코드", "005930")
        self.__setinputvalue("틱범위", "3")
        self.__setinputvalue("수정주가구분", "1")
        self.__commrqdata("분봉차트조회요청", "opt10080", 0, "0600")
        self.event_loop = QEventLoop()
        self.event_loop.exec_()
        time.sleep(0.5)

        while self.remained_data == True:
            self.__setinputvalue("종목코드", "005930")
            self.__setinputvalue("틱범위", "3")
            self.__setinputvalue("수정주가구분", "1")
            self.__commrqdata("분봉차트조회요청", "opt10080", 2, "0600")
            self.event_loop = QEventLoop()
            self.event_loop.exec_()
            time.sleep(0.5)

        chart_data = pd.DataFrame(self.chart_data, columns=['time', 'now', 'open', 'high', 'low'])
        print(chart_data)

 

결과 데이터는 아래와 같다. 맨 마지막 줄을 보면 총 32,358개의 데이터가 조회되었으며, 칼럼은   time, now, open, high, low  라는 다섯 개의 칼럼으로 이루어졌음을 한 줄로 요약해주고 있다. 하루동안 장은 아침 9시부터 오후 3시 20분까지 진행되며, 마지막에는 종가 데이터 2개가 추가적으로 형성된다는 점을 고려하면 하루의 데이터는 약 128개 정도가 된다. 그냥 간단하게 130개라고 가정했을 때, 32,358개의 데이터는 대략 248거래일에 해당하는 데이터임을 확인할 수 있다. 실제로   time  부분의 데이터들을 보면 맨 위의 2022.07.07. 15:30:00부터 시작해서 마지막 데이터는 2021.07.01. 09:00:00에서 끝난다는 것을 확인할 수 있다.

"""결과 데이터"""
                 time     now    open    high     low
0      20220707153000  +58200  +58200  +58200  +58200
1      20220707151800  +58300  +58300  +58300  +58200
2      20220707151500  +58200  +58300  +58300  +58200
3      20220707151200  +58200  +58300  +58300  +58200
4      20220707150900  +58200  +58200  +58300  +58200
...               ...     ...     ...     ...     ...
32353  20210701091200  -80200  -80100  -80200  -80100
32354  20210701090900  -80200  -80100  -80200  -80000
32355  20210701090600  -80100  -80200  -80200  -80100
32356  20210701090300  -80200  -80400  -80400  -80100
32357  20210701090000  -80400  -80500  -80600  -80300
[32358 rows x 5 columns]

 

 

 


728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.