백테스팅 구축 (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
손절매 함수 수정하기
세 번째 매도 함수를 수정했던 것과 마찬가지로, 손절매 함수 내에서도 그 수량을 계산해주어야 한다. 왜냐하면 손절매 함수에서도 없는 주식을 매도하는 경우가 존재할 수 있기 때문이다.
따라서 앞에서 했던 방법과 동일하게 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% 이상에 해당하는 수익이 발생했음에도 불구하고 매도가 이루어지지 않은 것이다. 다음 게시글에서 원인을 찾고 코드를 수정하도록 하자.
'AUTO TRADE > Back test' 카테고리의 다른 글
백테스팅 구축 (24) - 결과 데이터 확인하기 (0) | 2021.07.11 |
---|---|
백테스팅 구축 (23) - 매도 함수 수정하기 ⑦ (0) | 2021.07.11 |
백테스팅 구축 (21) - 매도 전략 수정하기 ⑤ (0) | 2021.07.10 |
백테스팅 구축 (20) - 매도 전략 수정하기 ④ (0) | 2021.07.10 |
백테스팅 구축 (19) - 매도 전략 수정하기 ③ (0) | 2021.07.10 |
소중한 공감 감사합니다