PYTHON/Back test

백테스팅 구축 (22) - 매도 함수 수정하기 ⑥

  • -

지난 게시글에서도 이야기했듯이 아직 수정해야 할 부분이 하나 있는데, 그 내용은 단순한 내용이다. 바로 현재 보유 수량에 따른 거래 진행 방법의 수정인데, 아래의 사진을 참고하도록 하자.

작아서 잘 안 보일 수도 있으니 간단하게 설명을 해보자면 초기 매수 수량은 3주인데 반해, 첫 번째 매도 조건에서는 20%에 해당하는 1주를 매도했고, 두 번째 매도 조건에서는 50%에 해당하는 2주를 매도했다. 지금까지의 상황으로 보면 3주를 매수했고 이후에는 3주를 매도했기 때문에 추가적인 매도는 이루어지지 않아야 하지만, t_sell_quantity 칼럼에도 2라는 값이 입력되어 있는 것으로 보아 3주를 매수했음에도 5주를 매도했음을 확인할 수 있다. 즉, 없는 주식을 판 것이다.

 

매도 좀 똑바로 해라

이제 문제가 있는 세 번째 매도 조건을 수정해주도록 하자. 일단 현재 존재하는 문제점은 바로 매도 수량이 보유 수량보다 많음에도 불구하고 매도가 이루어진다는 점이었기 때문에, 매수 수량에서 전체 매도 수량을 뺀 값이 0보다 같거나 큰 경우에만 매도를 진행하도록 한다.

이는 어떤 경우인지 예를 들어보자면 총 6주를 매수했고 첫 번째에는 1주를, 두 번째에는 3주를 매도핬고 현재 2주가 남아있다고 했을 때, 6주의 30%는 1.8로 반올림하면 2주가 되니 6-1-3-2 >= 0이기 때문에 2주를 매도하면 되는 것이다. 하지만 총 5주를 매수한 경우에는 첫 번째에는 1주를, 두 번째에는 3주를 매도했고 현재 1주가 남았는데, 5주의 30%는 1.5로 반올림하면 2주가 된다. 따라서 5-1-3-2 < 0이기 때문에 2주가 아니라, 5-1-3을 계산한 값인 1주가 곧 보유 수량이 되고, 그 1주만을 매도하도록 하는 것이다. 전자가 if 문에 해당하고, 후자가 그 아래의 elif문에 해당한다. 따라서 elif문 아래에서는 5-1-3-2 < 0이라는 결과값이 나왔다면, 현재 2라는 값이 입력되어 있는   sell_quantity  값을 2가 아닌 1(=5주-1주-3주)로 변경해주어야 한다.

## 세 번째 수익률 조건
if float(self.chart_data['high'].iloc[1]) >= (float(row[3]) * float(self.third_sellprofit)):
    if self.df_tracking_data.iloc[df_tracking_index][6] != 0 and self.df_tracking_data.iloc[df_tracking_index][11] != 0 and self.df_tracking_data.iloc[df_tracking_index][16] == 0:
        first_quantity = self.df_tracking_data.iloc[df_tracking_index][7]
        sec_quantity = self.df_tracking_data.iloc[df_tracking_index][12]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(buy_quantity) - int(first_quantity) - int(sec_quantity) - int(sell_quantity) >= 0:
            sell_price = int(row[3]) * float(self.third_sellprofit)
            today_profit = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
            self.df_tracking_data.at[df_tracking_index, 't_sell_date'] = self.today
            self.df_tracking_data.at[df_tracking_index, 't_sell_price'] = int(sell_price)
            self.df_tracking_data.at[df_tracking_index, 't_sell_quantity'] = int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 't_sell_value'] = int(sell_price) * int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 't_profit'] = today_profit
            self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))
            self.today_profit = self.today_profit + today_profit

        elif int(buy_quantity) - int(first_quantity) - int(sec_quantity) - int(sell_quantity) < 0:
            ## 매도 수량이 현재 보유 수량보다 많은 경우
            sell_quantity = int(buy_quantity) - int(first_quantity) - int(sec_quantity)

            sell_price = int(row[3]) * float(self.third_sellprofit)
            today_profit = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
            self.df_tracking_data.at[df_tracking_index, 't_sell_date'] = self.today
            self.df_tracking_data.at[df_tracking_index, 't_sell_price'] = int(sell_price)
            self.df_tracking_data.at[df_tracking_index, 't_sell_quantity'] = int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 't_sell_value'] = int(sell_price) * int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 't_profit'] = today_profit
            self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))
            self.today_profit = self.today_profit + today_profit

 

 


728x90

 

 

손절매 함수 수정하기

세 번째 매도 함수를 수정했던 것과 마찬가지로, 손절매 함수 내에서도 그 수량을 계산해주어야 한다. 왜냐하면 손절매 함수에서도 없는 주식을 매도하는 경우가 존재할 수 있기 때문이다.

따라서 앞에서 했던 방법과 동일하게 first_quantity, sec_quantity, third_quantity 값을 얻어온 후에, buy_quantity(매수 수량)에서 각각의 값들을 뺴주도록 하자. 그 값이 곧 현재 보유 수량이 된다. 여기서는 복잡한 계산 과정을 이용하지 않아도 되는데, 그 이유는 위에서 없는 종목을 매도하지 않도록 수량을 조절해두었기 때문이다. 아래의 예시를 통해 이해해보도록 하자.

첫 번째로, 5주를 매수한 경우에는 first_quantity에는 1주가, sec_quantity에는 3주, third_quantity에는 1주가 입력(앞의 계산에 따라)되어 있을 것이다. 따라서 현재의   sell_quantity  는 0이 되기 때문에 손절매를 진행하지 않게 된다. 두 번째로는 10주를 매수한 후에 first_quantity에는 2주가, sec_quantity에는 5주가 입력되어 있지만 세 번째 매도 조건은 충족시키지 못해서 third_quantity에는 0이 입력되어 있는 경우이다. 이 경우 역시 매수 수량에서 첫 번째와 두 번째 매도 수량을 빼더라도 0보다는 크며, 세 번째는 거래를 하지 않았으므로 third_quantity는 0이 입력되어 있으므로 계산을 하나 마나 마찬가지이다.

이처럼 우리는 sell_quantity가 0인 경우에는 보유 종목이 없는 것으로 계산하고 손절매를 진행하지 않도록 하되, 0보다 큰 경우에는 보유 수량이 있는 것이기 때문에 손절매를 진행하도록 함으로써 보유 수량에 따른 오류를 해결할 수 있다.

## 손절 조건
if float(yester_ma5) > float(yester_ma20) and float(today_ma5) < float(today_ma20):

    first_quantity = self.df_tracking_data.iloc[df_tracking_index][7]
    sec_quantity = self.df_tracking_data.iloc[df_tracking_index][12]
    third_quantity = self.df_tracking_data.iloc[df_tracking_index][17]

    ## 매수 수량 - 첫 번째 매도 - 두 번째 매도 - 세 번째 매도 = 현재 보유 수량
    sell_quantity = int(buy_quantity) - int(first_quantity) - int(sec_quantity) - int(third_quantity)

    ## 매도 가능 수량이 1주 이상 있다면
    if sell_quantity == 0:
        pass

    elif sell_quantity > 0:
        today_close = self.chart_data['close'].iloc[1]  ## 매도 가격(sell_price)
        buy_value = int(row[3]) * int(row[4])
        sell_value = int(today_close) * int(row[4])
        code_profit = int(int(sell_value) - int(buy_value))   ## 매도 가격 빼기 매수 가격, 주당 손익

        self.init_money = self.init_money + int(sell_value)  ## 전체 매도 금액을 더하고
        self.today_profit = self.today_profit + int(code_profit)  ## 수익을 더하고

        self.df_account.drop(self.df_account.index[df_account_index], inplace=True)  ### 수정했음
        self.df_account = self.df_account.reset_index(drop=True)

        self.df_tracking_data.at[df_tracking_index, 'n_sell_date'] = self.today
        self.df_tracking_data.at[df_tracking_index, 'n_sell_price'] = today_close
        self.df_tracking_data.at[df_tracking_index, 'n_sell_value'] = sell_value
        self.df_tracking_data.at[df_tracking_index, 'n_profit'] = code_profit

 

 

결과 데이터 확인하기

사진이 잘 보이지 않아서 아래와 같이 데이터를 간략하게 정리하였다. 

코드 매수일 매수가격 손절일 손절가 손익
000100 20200113 44692 20200120 44213 -10,538
003580 20200114 8400 20200128 7770 -74,970
003690 20200115 9310 20200128 8830 -51,360
000720 20200117 42050 20200129 39650 -55,200

실제 데이터를 확인해본 결과는 아래와 같다.

 

이후에는 3번째 매도까지 잘 이루어진 종목을 확인할 수 있었는데, 그 데이터 역시 확인해보도록 하자.

매수 종목 코드는 002320, 매수일은 20200115, 첫 번째 매도는 20200128, 두 번째는 20200203, 세 번째는 20200207이다. 각각 5%, 10%, 15%의 수익을 기록했는지 확인해보도록 하자.

아직도 오류가 있다. 매수 직후 다음 날에 곧바로 20% 이상에 해당하는 수익이 발생했음에도 불구하고 매도가 이루어지지 않은 것이다. 다음 게시글에서 원인을 찾고 코드를 수정하도록 하자.

 

 


728x90
반응형
Contents

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

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