PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 매수 매도 함수 구현하기 (2) - 매수 매도 함수 발생 시 디테일한 데이터 처리 구현하기 ①

  • -

지난 게시글에서는 매수 함수와 매도 함수가 동작하는 그 로직에 대해 살펴봤고, 이번 게시글에서는 직접 매수 함수와 매도 함수를 만들어볼 예정이다. 물론 알고리즘은 어떻게 동작하는지 알고리즘은 어떤 로직으로 동작해야 하는지 등에 대해 모두 살펴봤었지만 아직은 그 내용이 익숙하지 않을 수 있다. 그래도 일단은 한 번 만들어 보고 그 후에 오류가 발생한다면 그 내용을 수정하는 방향으로 진행해보고자 한다. (물론 제작 과정에서 본인이 직접 겪은 오류들은 발생하지 않도록 할 것이다.)

제목에서 일단 매수와 매도를 따로 구분해놓긴 했지만, 실질적으로 두 함수는 모두   SendOrder  라는 하나의 메서드를 통해 동작한다. 차이점이 있다면 단순하게 매수를 실행할 함수에서 사용되는가 아니면 매도를 실행할 함수에서 사용되는가의 차이만 있을 뿐, 실질적으로 사용하는 함수는 동일하다. 그렇다면 이제 하나를 작성해보도록 하자.

 

 

실시간 데이터를 처리해보자.

실시간 데이터를 수신하는 방법과 그 코드에 대해서는 이전에 실시간 데이터를 구축하는 방법에 대해 작성했던 글에 있으니 그 글을 참고하도록 하자. 해당 게시글에서 실시간 데이터를 받는 함수는   def receive_realdata(self, jongmokcode, realtype, realdata):  였고, 수신된 데이터 중   realtype  의 인자로 전달받은 값이 "주식체결"인 경우 발생한 실시간 데이터를   GetCommRealData  함수를 통해 각각의 FID 번호를 전달하여 실시간 데이터를 수신받았다. 

 이 때 발생한 데이터 중 현재가 데이터는   now  에 저장해두었는데, 우리는 이   now  데이터만 사용하면 된다. 왜냐하면 현재 가격이 어떠한 범위에 위치해 있는가에 따라 각기 다른 함수를 실행할 것이기 때문이다. 이 내용 역시 지난 게시글에서 살펴봤던 개념이므로, 여기서는 간단하게 작성한 후 넘어가도록 하자.

"""실시간 데이터 수신 구간"""
def receive_realdata(self, jongmokcode, realtype, realdata):

    if realtype == "주식체결":
        now = self.__getcommrealdata(jongmokcode, 10)
        acc_vol = self.__getcommrealdata(jongmokcode, 13)
        acc_tvol = self.__getcommrealdata(jongmokcode, 14)
        open = self.__getcommrealdata(jongmokcode, 16)
        high = self.__getcommrealdata(jongmokcode, 17)
        low = self.__getcommrealdata(jongmokcode, 18)
        turnover = self.__getcommrealdata(jongmokcode, 31)
        strength = self.__getcommrealdata(jongmokcode, 228)
        gubun = self.__getcommrealdata(jongmokcode, 290)

        if 5000 < now < 5500:
            self.알고리즘매수함수1()
        elif 4500 < now < 5000:
            self.알고리즘매수함수2()
        elif 7000 < now < 7500:
            self.알고리즘매도함수1()
        elif 7500 < now:
            self.알고리즘매도함수2()

 

이제 여기서 주의해야 할 점이 하나 있다면, 키움증권 Open API 서비스는 기본적으로 반환되는 데이터들을 문자열 형태로 전달해준다는 것이다. 코딩을 조금 해봤었던 사람이라면 알겠지만, 문자열과 숫자형 데이터 간의 비교는 옳지 않은 형태의 데이터이다. 따라서 실시간으로 수신되는 데이터의 형태들을 숫자형으로 변환해주도록 하자.(float이 아닌 int를 사용하는 이유는, 기본적으로 주가는 소수점이 없기 때문이다. 하지만 전일 거래량대비 비율이나 등락률을 나타내는 데이터들은 소수점이 포함된 문자열 형태로 전달받기 때문에 그 부분에 대해서는 float을 사용해줘야 한다. 하지만 다행히도, 위 코드에서 소수점이 포함된 데이터는 없다.)
※ Line : 5~13

"""실시간 데이터 수신 구간"""
def receive_realdata(self, jongmokcode, realtype, realdata):

    if realtype == "주식체결":
        now = int(self.__getcommrealdata(jongmokcode, 10))
        acc_vol = int(self.__getcommrealdata(jongmokcode, 13))
        acc_tvol = int(self.__getcommrealdata(jongmokcode, 14))
        open = int(self.__getcommrealdata(jongmokcode, 16))
        high = int(self.__getcommrealdata(jongmokcode, 17))
        low = int(self.__getcommrealdata(jongmokcode, 18))
        turnover = int(self.__getcommrealdata(jongmokcode, 31))
        strength = int(self.__getcommrealdata(jongmokcode, 228))
        gubun = self.__getcommrealdata(jongmokcode, 290)

        if 5000 < now < 5500:
            self.알고리즘매수함수1()
        elif 4500 < now < 5000:
            self.알고리즘매수함수2()
        elif 7000 < now < 7500:
            self.알고리즘매도함수1()
        elif 7500 < now:
            self.알고리즘매도함수2()
        elif 4000 > now:
            self.알고리즘손절함수()

 

위의 코드에서, 특정 종목의 현재가 데이터(now)가 5,000원에서 5,500원 사이에 위치한 경우에 알고리즘 매수함수를 실행하도록 했다. 이 때 가격적인 범위를 반드시 설정해줘야 한다. 예를 들어, 특정 종목이 5,400원이 되었을 때 매수하고 싶다면 가격적인 범위를 5,390~5,400원 정도로 설정해둬야 한다. 이렇게 해야만 하는 이유는 분할 매수 기능을 구현하든지 말든지와 무관하게 가격적 범위를 설정해주지 않으면 다른 가격 데이터와 충돌할 수 있기 때문이다. 이 내용은 아래의 예시를 통해 살펴보도록 하자.

"""가격적인 범위가 설정되어 있는 경우"""
def test(value):
    value = int(value)
    if 5000 < value < 5500:
        print("알고리즘매수함수1")
    elif 4500 < value < 5000:
        print("알고리즘매수함수2")
    elif 4000 > value:
        print("알고리즘손절함수")


>>> test(5100)		## 5100원일 때의 출력값
알고리즘매수함수1
>>> test(4900)		## 4900원일 때의 출력값
알고리즘매수함수2
>>> test(3900)		## 3900원일 때의 출력값
알고리즘손절함수

위의 코드는 가격적인 범위가 잘 설정되어 있기 때문에 각각의 범위에 해당하는 값을 전달하면 알아서 잘 출력된다. 하지만 아래와 같이 가격적 범위를 명확하게 나누어주지 않으면, 매수 함수는 오류를 일으키게 된다. 아래의 예시를 살펴보자. 어떤 값을 입력하든지 간에 매수함수1만 실행된다는 것을 알 수 있다.

"""가격적인 범위가 올바르게 설정되지 못한 경우"""
def test(value):
    value = int(value)
    if value < 5500:
        print("알고리즘매수함수1")
    elif value < 5000:
        print("알고리즘매수함수2")
    elif value < 4000:
        print("알고리즘손절함수")

>>> test(5100)
알고리즘매수함수1
>>> test(4900)
알고리즘매수함수1
>>> test(3900)
알고리즘매수함수1

 

 


728x90

 

 

매수 함수의 동작을 위한 SendOrder 함수 제작하기

일단 현재 사용하는 알고리즘의 이름이 algorithm1이라고 가정하고, 해당 알고리즘의 매수 함수의 이름도 임의로 작성해보자.

"""실시간 데이터 수신 구간"""
def receive_realdata(self, jongmokcode, realtype, realdata):

        """중략"""

        if 5000 < now < 5500:
            self.algo1_buy_1()
        elif 4500 < now < 5000:
            self.algo1_buy_2()
        elif 7000 < now < 7500:
            self.algo1_sell_1()
        elif 7500 < now:
            self.algo1_sell_2()
        elif 4000 > now:
            self.algo1_cut_loss()

"""알고리즘 매수함수 1"""
def algo1_buy_1():
    print("알고리즘1의 첫 번째 매수함수를 실행합니다.")

 

이제 now라는 현재가 데이터가 5,000원에서 5,500원 사이에 위치할 경우 곧바로 algo1_buy_1 함수가 실행될 것이다. 이 게시글의 도입부에서도 이야기했듯, 우리는 모든 매수 매도 함수에   SendOrder  라는 단 하나의 메서드만을 사용해서 모든 거래가 이루어지도록 할 수 있다. 그렇다면 여느 함수와 마찬가지로, SendOrder 함수를 하나의 캡슐로 생성해주도록 하자. 이 메서드는 어떠한 파라미터를 요구하고 있는지 어디서 확인할 수 있을까? 개발가이드를 참고해보자.

내용이 다소 복잡하게 보일 수도 있겠지만, 하나하나씩 입력해보면 된다. 가장 먼저 원형이라고 적혀있는 부분에서 해당 메서드가 요구하는 파라미터를 하나하나씩 작성해보도록 하자. 그리고 """ """을 이용해서, 해당 함수에 대한 설명을 간략하게 적어주도록 하자. 주문 접수 함수의 경우에는 다른 파라미터들은 몰라도 주문유형(  ordertype  )과 거래구분(  hogagb  )에 대해서는 상당히 많은 값들을 지원하고 있기 때문에, 외워놓고 쓸 정도는 안 되니 그냥 메모해놓고 나중에 필요할 때마다 확인하는 것이 좋다.

def _send_order(self, rqname, scrno, accno, ordertype, code, qty, price, hogagb, orgorderno):
    """
    [ordertype] 주문유형
                1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정 
    [hogagb] 거래구분
             00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가,
             10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK,
             61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
             ※ 03, 06, 07, 13, 16, 23, 26, 61, 81은 주문가격 공란으로 전달
    """
    pass

 

이제   SendOrder  메서드에 전달받은 파라미터들을 전달해주면 된다.
※ Line : 11

def _send_order(self, rqname, scrno, accno, ordertype, code, qty, price, hogagb, orgorderno):
    """
    [ordertype] 주문유형
                1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정 
    [hogagb] 거래구분
             00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가,
             10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK,
             61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
             ※ 03, 06, 07, 13, 16, 23, 26, 61, 81은 주문가격 공란으로 전달
    """
    self.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", [rqname, scrno, accno, ordertype, code, qty, price, hogagb, orgorderno]")

하지만 지금 보니 코드가 너무 길어져서 뭔가 제대로 쓰자니 찝찝하고 깔끔하지 못한 것 같고 그렇다. 그렇다면 여기서 우리가 그때그때 전달해줘야 하는 데이터와 항상 고정적으로 사용할 수 있는 데이터를 구분해주는 것도 하나의 방법인데, 아쉽게도 고정되어 있는 값은 화면번호(  scrno  ) 하나뿐이다. 주문 접수의 경우 현재가[0101] 화면을 통해 이루어지기 때문에, 굳이 scrno라는 하나의 파라미터로 놓지 않고 아예 "0101"을 전달해주면 된다.
※ Line : 1, 11

def _send_order(self, rqname, accno, ordertype, code, qty, price, hogagb, orgorderno):
    """
    [ordertype] 주문유형
                1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정 
    [hogagb] 거래구분
             00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가,
             10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK,
             61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
             ※ 03, 06, 07, 13, 16, 23, 26, 61, 81은 주문가격 공란으로 전달
    """
    self.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", [rqname, "0101", accno, ordertype, code, qty, price, hogagb, orgorderno]")

 

 

 


728x90
반응형
Contents

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

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