PYTHON/Back test

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

  • -

지난 게시글에서는 첫 번째 매도 조건이 충족됐을 경우 어떻게 코드를 구축하는지에 대해 살펴봤었으니, 이번 게시글에서는 두 번째 매도 조건과 세 번째 매도 조건이 충족됐을 때에는 어떤 방식으로 매도를 진행해야 하는지에 대해 살펴보도록 하자.

 

두 번쨰 매도 조건 수정하기

두 번째 매도 조건부터는 남아 있는 수량을 확실하게 계산해주어야 한다. 즉, 현재 보유 종목 데이터가 포함되어 있는 변수에서 현재 보유 중인 주식수에서 첫 번째 매도 조건에서 매도한 수량만큼을 제외한 나머지 수량 안에 우리가 매도할 수 있는 수량이 충분히 포함되어 있는지를 확인해주어야 한다는 것이다.

일단 첫 번째 매도 조건문 하에서 제작했던 코드를 그대로 가지고 와서 두 번째 매도 조건에도 제작해주도록 하자. 변경해주어야 할 부분은   self.first_sellrate  를   self.sec_sellrate  로,   sell.first_sellprofit  을   self.sec_sellprofit  으로. 그리고   f_sell_date  는   s_sell_date  로 변경해주는 것이다. 

## 두 번째 수익률 조건
if 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:
        sell_quantity = round(self.sec_sellrate * int(row[4]))
        sell_price = int(row[3]) * float(self.sec_sellprofit)
        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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))

 

그 다음에는 매도 수량과 현재 보유 수량을 계산해주어야 한다. 사실 이는 첫 번째 매도 시점에서도 필요한 작업이긴 하지만, 두 번째 매도 조건이 충족된 시점에서 더 잘 와닿기 때문에 여기서 설명하는 것이다. 즉, 첫 번째 매도 시점에서 매도한 수량은 이미   self.df_tracking_data  변수 안에 있는 f_sell_quantity 칼럼에 입력되어 있다.

그렇다면 f_sell_quantity에 입력되어 있는 값은 어떻게 인덱싱(인덱스를 통한 접근 방법을 지칭하는 용어)할 수 있을까? 기존에 제작했던 self.df_tracking_data 변수의 칼럼을 확인해보면 된다. 아래를 보면 'f_sell_date'는 우리가 인덱스 번호 6을 통해 접근했으니 'f_sell_price'는 인덱스 번호가 7번, 'f_sell_quantity'는 인덱스 번호가 8번이 된다.

self.df_tracking_data = pd.DataFrame(self.tracking_data, columns=['code', 'buy_date', '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'])

 

그럼 두 번째 매도 시점에서 매도 대상이 되는 매도 수량은   sell_quantity  변수에 입력되어 있으니, 우리는 두 번째 매도 조건이 충족된 시점에서 보유 주식수를 계산해주고 그 보유 주식수와 두 번째 매도 조건 하에서의 매도 대상 수량 변수인   sell_quantity  의 값을 비교함으로써 거래를 진행할지 말지를 결정할 수 있다. 어차피 분할 매도 비율은 다 합해봐야 1인데 뭐하러 이렇게까지 하냐는 의견이 있을 수도 있지만 우리는 반올림을 통해 매도 수량을 계산한 후에 거래하기 때문에 문제가 발생할 수 있다는 점을 고려해야 한다.

일단 우리가 맨 처음에 매수했던 수량은 sell 함수 하단의 for 문을 보면 알겠지만,   row  에 인덱싱을 통해 값을 얻어올 수 있으며, 이전 게시글에서도 설명했듯이 매수 수량은 row[4]가 된다. 따라서 for문 아래에서   buy_quantity  라는 변수 안에 매수 수량을 입력해주도록 하자. for문 하단에 제작하는 이유는 이렇게 해야 조건문 하나하나마다 이 값을 받아서 계산해줄 필요가 없기 때문이다.

def sell(self):

    for row in self.df_account.itertuples():
        self.chart_data = self.load_chart_indate(self.yesterday, self.today, row[2])

        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]
        self.df_tracking_data = self.df_tracking_data.fillna(0)

        buy_quantity = row[4]

 

다시 두 번째 수익률 조건에 해당하는 조건문으로 내려와서,   first_quantity  라는 변수 안에 첫 번째 매도 조건이 충족된 시점에서 매도했던 수량을 입력해주도록 하자.

## 두 번째 수익률 조건
if 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][8]
        sell_quantity = round(self.sec_sellrate * int(row[4]))
        sell_price = int(row[3]) * float(self.sec_sellprofit)
        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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))

 

그렇다면 이제 맨 처음에 매수한 수량인   buy_quantity  와 첫 번째 매도 시점에서 매도한 수량인   first_quantity  의 차이값이 곧 현재 매도 가능 수량이자 현재 보유 주식수에 해당하는 값이 된다. 따라서 그 값과 현재 계산된   sell_quantity  를 비교해서   sell_quantity  값이 더 작은 경우에만 매도를 진행하도록 하는 코드를 구축해야 한다. 아래의 조건문을 보면 계산한 결과값이 1보다 크거나 같은 경우로 설정해뒀는데, 그 이유는 바로 1주 이상은 있어야 매도할 수 있기 때문이다. 그 후에는 두 번째 매도 조건 하단 부분에서도 매도 대금을   self.init_money  에 더해주도록 하자. 

## 두 번째 수익률 조건
if 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][8]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity)) - int(sell_quantity) >= 1:
            sell_price = int(row[3]) * float(self.sec_sellprofit)
            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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))

 

 


728x90

 

세 번째 매도 조건 수정하기

세 번째 매도 조건에서는 두 번째 매도 조건 하에서와 마찬가지로 첫 번째 매도와 두 번째 매도가 모두 이루어진 후에 진행되어야 하며, 첫 번째 조건과 두 번째 조건이 충족되어 매도된 수량을 제외한 나머지 수량을 대상으로 거래가 이루어져야 한다.

마찬가지로   first_quantity  와   sec_quantity   sell_quantity  세 개의 변수를 제작해주도록 하자. 두 번째 조건이 충족되어 매도한 주식수 데이터를 이용하기 위한 인덱스 번호는 13번이다.(위에 있는   self.df_tracking_data  변수를 확인) 

## 세 번째 수익률 조건
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][8]
        sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

 

이제 여기서도 마찬가지로 첫 번째로 매수한 수량(  buy_quantity  )에서 첫 번째로 매도한 수량(  first_quantity  )과 두 번째도 매도한 수량(  sec_quantity  )을 뺀 후의 값이 1보다 같거나 크다면 매도를 진행하도록 한다. 

## 세 번째 수익률 조건
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][8]
        sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity) - int(sec_quantity)) >= 1:
            pass

 

이제 맨 밑에 있는 조건문 아래의 코드의 형태는 두 번째 매도 조건의 코드와 동일하고 그 안에 입력되어 있는 변수들만 변경해주면 된다.

## 세 번째 수익률 조건
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][8]
        sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity) - int(sec_quantity)) >= 1:
            sell_price = int(row[3]) * float(self.third_sellprofit)
            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'] = (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))
            self.init_money = self.init_money + (int(sell_price) * int(sell_quantity))

 

 

손절매 매도 조건 수정하기

손절매를 진행하는 코드 안에는   self.df_account.drop()  코드가 제작되어 있다. 즉, 매도한 종목은   self.df_account  변수, 현재 보유 종목 현황 변수 내에서 데이터를 제거하라는 것이다. 하지만 이 손절매나 첫 번째 조건 하에서의 매도 또는 두 번째 매도나 세 번째 매도 모두   sell()  이라는 하나의 함수 내에서 for문을 통해 이뤄지는 것이기 때문에 데이터를 지운 후에는 인덱스를 새롭게 전환해주어야 발생할 수 있는 몇 가지 오류들을 제거할 수 있다. 따라서 손절매 조건 하에 있는 코드 중에서 drop을 실행한 이후에,   self.df_account  변수를 대상으로   reset_index(drop=True)  를 추가로 입력해주자.

## 손절 조건
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)
    
    ## 수정된 코드 ## 뒷 부분에 .reset_index가 추가됨
    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

 

 

매도 함수 코드 전체 수정하기

얼떨결에 디버그를 해봤는데, 일별 손익과 누적 손익이 계산되지 않는 모습을 확인했다. 확인해보니 일별손익 부분에 별도로 계산하는 코드를 매도 함수 안에 입력하지 않았기 때문이다. 따라서 첫 번째 매도 코드와 두 번째 매도 코드, 세 번째 매도 코드 중에서 각각 'f_profit', 's_profit', 't_profit'에 데이터를 입력하는 값에는   (int(sell_price) * int(sell_quantity)) - (int(row[3]) * int(sell_quantity))  내용이 입력되어 있을 것인데, 이제 이를   today_profit  이라는 변수 안에 따로 저장한 후에 데이터를 입력하는 값에는 위의 길다란 코드를   today_profit  변수로 대체해주자. 그리고 최하단에는 self.todiay_profit = self.today_profit + today_profit 코드를 추가해줌으로써 일별 실현손익 데이터를 계속해서 업데이트하도록 제작하자.

## 첫 번쨰 수익률 조건
## 조회 시점의 종가가 매수 가격 * 수익률보다 클 경우
if 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(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(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][8]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity)) - int(sell_quantity) >= 1:
            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][8]
        sec_quantity = self.df_tracking_data.iloc[df_tracking_index][13]
        sell_quantity = round(self.sec_sellrate * int(row[4]))

        if int(int(buy_quantity) - int(first_quantity) - int(sec_quantity)) >= 1:
            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

 

 

결과 확인해보기

여기까지 제작한 코드를 바탕으로 제작되는 결과값들을 확인해보도록 하자. 일단 자료형이 너무 길어서 파이참 내에서는 정확하게 출력되지 않으니 엑셀 파일을 통해 확인하도록 하자.

데이터를 보니 첫 번째 매도 조건이 충족된 경우에는 매도가 잘 이뤄지고 있지만, 두 번째 매도와 세 번째 매도의 경우에는 데이터가 입력되지 않고 있다. 어떤 연유에서인지 확인해보도록 하자. 

이와 관련하여 10% 또는 15%의 수익이 발생하지 않아서 입력되지 않은 것일 수도 있지 않나 하는 궁금증이 들 수도 있는데, 결과값을 보면 알 수 있듯이 그건 아니다. 아래의 데이터를 보면 알 수 있지만, 3월에 매수해서 5월에 매도한 거래에서 67만원의 수익을 얻었다. 우리는 기존에 종목 당 최대 매수 금액으로   self.money_by_unit  변수를 두었고, 이 변수 값으로는 100만원을 설정했기 때문이다. 즉 67%의 수익이 발생했음에도 불구하고 10%와 15%에서 매도하지 않았다는 것은 연산 처리에서의 오류가 있음을 의미한다.

 

 


728x90
반응형
Contents

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

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