PYTHON/Back test

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

  • -

지난 게시글에서는 분할 매도 과정에서 발생할 수 있는 문제점을 어떻게 처리해야 하는지 그리고 조건문 아래에는 어떤 조건을 설정해두어야 오류 없이 데이터를 저장할 수 있는지에 대해 살펴보았다. 따라서 이번 게시글에서는 조건문 아래를 제작한 후 전체 코드를 디버그함으로써 결과값을 확인해보도록 하겠다.

 

손절 조건 수정하기

일단 기본적으로 우리는 이전에 5일 이동평균선과 20일 이동평균선의 데드 크로스가 발생했을 경우 수익 실현을 하는 코드를 제작했었는데, 저번 게시글에서 이 전략을 수정해서 데드 크로스가 발생했을 경우에는 보유 수량을 모두 매도하는 방향으로 설정했고 수익 실현 구간이 아닌 손절매 구간으로 수정했다. 

따라서 해당 코드를 손절매 구간으로 설정했기 때문에 데이터를 입력하는 지점 역시 기존의 칼럼이 아닌 새로운 칼럼으로 변경해주어야 한다. 이전에는 단순하게   sell_date, sell_price, sell_value  등을 사용했었지만 이제는 칼럼을 새롭게 추가했기 때문에   n_sell_date, n_sell_price, n_sell_value  등에 데이터를 입력해줘야 한다. 또한 중간에   self.df_account.drop  이라는 코드가 있는데, 이는 수정하지 않아도 된다. 이 코드는 현재 조회 중인 종목이 손절 조건이 충족됐을 경우 매도한 후에 현재 보유 종목에서 데이터를 삭제하라는 것이기 떄문이다. (모든 코드는 동일하고,   self.df_tracking_data.at  부분만 수정됐다.)

## 손절 조건
## 수정 전 코드
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_tracking_data.at[df_tracking_index, 'sell_date'] = self.today
    self.df_tracking_data.at[df_tracking_index, 'sell_price'] = today_close
    self.df_tracking_data.at[df_tracking_index, 'sell_value'] = sell_value
    self.df_tracking_data.at[df_tracking_index, 'profit'] = code_profit
    self.df_tracking_data.at[df_tracking_index, 'ma5_sell'] = self.chart_data['MA5'].iloc[1]
    self.df_tracking_data.at[df_tracking_index, 'ma10_sell'] = self.chart_data['MA10'].iloc[1]
    self.df_tracking_data.at[df_tracking_index, 'ma20_sell'] = self.chart_data['MA20'].iloc[1]
    self.df_tracking_data.at[df_tracking_index, 'ma60_sell'] = self.chart_data['MA60'].iloc[1]
    self.df_tracking_data.at[df_tracking_index, 'ma120_sell'] = self.chart_data['MA120'].iloc[1]

## 손절 조건
## 수정 후 코드
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_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

 

 

첫 번째 매도 지점 코드 제작하기

첫 매도는 어떤 시점에 발생하는가 하면, 우리가 매수했던 가격(  row[3]  )에 우리가 설정했던 수익률(  self.first_sellprofit  )을 곱한 값보다 현재 시점(  self.today  )의 해당 종목의 고가(  self.chart_data['high'].iloc[1]  )가 더 높다면 기존에 설정한 매도 비율(  self.first_sellrate  )대로 매도하는 것이다. 기존에 제작했던 첫 매도 신호 발생 조건문 코드는 아래와 같다. 

## 첫 번쨰 수익률 조건
## 조회 시점의 종가가 매수 가격 * 수익률보다 클 경우
if float(self.chart_data['high'].iloc[1]) >= (int(row[3]) * int(self.first_sellprofit)):
    if self.df_tracking_data.iloc[df_tracking_index][6] == 0:
        print("첫 번째 수익률 발생")

 

여기서 보아야 하는 것은 현재 우리가 입력한   self.first_sellprofit  이 몇인지이다. 예전에는 0.05로 설정했었는데, 이 0.05로 두게 된다면 매수 가격에 0.05를 곱한 금액보다 크면 매도한다는 것이기 떄문에, 매수한 즉시 매도할 가능성이 높다. 왜냐하면 이 계산대로라면 우리가 종목을 100원에 매수했고, 이 종목의 주가가 5원보다 크다면 매도하라는 것과 같기 때문이다. 따라서   def __init__  하단의   sellprofit  변수들을 다음과 같이 변경해주도록 하자. 아래와 같이 1.05와 같은 식으로 계산해준다면 매수가격이 100원일 때 105원 이상인 경우에는 매도를 진행하게 되므로 5%의 수익을 얻을 수 있다.

## 수익률 설정
self.first_sellprofit = 1.05
self.sec_sellprofit = 1.1
self.third_sellprofit = 1.15

 

이제 다음으로는 분할 매도 수량을 계산해주도록 하자. 분할 매도 비율은 역시   self.first_sellrate  등과 같은 변수를 통해 지정해주었으니, 이를 바탕으로 계산한 수량만큼만 수익을 실현해준 후에 해당 수량은 현재 보유 종목 데이터가 담겨져 있는   self.df_account  변수에서 빼주어야 한다. 그럼 우리는 첫 번째 매도 조건이 충족된 시점에서 매도할 수량을 계산해야 하는데, 이전 게시글에서 이 수량을 반드시 반올림(사사오입)을 통해 정수 형태로 계산해주어야 한다고 설명했었다.

아래의 코드는 우리가 현재 sell() 함수 내에서 현재 보유 종목 변수(  self.df_account  )를 대상으로 for문을 돌리고 있는 코드이며, 각 한 줄 한 줄에 입력되어 있는 데이터가 row에 해당한다. 또한 이   self.df_account  변수 안에는 'date', 'code', 'buy_price', 'quantity', 'profit'이라는 총 다섯 개의 칼럼명이 입력되어 있으며 데이터프레임으로 만드는 과정에서 'index'라는 칼럼이 추가되었으니   row[1]  은 'date',   row[2]  는 'code',   row[4]  는 'quantity'를 의미하게 된다. 우리는 이   row[4]  와 앞서 제작한   self.first_sellrate  라는 두 개의 변수를 바탕으로 매도할 수량을 계산할 수 있으며, 반올림하는 방법은   round()  메서드를 이용해서 계산할 수 있다. 

## sell() 함수 내의 for문 ##
for row in self.df_account.itertuples():

 

위의 내용을 바탕으로 첫 번째 매도 조건이 충족됐을 경우 매도해야 할 수량은 아래와 같이 계산할 수 있다. 분할 매도 비율(  self.first_sellrate  )에 현재 보유 수량(  row[4]  )를 곱한 후,   round()  를 통해 반올림한 값이 첫 번째 매도 조건 충족 시 매도해야 할 수량에 해당하는 것이다. 또한 매도 가격은 조회 시점의 고가(  self.chart_data['high'].iloc[1]  )가 아니라 매수 가격에 첫 번째 매도 수익률을 곱한 값(  row[3] * self.first_sellprofit  )이 된다. 

[주의]   self.first_sellprofit  의 경우에는 현재 1.05나 1.1과 같은 소수점으로 입력되어 있다. 따라서 매수 가격을 의미하는 변수인   int(row[3])  에 소수점을 곱해야 하는데   int(self.first_sellprofit)  으로 입력하게 되면 소수점이 아니라 1.05나 1.1을 정수로 표현한 1을 곱하게 되므로 수익률이 곱해진 매도 가격인   sell_price  가 정확하게 계산되지 않고 다시 매수 가격에 매도를 하게 된다. 따라서   self.first_sellprofit  의 경우에는   int()  메서드를 통해 정수로 변환하면 안 된다.  

## 첫 번쨰 수익률 조건
## 조회 시점의 종가가 매수 가격 * 수익률보다 클 경우
if float(self.chart_data['high'].iloc[1]) >= (int(row[3]) * int(self.first_sellprofit)):
    if self.df_tracking_data.iloc[df_tracking_index][6] == 0:
        sell_quantity = round(self.first_sellrate * int(row[4]))
        sell_price = int(row[3]) * float(self.first_sellprofit)

 

이제 이렇게 계산을 했다면, 앞서 제작했던 거래 내역 데이터 변수(  self.df_tracking_data  )안에 있는 칼럼에 각각의 데이터들을 입력해주어야 한다. 첫 번째 매도 시에는   f_sell_date, f_sell_price, f_sell_value, f_profit  네 개의 칼럼이 있으니 각각의 칼럼에 데이터들을 입력해주면 된다.

  • f_sell_data : self.today. 조회 시점의 일자
  • f_sell_price : int(sell_price),  현재의 매도 가격
  • f_sell_quantity : int(sell_quantity), 첫 번째 매도 조건 충족 시 매도 수량
  • f_sell_value : int(sell_price) * int(sell_quantity), 현재의 매도 가격에 분할 매도 비율을 곱한 매도 수량
  • f_profit : 앞서 계산한 f_sell_value 값에서 매수했던 가격에 매수 수량을 곱한 값을 뺀 금액
## 첫 번쨰 수익률 조건
## 조회 시점의 종가가 매수 가격 * 수익률보다 클 경우
if float(self.chart_data['high'].iloc[1]) >= (int(row[3]) * int(self.first_sellprofit)):
    if self.df_tracking_data.iloc[df_tracking_index][6] == 0:
        sell_quantity = round(self.first_sellrate * int(row[4]))
        sell_price = int(row[3]) * float(self.first_sellprofit)
        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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))

 

이제 마지막으로, 매도했다면 매도한 금액만큼 우리가 초기에 설정한 금액 변수(  self.init_money  )에 그 금액을 더해주어야 계속해서 거래를 진행할 것이니 그와 관련된 부분도 코드를 구축해주도록 하자. 

## 첫 번쨰 수익률 조건
## 조회 시점의 종가가 매수 가격 * 수익률보다 클 경우
if float(self.chart_data['high'].iloc[1]) >= (int(row[3]) * int(self.first_sellprofit)):
    if self.df_tracking_data.iloc[df_tracking_index][6] == 0:
        sell_quantity = round(self.first_sellrate * int(row[4]))
        sell_price = int(row[3]) * float(self.first_sellprofit)
        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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
        self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))

 

첫 번째 매도 조건이 충족된 경우의 코드는 비교적 간단하게 제작할 수 있었지만, 두 번째부터는 조금 복잡해질 수도 있다. 두 번째 매도 조건은 다음 게시글에서 제작하도록 하겠다.

 

 


728x90
반응형
Contents

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

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