PYTHON/Back test

백테스팅 구축 (21) - 매도 전략 수정하기 ⑤

  • -

이전 게시글에서는 백테스팅 결과를 확인했는데, 두 번째 매도 조건인 10% 수익과 세 번째 매도 조건인 15% 수익이 발생했음에도 불구하고 매도가 이뤄지지 않았다는 사실을 확인했다. 그렇다면 왜 매도 조건이 충족됐지만 매도할 수 없었는지 다시 확인해보도록 하자.

 

첫 번째 매도는 잘 된 거야?

일단 가장 먼저, 첫 번째 매도는 제대로 이뤄진 것인지 확인하기 위해 매도 가격(  f_sell_price  )이 매수 가격(  buy_price  ) 대비 5%의 수익이 발생한 가격인지 그리고 매도 비율은 0.2 비율에 맞춰서 잘 이뤄진 것인지 확인해보도록 하자. 수익률은 매도 가격 나누기 매수 가격이고, 매도 비율은 매도 수량 나누기 매수 수량이다.

 

결과값을 보니 상태가 이상하다.

데이터를 보니 수익률이 정확하게 1.05, 즉 5%의 수익률을 거둔 경우가 대부분이긴 하지만 이상하게 0.75 또는 0.85 값이 입력되는 경우들이 있다. 즉, 매수 가격보다 낮은 가격에 매도했음에도 불구하고 수익이 발생한 것이다. 하지만 수익률이 비정상적으로 계산되어 있는 자료들의 공통점이 있다. 바로 매도 수량이 정확하게 계산되지 않았다는 것이다. 아래의 사진을 보도록 하자. 

 

위의 사진에서 반올림값이라고 표시되어 있는 구간(연한 색 배경)은 엑셀 상에서 매수 수량을 기준으로 0.2를 곱한 후 반올림한 결과값이고, 그 왼쪽에 있는 sell_quantity가 파이썬 로직 상에서 계산된 반올림값이다. 연한 노란색보다 조금 진한 색으로 칠해져 있는 구간들을 보면 엑셀 상에서 계산된 값과 파이썬 로직 상에서 계산된 값이 다르다. 네 번째 거래를 보면 31주를 매수했으니 매수 수량인 31에 0.2를 곱했다면 6.2가 나오고 이를 반올림을 하면 6이라는 값이 나오지만, 파이썬에서는 매도 수량으로는 7주가 계산되어 데이터를 입력한 것이다.

 


728x90

 

매도 함수 다시 수정하기

디버그를 하면서 분석 결과를 보니, 보유 수량을 계산하는 과정에서 오류가 발생했다. 디버그를 할 때에는 그냥 print()문을 걸어놓고 실행하면서 어느 부분에서 오류가 발생하는지를 확인해도 되고, 로그를 사용해서 확인해도 되긴 하지만 로그 파일은 아직 import하지 않았으니 그냥 print()문을 사용해서 확인했다. 아래는 확인하는 과정에서의 코드와 그 출력 결과이다.

if (float(row[3]) * float(self.third_sellprofit)) > float(self.chart_data['high'].iloc[1]) >= (float(row[3]) * float(self.sec_sellprofit)):
    print("10%~15%")
    if self.df_tracking_data.iloc[df_tracking_index][6] != 0 and self.df_tracking_data.iloc[df_tracking_index][11] == 0:
        print("데이터 입력함")
        first_quantity = self.df_tracking_data.iloc[df_tracking_index][8]
        sell_quantity = round(float(self.sec_sellrate) * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity)) - int(sell_quantity) >= 1:
            print("보유 수량 분석하니 매도 가능")
            sell_price = int(row[3]) * float(self.sec_sellprofit)
            today_profit = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
            self.df_tracking_data.at[df_tracking_index, 's_sell_date'] = self.today
            self.df_tracking_data.at[df_tracking_index, 's_sell_price'] = int(sell_price)
            self.df_tracking_data.at[df_tracking_index, 's_sell_quantity'] = int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 's_sell_value'] = int(sell_price) * int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 's_profit'] = today_profit
            self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))
            self.today_profit = self.today_profit + today_profit

## 출력 결과 ##
현재 종목의 최고 수익률: 112.3076923076923
10%~15%
데이터 입력함

 

보유 수량을 계산하는 과정에서 그 결과값이 1보다 크지 않았기 때문에 두 번째 매도 조건이 충족됐음에도 불구하고 매도가 이루어지지 않았다는 것이기 때문에 해당 계산 과정의 코드를 수정해주도록 하자. 현재 코드는 매수한 수량 변수인   row[4]  를   buy_quantity  에 입력했기 떄문에   buy_quantity  에서 처음에 매수했던 수량인   first_quantity  를 빼고, 그 후에 매수 수량에 분할 매도 비율인 0.5를 곱한 값인   sell_quantity  를 빼고도 1주 이상을 보유하고 있다면 매도를 진행하라는 의미였다. 이게 어떤 부분에서 오류가 발생하는가 하면, 바로 3주인 경우에 오류가 발생한다. 이것이 첫 번째 오류이다.

예를 들어, 3주를 매수했고 그 중 20%는 첫 번째 매도 시점에서 매도했다고 가정해보자. 그렇다면 3주의 20%는 0.6주이고 0.6주는 거래가 불가능하니 반올림 후 1주를 매도했을 것이다. 그 후에 두 번째 매도 조건이 충족되어 3주의 50%인 1.5주, 반올림한 2주를 매도해야 하는데 앞전에 1주를 매도했으니 2주가 남았다. 이런 경우로 인해 매도가 이루어지지 않는 것이다. 즉, 위의 조건문 하에서는 3에서 1을 빼고, 2를 뺀 값이 1보다 크다면 매도를 진행하라는 의미이다. 

다음으로 두 번째 오류는,   self.df_tracking_data.iloc[df_tracking_data][index]  를 통해 데이터프레임의 특정 값에 인덱싱하는 과정에서 발생하는 오류가 존재한다는 것이다. 앞서 우리는 첫 번째 매도 조건을 충족하는 지점에서 매도했던 값을 얻어오기 위해   self.df_tracking_data.iloc[df_tracking_index][8]  이라는 값을 통해 인덱싱 했는데, 그 결과를 보니 [8]번 자리에는 매도 수량에 해당하는 f_sell_quantity가 아니라 f_sell_value가 위치해 있었다. 

따라서 이 오류를 해결하기 위해서는 일단 매도 수량을 뺀 후의 값이 0보다 크거나 같으면 매도를 진행하는 것으로 하고, 인덱싱에 있어서도 [8]이 아닌 [7]로 수정해주어야 한다. 마찬가지로 두 번째 매도 조건이 충족되어 매도했던 수량이 입력되어 있는 변수인   sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]  의 경우에도 [13]을 [12]로 변경해주어야 한다.

[추신] 코드를 잘 보면 첫 번째 매도 조건과 두 번째 매도 조건, 세 번째 매도 조건 모두에 입력되어 있는 공통적인 부분이 있는데, 바로 f_sell_date 또는 s_sell_date, t_sell_date 등 매도 일자에 입력된 데이터가 없는 경우에 매도를 진행하라는 것이었다. 우리는 이를   if self.df_tracking_data.iloc[df_tracking_index][6] == 0:  를 통해 처리했었는데, 여기서의 [6] 역시   f_sell_date   값이 아니라 [5]에 해당한다. 하지만 여태까지오류가 없었던 이유는 바로   self.df_tracking_data.iloc[df_tracking_index][6]  코드는 f_sell_price 값에 해당하는 인덱싱 방법인데, 어찌 됐든 간에 첫 번째 매도가 이뤄지기 전에는 [6]이나 [5]나 데이터가 입력되지 않고 0이 입력되어 있었기 때문에 오류 없이 진행이 가능했었다, 즉,   if self.df_tracking_data.iloc[df_tracking_index][6] == 0:  부분에서 [6]에 해당하는 부분에는 [5]나 [6]이나 [7] 또는 [9]까지 아무 값이나 입력해도 동작하는 데에는 아무런 문제가 없다는 것이다. 따라서 이 부분은 수정하지 않도록 하겠다.

그럼 이제 다시 본론으로 돌아와서 코드를 수정해보도록 하자. 

## 첫 번째 수익률 조건
if (float(row[3]) * float(self.sec_sellprofit)) > float(self.chart_data['high'].iloc[1]) >= (float(row[3]) * float(self.first_sellprofit)):

    if self.df_tracking_data.iloc[df_tracking_index][6] == 0:
        sell_quantity = round(float(self.first_sellrate) * int(row[4]))
        sell_price = int(row[3]) * float(self.first_sellprofit)
        today_profit = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
        self.df_tracking_data.at[df_tracking_index, 'f_sell_date'] = self.today
        self.df_tracking_data.at[df_tracking_index, 'f_sell_price'] = int(sell_price)
        self.df_tracking_data.at[df_tracking_index, 'f_sell_quantity'] = int(sell_quantity)
        self.df_tracking_data.at[df_tracking_index, 'f_sell_value'] = int(sell_price) * int(sell_quantity)
        self.df_tracking_data.at[df_tracking_index, 'f_profit'] = today_profit
        self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))
        self.today_profit = self.today_profit + today_profit


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

        # 매수 수량 - 첫 번째 매도 수량 - 두 번째 매도 수량 >= 0:
        if int(int(buy_quantity) - int(first_quantity)) - int(sell_quantity) >= 0:
            sell_price = int(row[3]) * float(self.sec_sellprofit)
            today_profit = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
            self.df_tracking_data.at[df_tracking_index, 's_sell_date'] = self.today
            self.df_tracking_data.at[df_tracking_index, 's_sell_price'] = int(sell_price)
            self.df_tracking_data.at[df_tracking_index, 's_sell_quantity'] = int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 's_sell_value'] = int(sell_price) * int(sell_quantity)
            self.df_tracking_data.at[df_tracking_index, 's_profit'] = today_profit
            self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))
            self.today_profit = self.today_profit + today_profit


## 세 번째 수익률 조건
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]))

        ## 매수 수량 - 첫 번째 매도 수량 - 두 번째 매도 수량 - 세 번째 매도 수량 >= 0:
        if int(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

## 손절 조건
if float(yester_ma5) > float(yester_ma20) and float(today_ma5) < float(today_ma20):
    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

 

 

결과값 확인하기

두 번째 매도나 세 번째 매도 모두 잘 입력되고 있음을 확인할 수 있다. 아직 한 가지 더 다듬어야 할 부분이 있는데, 그 내용은 다음 게시글에서 작성하도록 하겠다. 다음 게시글에서 그 내용을 수정한 후에는 이 알고리즘의 전체적인 결과값을 다시 한 번 확인해보도록 하자.

 

 


728x90
반응형
Contents

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

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