PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 주문 정보 관리하기 (3) - order_info 변수 생성하기 ①

  • -

지난 게시글에서는 원주문번호가 발생하는 논리구조에 대해 살펴보았다. 이번 게시글에서는 종목 별로 발생하는 원주문번호를 어떻게 관리해야 할지에 대해 살펴볼 예정이다. 

원주문번호를 관리하는 것은 여태 구현하는 모든 기능들이 그러했듯이, 별도의 데이터프레임(  DataFrame  )을 만들어서 관리할 예정이다. 이 데이터프레임의 이름은 바로   self.order_info  가 되는데, 이 데이터프레임의 칼럼으로는 어떠한 것들을 생성해줘야 추후에 데이터를 정확하게 관리할 수 있을까?

 

 

order_info 변수에 어떤 구조의 기능을 구현할 것인가?

우리가 구현해야 할 내용에 대해 조금만 생각해보면 이 변수가 가지고 있어야 할 데이터는 의외로 단순하다. 우리가 주문 정보를 관리하는 데에 있어서 필요한 건 말 그대로 주문 정보이다. 따라서 이미 완료된 주문 건은 주문 목록에서 데이터를 삭제해줘야 하며, 주문 번호 변수에는 미체결된 데이터만이 남아 있어야 한다는 것이다. 그럼 어떻게 해야 할까?

이전 게시글에서 우리는   OnReceiveChejanData  이벤트의 결과로   def receive_chejandata(self)  함수로 연결되도록 했으며, 해당 함수 내에서는 Gubun 데이터가 0인가 1인가에 따라 발생하는 데이터의 유형을 구분하도록 했다. 여기서 Gubun 데이터가 0인 경우가 바로 "주문체결통보"에 해당하는데, 아직 구현하지 않은 기능이지만 해당 함수 내에서   order_status  라는 변수는 현재 발생한 데이터의 유형이 "접수" 데이터인지 또는 "체결" 데이터인지를 구분해준다. 

그럼 우리는 데이터의 유형이 "접수"일 경우   order_info  내에 원주문번호를 입력하도록 하는 구조의 코드를 구현하고, 추후 미체결잔량이 0이 될 경우 해당 원주문번호를 갖고 있는 주문 데이터를   order_info   변수 내에서 제거하도록 하면 된다. 말이 어렵지, 막상 구현하다보면 이만큼 쉬울 수가 없다. 그 동작 구조를 이해하는 게 어려울 뿐이다.
※ Line : 26~30

    def receive_chejandata(self, Gubun, ItemCnt, FidList):
        """
        Gubun(string) - 0:주문체결통보, 1:국내주식잔고통보, 4:파생상품잔고통보
        FidList(string) - 데이터 구분은 ;이다.
        """
        if Gubun == "0":
            accno = self.__getchejandata("9201").strip()        ## 계좌번호
            orderno = self.__getchejandata("9203").strip()      ## 주문번호
            item_code = self.__getchejandata("9001").strip()    ## 종목코드
            order_gubun = self.__getchejandata("912").strip()   ## 주문 구분(JJ:주식주문, FJ:선옵, JG:주식잔고, FG:선옵잔고)
            order_status = self.__getchejandata("913").strip()  ## 주문 상태
            item_name = self.__getchejandata("302").strip()     ## 종목명
            order_quan = self.__getchejandata("900").strip()    ## 주문 수량
            order_pri = self.__getchejandata("901").strip()     ## 주문 가격
            remained_quan = self.__getchejandata("902").strip() ## 미체결량
            signed_vol = self.__getchejandata("903").strip()    ## 체결대금
            origin_ordno = self.__getchejandata("904").strip()  ## 원주문번호
            order_bns = self.__getchejandata("905").strip()     ## 주문 구분(+:매수, -:매도)
            trade_gubun = self.__getchejandata("906").strip()   ## 매수 구분(시장가, 지정가, 보통 등)
            signed_num = self.__getchejandata("909").strip()    ## 체결 번호
            signed_pri = self.__getchejandata("910").strip()    ## 체결가
            signed_quan = self.__getchejandata("911").strip()   ## 체결량
            today_com = self.__getchejandata("938").strip()     ## 당일매매 수수료
            today_tax = self.__getchejandata("939").strip()     ## 당일 매매 세금

            if order_status == "접수":
                pass
            
            elif order_status == "체결":
                pass


        elif Gubun == "1":
            pass

 

일단 Line 26에서 order_status == "접수"인 경우 원주문번호를 입력하도록 하고, Line 28에서 order_status == "체결"이고 미체결잔량이 0주인 경우 원주문번호를 삭제하도록 하면 된다.

그럼 이제 order_status == "접수"인 경우 원주문번호를 입력할 대상이 되는   order_info  변수를 생성해줘야 하는데, 이는 다른 변수와 마찬가지로 데이터프레임(DataFrame) 형태의 자료로 만들 것이다. 그럼 이 변수 안에는 어떤 이름의 칼럼들이 입력되어야 할까? 일단 종목코드와 원주문번호라는 칼럼은 있어야 할 것이다. 그래야 원주문번호가 어떤 종목에 해당하는 주문이었는지를 판단할 수 있으며 그와 동시에 특정 종목코드를 기반으로 접수되어 있는 주문들의 원주문번호를 얻어올 수 있기 때문이다. 그리고 매수 주문인지 매도 주문인지도 구별해낼 수 있어야 한다. 그래야 추후 정정 주문을 접수할 때 어떤 주문이었는지를 구분해서 정정 주문을 접수할 수 있기 때문이다. 이외에 어떠한 데이터들이 구현되어야 하는지 잘 생각나지 않을 수 있겠지만, 간략하게 설명해보자면 본인은 다음과 같은 칼럼을 생성해서 사용하고 있다.

  • item_code(종목코드), item_name(종목명), bns(buy and sell, 매수매도 구분), origin_code(원주문번호), order_gubun(주문 구분), trade_gubun(주문 구분), order_quan(주문 수량), signed_quan(체결량), remained_quan(미체결 수량), order_info(주문 정보, 별도 설명)

그럼 이제 이전에 생성했던   _df.py   파일 내에 주문 정보 데이터프레임 변수를 생성해주는 함수를 제작해보도록 하자.
※ Line : 15~17

"""데이터프레임 변수 생성"""
import pandas as pd

def setrealreg():
    data = {'item_code':[]}
    data = pd.DataFrame(data, columns=['item_code'])
    return data

def algo1_df():
    data = {'item_code':[], 'first_buy_price':[], 'second_buy_price':[]}
    data = pd.DataFrame(data, columns=['item_code', 'first_buy_price', 'second_buy_price'])
    return data

def order_info():
    order_info = {'item_code': [], 'item_name': [], 'bns': [], 'origin_code': [], 'order_gubun': [], 'trade_gubun': [], 'order_quan': [], 'signed_quan': [], 'remained_quan': [], 'order_info': []}
    df_order_info = pd.DataFrame(order_info, columns=['item_code', 'item_name', 'bns', 'origin_code', 'order_gubun', 'trade_gubun', 'order_quan', 'signed_quan', 'remained_quan', 'order_info'])
    return df_order_info

 

그리고 다시   main.py   파일로 돌아와서   self.order_info   변수를 생성해주도록 하자.
※ Line : 12

class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정
        self.condition_list = {'index':[], 'name':[]}

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        self.setrealreg = _df.setrealreg()
        self.order_info = _df.order_info()

 

 


728x90

 

 

주문 정보를 입력해보자.

주문 정보는 어디서 입력할 수 있을까? 바로   if order_status == "접수":  인 경우이다. 다시 말해,   SendOrder  함수를 통해 특정 주문을 접수하게 되면 해당 주문으로 인해   OnReceiveChejanData  이벤트가 발생하여   def receive_chejandata(self):  함수를 호출하게 되므로, 그 안에서   order_status  가 접수인 경우에 데이터를 입력하도록 하면 된다.

이 때   self.order_info  의 데이터 타입은 DataFrame이기 때문에, 데이터를 입력하기 위해서는 인덱스 번호가 필요하다. 여기서의 인덱스 번호를 이용하기 위해서는 아래의 개념을 고려해야 한다.

  • 한 종목에 대해 하나의 주문만을 접수할 것인가?

이 개념을 고려해야 하는 이유는 "해당 종목코드로 주문이 접수되었으나 미체결된 주문이 또 있는 상태에서 동일한 종목에 대한 새로운 주문이 접수될 가능성"을 고려해야 하기 때문이다. 한 종목에 대해 한 번의 주문만 접수할 예정이라면 해당 데이터프레임을 관리하는 데에 있어서 원주문번호를 제외하면 크게 고려해야 하는 내용은 없다. 아래의 예시를 한 번 살펴보자.

위의 경우에 대해 이해하기가 조금 어려울 수도 있겠지만, 해당 종목의 호가가 얇고 두 개의 알고리즘에 의해 계산된 가격이 있는 경우를 생각하면 한결 이해하기가 쉬울 것이다. 즉, algorithm1에 의해 8,000원이라는 가격에 400주를 매수하는 주문을 접수했으나 주가가 하락하면서 7,950원까지 하락했고, 해당 가격에서는 algorithm4에 의해 7,950원이라는 가격에 300주를 매수하는 주문이 접수됨에 따라 두 개의 가격대에 주문이 접수되어 있는 경우인 것이다.

이 때 만약 000020 종목에 대해 30주가 추가적으로 체결되었다면 이 체결량은 어떤 주문으로부타 야기된 체결 건인지 어떻게 확인할 수 있을까? 그리고 이 경우에 특정 주문에 의해 체결된 수량을 입력하기 위한 인덱스 번호를 얻기 위해선 어떤 정보를 추가적으로 확인해야 할까? 그 핵심이 되는 것이 바로 원주문번호이다. 한 종목에 대해 하나의 주문만 접수되는 경우에는 원주문번호 외에 고려해야 할 사항이 없다는 것도 이와 같은 맥락에 놓여있는 내용이기도 하다.

일단 지금 당장으로서는 한 종목에 대해 여러 번의 주문이 접수되었을 때 그러한 데이터들을 처리하는 고차원적인 기능을 구현하는 것은 차치하고, 한 종목에 대해 하나의 주문만을 접수할 거라는 가정 하에 주문 정보를 입력하는 코드를 구현해보도록 하자.

아래의 코드에서 Line 27을 보면   idx   값을 구하고 있는데, 이는 단순하게 현재 주문이 접수된 종목의 종목코드가 담겨 있는 변수(  item_code  )가   self.order_info   라는 주문 정보 변수 내에 정확히 몇 번의 인덱스에 위치해 있는지를 확인하는 것이다.  
※ Line : 27

    def receive_chejandata(self, Gubun, ItemCnt, FidList):
        """
        Gubun(string) - 0:주문체결통보, 1:국내주식잔고통보, 4:파생상품잔고통보
        FidList(string) - 데이터 구분은 ;이다.
        """
        if Gubun == "0":
            accno = self.__getchejandata("9201").strip()        ## 계좌번호
            orderno = self.__getchejandata("9203").strip()      ## 주문번호
            item_code = self.__getchejandata("9001").strip()    ## 종목코드
            order_gubun = self.__getchejandata("912").strip()   ## 주문 구분(JJ:주식주문, FJ:선옵, JG:주식잔고, FG:선옵잔고)
            order_status = self.__getchejandata("913").strip()  ## 주문 상태
            item_name = self.__getchejandata("302").strip()     ## 종목명
            order_quan = self.__getchejandata("900").strip()    ## 주문 수량
            order_pri = self.__getchejandata("901").strip()     ## 주문 가격
            remained_quan = self.__getchejandata("902").strip() ## 미체결량
            signed_vol = self.__getchejandata("903").strip()    ## 체결대금
            origin_ordno = self.__getchejandata("904").strip()  ## 원주문번호
            order_bns = self.__getchejandata("905").strip()     ## 주문 구분(+:매수, -:매도)
            trade_gubun = self.__getchejandata("906").strip()   ## 매수 구분(시장가, 지정가, 보통 등)
            signed_num = self.__getchejandata("909").strip()    ## 체결 번호
            signed_pri = self.__getchejandata("910").strip()    ## 체결가
            signed_quan = self.__getchejandata("911").strip()   ## 체결량
            today_com = self.__getchejandata("938").strip()     ## 당일매매 수수료
            today_tax = self.__getchejandata("939").strip()     ## 당일 매매 세금

            if order_status == "접수":
                idx = self.order_info.index[self.order_info['item_code'] == item_code]

            elif order_status == "체결":
                pass


        elif Gubun == "1":
            pass

 

하지만   idx = self.order_info.index[self.order_info['item_code'] == item_code]  와 같은 방식으로 인덱스 번호를 구한다는 것은 곧   self.order_info   변수 내   ['item_code']   칼럼 안에   item_code  라는 종목코드 데이터가 입력되어 있어야 함을 의미힌다. 하지만 우리는 해당 변수에 종목코드 데이터를 입력한 적이 없기 때문에, 인덱스 번호를 그렇게 구해선 안 된다. 다시 말해, 우리는   self.order_info  변수 내에 입력되어 있는 데이터의 개수를 인덱스 번호로 전달해야 한다.
※ Line : 2

    if order_status == "접수":
        idx = len(self.order_info['item_code'].to_list())
        idx = self.order_info.index[self.order_info['item_code'] == item_code]

위의 코드 중 Line 2를 보면   self.order_info  변수 내에   ['item_code']  라는 칼럼 안에 있는 데이터를 list 형태의 데이터로 변환한 후, 해당 데이터의 개수를 반환해주고 있음을 확인할 수 있다. Line 3은   self.order_info  의   ['item_code']  에 종목코드가 입력되어 있다면 그 인덱스 번호를 반환하는 것이라는 점에서 차이가 있다. 여기서 Line 3의 경우에는 반드시   self.order_info  변수 내에 종목코드가 입력되어 있어야만 인덱스(  idx  ) 값을 얻어올 수 있는 구조인 것이다. 두 구조의 코드를 각각 어떠한 상황에서 사용해야 하는지에 대해서는 어렵지 않게 구분할 수 있다. (여기서는 Line 2를 사용한다.)

그럼 이제   self.order_info  변수 내에 접수된 주문의 데이터를 입력해보자. 사전에 생성한   self.order_info  (DataFrame 형)의 각 칼럼 안에 칼럼 명에 따라 적절한 변수를 전달해주면 된다.
※ Line : 29~38

def receive_chejandata(self, Gubun, ItemCnt, FidList):
    """
    Gubun(string) - 0:주문체결통보, 1:국내주식잔고통보, 4:파생상품잔고통보
    FidList(string) - 데이터 구분은 ;이다.
    """
    if Gubun == "0":
        accno = self.__getchejandata("9201").strip()        ## 계좌번호
        orderno = self.__getchejandata("9203").strip()      ## 주문번호
        item_code = self.__getchejandata("9001").strip()    ## 종목코드
        order_gubun = self.__getchejandata("912").strip()   ## 주문 구분(JJ:주식주문, FJ:선옵, JG:주식잔고, FG:선옵잔고)
        order_status = self.__getchejandata("913").strip()  ## 주문 상태
        item_name = self.__getchejandata("302").strip()     ## 종목명
        order_quan = self.__getchejandata("900").strip()    ## 주문 수량
        order_pri = self.__getchejandata("901").strip()     ## 주문 가격
        remained_quan = self.__getchejandata("902").strip() ## 미체결량
        signed_vol = self.__getchejandata("903").strip()    ## 체결대금
        origin_ordno = self.__getchejandata("904").strip()  ## 원주문번호
        order_bns = self.__getchejandata("905").strip()     ## 주문 구분(+:매수, -:매도)
        trade_gubun = self.__getchejandata("906").strip()   ## 매수 구분(시장가, 지정가, 보통 등)
        signed_num = self.__getchejandata("909").strip()    ## 체결 번호
        signed_pri = self.__getchejandata("910").strip()    ## 체결가
        signed_quan = self.__getchejandata("911").strip()   ## 체결량
        today_com = self.__getchejandata("938").strip()     ## 당일매매 수수료
        today_tax = self.__getchejandata("939").strip()     ## 당일 매매 세금

        if order_status == "접수":
            idx = len(self.order_info['item_code'].to_list())

            self.order_info.loc[idx, 'item_code'] = item_code
            self.order_info.loc[idx, 'item_name'] = item_name
            self.order_info.loc[idx, 'bns'] = order_bns
            self.order_info.loc[idx, 'origin_code'] = origin_ordno
            self.order_info.loc[idx, 'order_gubun'] = order_gubun
            self.order_info.loc[idx, 'trade_gubun'] = trade_gubun
            self.order_info.loc[idx, 'order_quan'] = order_quan
            self.order_info.loc[idx, 'signed_quan'] = signed_quan
            self.order_info.loc[idx, 'remained_quan'] = remained_quan
            self.order_info.loc[idx, 'order_info'] = ""

        elif order_status == "체결":
            pass

 

이번 게시글에서 중점적으로 다룬 내용은 바로 "주문 접수"가 발생했을 때, self.order_info 내에 데이터를 입력하는 것이었다. 이제 다음 게시글에서는 접수된 주문이 실제로 "체결"되었을 때 해당 변수를 어떻게 처리해야 할지에 대해 구현해보자.

 

 


728x90
반응형
Contents

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

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