PYTHON/Back test

백테스팅 구축 (25) - 거래별 고유정보 입력하기

  • -

지난 게시글을 마지막으로 백테스팅 코드 구축을 마무리할 수 있을 거라 생각했지만, 거래 데이터 상에서 존재하는 오류가 있어서 다시 글을 게시하게 되었다. 일단 오류부터 살펴보도록 하자.

 

그렇게 고쳤는데 오류가 아직도 있다고?

바로 특정 종목 코드가 거래 데이터 변수인   self.df_tracking_data  안에서 몇 번째 인덱스에 위치해 있는지를 얻어오는 코드를 실행하는 과정에서 발생하는 오류이다.

예를 들어, 000100이라는 코드를 가진 종목을 2020년 1월 3일에 매수했고 2020년 1월 5일에 첫 번째 매도, 2020년 1월 10일에 손절매를 했다. 이후 2020년 3월 10일에 그 종목을 다시 매수했고, 2020년 3월 15일에 첫 번째 매도, 2020년 3월 20일에 두 번재 매도를 했다고 가정해보도록 하자. 정상적인 데이터라면 아래와 같이 출력되어야 한다.

인덱스 코드 매수일자 매도일자1 매도일자2 매도일자3 손절일자
0 000100 20200103 20200105     20200110
1 000100 20200310 20200315 20200320    

하지만 특정 코드의 인덱스 값을 불러오는 과정에서 000100 종목을 여러 번 거래했다고 하더라도, 가장 앞에 있는(먼저 입력된) 인덱스 값을 가져오기 때문에 여러 번에 걸쳐 거래를 진행했다 하더라도 아래와 같은 결과값이 출력된다.

인덱스 코드 매수일자 매도일자1 매도일자2 매도일자3 손절일자
0 000100 20200103 20200315 20200320   20200110

즉, 특정 종목 코드가 위치한 인덱스 행에 매도 정보만 새롭게 입력하는 것이기 때문에, 매도 데이터 중 중복되는 데이터는 삭제된 후에 새롭게 입력되고 중복되지 않는 데이터는 수정되지 않고 그대로 유지된다. 이로 인해 000100 종목을 2020년 1월 3일에 매수했고 1월 10일에 손절매를 했지만 3월 15일에 첫 번째 매도가 이루어지는 문제가 발생하는 것이다. 

그렇다면 이 문제를 해결하기 위해서는 어떤 방법을 사용해야 할까?

 


728x90

 

거래별 고유정보 입력하기

종목 코드를 기반으로 인덱스 값을 불러오는 것은 위의 사례를 통해 오류가 존재한다는 것을 확인했다. 그렇다면 중복될 수 있는 가능성이 있는 값은 어떤 것들이 있을까? 일단 종목 코드는 여러 번 거래가 이뤄질 수 있기 때문에 중복 가능성이 있고, 매수 일자나 매도 일자 역시 모두 중복될 가능성이 있다. 그렇다면 가격은 어떨까? 가격도 중복된 데이터가 입력될 가능성이 높을까? 높진 않겠지만 중복되는 매수 가격이나 매도 가격이 존재할 가능성은 충분히 있다.

그렇다면 이 문제를 해결하기 위해서는 거래 별로 고유한 번호를 부여해야 한다. 고유 번호로는 어떤 것들이 좋을까? 일단 종목 코드들끼리는 중복될 가능성이 있고 매수일자끼리도 중복될 가능성이 있지만, 종목코드와 매수일자가 결합된 값이라면 중복될 가능성이 없다. 왜냐하면 특정 종목에 대해 1월 2일에 매수하고 1월 3일에 매수하면 매수했지, 1월 2일에 매수하고도 1월 2일에 다시 매수하는 경우는 없기 때문이다.

본격적으로 코드를 수정하기 전에 앞서 우리의 매수 대상 종목 리스트가 담겨 있는   buy_list  변수에서는 굳이 고유 번호를 입력해주지 않아도 된다. 왜냐하면 매수 대상 종목 데이터는 골든 크로스가 발생하면 편입되고 데드 크로스가 발생하면 삭제되기 때문이다. 골든 크로스는 두 번 연속으로 발생하지 않는다. 그렇다면 현재 보유 종목 리스트가 담겨 있는   self.df_account  변수에서는 어떨까? 현재 보유 종목 역시 중복될 가능성이 전혀 없다. 왜냐하면 buy_list를 기반으로 하여 제작되는 변수값이기 때문이다. 따라서 우리는 거래 이력 데이터가 입력되어 있는   self.df_tracking_data  변수 내에서만 중복 방지를 위한 고유 정보를 입력해주면 된다.(고유 정보는 'noun'이라는 칼럼에 입력될 것이다.)

self.tracking_data = {'code':[], 'buy_date':[], 'noun':[], 'buy_price':[], 'buy_quantity':[], 'buy_value':[],
                      'f_sell_date':[], 'f_sell_price':[], 'f_sell_quantity':[], 'f_sell_value':[], 'f_profit':[],
                      's_sell_date':[], 's_sell_price':[], 's_sell_quantity':[], 's_sell_value':[], 's_profit':[],
                      't_sell_date':[], 't_sell_price':[], 't_sell_quantity':[], 't_sell_value':[], 't_profit':[],
                      'n_sell_date':[], 'n_sell_price':[], 'n_sell_quantity':[], 'n_sell_value':[], 'n_profit':[],
                      'l_sell_date':[], 'l_sell_price':[], 'l_sell_quantity':[], 'l_sell_value':[], 'l_profit':[]}
self.df_tracking_data = pd.DataFrame(self.tracking_data, columns=['code', 'buy_date', 'noun', 'buy_price', 'buy_quantity', 'buy_value',
                                                                  'f_sell_date', 'f_sell_price', 'f_sell_quantity', 'f_sell_value', 'f_profit',
                                                                  's_sell_date', 's_sell_price', 's_sell_quantity', 's_sell_value', 's_profit',
                                                                  't_sell_date', 't_sell_price', 't_sell_quantity', 't_sell_value', 't_profit',
                                                                  'n_sell_date', 'n_sell_price', 'n_sell_quantity', 'n_sell_value', 'n_profit',
                                                                  'l_sell_date', 'l_sell_price', 'l_sell_quantity', 'l_sell_value', 'l_profit'])

 

다음으로는 매수 함수를 수정해주도록 하자. 매수 함수를 수정하는 이유는 바로 종목을 매수한 이후에   self.df_tracking_data  변수 내에 거래 데이터를 입력하기 때문이다. 아래의 코드 중 ## 수정된 부분 ## 바로 아래에 있는   inherit_data  가 바로 종목코드와 매수일자를 합친 데이터이며, 데이터프레임의 기반이 되는   item_data  라는 변수의 세 번째 자리에   inherit_data  변수를 입력해주고   tempt_item_data  변수에 있는 칼럼에서도 'noun'을 추가해주었다. 

def buy(self):

    for row in self.buy_list.itertuples():
        quantity = int(self.money_by_unit / int(row[3]))

        if self.init_money >= int(row[3]) * int(quantity):
            buy_data = [(row[2], row[1], row[3], int(quantity))]
            tempt_df_account = pd.DataFrame(buy_data, columns = ['date', 'code', 'buy_price', 'quantity'])
            self.df_account = self.df_account.append(tempt_df_account).reset_index(drop=True)
            self.init_money = self.init_money - (int(row[3]) * int(quantity))
            buy_value = int(row[3]) * int(quantity)

            ## 수정된 부분 ##
            inherit_data = str(row[1]) + str(row[2])

            item_data = [(row[1], row[2], inherit_data, row[3], buy_value, quantity, row[4], row[5], row[6], row[7], row[8])]
            tempt_item_data = pd.DataFrame(item_data, columns = ['code', 'buy_date', 'noun', 'buy_price', 'buy_value', 'buy_quantity',
            'ma5_buy', 'ma10_buy', 'ma20_buy', 'ma60_buy', 'ma120_buy'])
            self.df_tracking_data = self.df_tracking_data.append(tempt_item_data).reset_index(drop=True)

 

## 여기까지의 출력 결과 ##
     code  buy_date            noun  ...  ma20_buy      ma60_buy     ma120_buy
0  000100  20200113  00010020200113  ...  44028.20  41481.250000  41220.108333
1  002450  20200113  00245020200113  ...   1841.75   1796.916667   1801.041667

 

이제   self.df_tracking_data  변수 안에 입력된 고유 정보를 기반으로 그 거래 데이터가 입력되어 있는 인덱스 값을 받아올 수 있다. 일단 고유 정보의 위치를 확인하기 위해서는 매도 함수 내부에서도 종목 코드와 매수일자가 결합된 고유 정보를 제작해주어야 하므로   inherit_data  변수를 제작해서 값을 제작해주도록 하자. 

def sell(self):

    for row in self.df_account.itertuples():
        self.chart_data = self.load_chart_indate(self.yesterday, self.today, row[2])
        self.df_tracking_data = self.df_tracking_data.fillna(0)
        inherit_data = str(row[2]) + str(row[1])

 

그 후에는 거래 데이터가 입력되어 있는 행의 인덱스를 구해오기 위해 입력했던 코드를 수정해주어야 한다. 아래와 같이 'code'라고 되어 있는 부분을 'noun'으로,   row[2]  라고 되어 있던 부분을   inherit_data  로 변경해주면 된다.

## 수정 전 코드 ##
df_account_index = self.df_account.index[(self.df_account['code'] == row[2])].to_list()[0]
df_tracking_index = self.df_tracking_data.index[(self.df_tracking_data['code'] == row[2])].to_list()[0]

## 수정 후 코드 ##
df_account_index = self.df_account.index[(self.df_account['code'] == row[2])].to_list()[0]
df_tracking_index = self.df_tracking_data.index[(self.df_tracking_data['noun'] == inherit_data)].to_list()[0]

 

그 이후에는 아래와 같은 부분들의 인덱스 번호를 하나씩 추가해주어야 한다. 아래의 경우 각각 기존에는 7, 12, 17, 27번이었지면 앞 부분에 noun이 추가되면서 8, 13, 18, 28로 밀린 결과이다. 물론 고유 정보가 입력된 'noun' 칼럼을 데이터프레임의 맨 뒷 부분에 입력한다면 굳이 아래 부분을 모두 수정해주지 않아도 된다.

first_quantity = self.df_tracking_data.iloc[df_tracking_index][8]
sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]
third_quantity = self.df_tracking_data.iloc[df_tracking_index][18]
three_quantity = self.df_tracking_data.iloc[df_tracking_index][28]

 

 

출력 결과 확인하기

수정된 내용을 바탕으로 백테스팅을 다시 돌려보았더니 같은 종목을 거래한 이력은 있지만, 데이터가 중복되지는 않는 모습을 확인할 수 있었다.

 

한 종목을 대상으로 결과값을 요약하면 아래와 같은데, 정확하게 입력되었는지 확인하기 위해 실제 차트를 열어서 결과값을 대조해보도록 하자.

코드 매수일자 매도일자1 매도일자2 매도일자3 손절일자 수익금
003550 20200120       20200131 -44,978
003550 20200210 20200212     20200225 -22,556

 

 


728x90
반응형
Contents

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

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