AUTO TRADE/Back test

백테스팅 구축 (8) - 매도 함수 구축

지난 게시글에서는 매수 조건 충족 종목(self.buy_list 변수)을 대상으로 for문을 통해 한 종목 한 종목 매수하는 방법에 대해 알아보았다. 이번 게시글에서는 일단 먼저 self.account를 보기 좋게 좀 편집하고, 현재 잔고가 부족한 경우에는 매수를 못하도록 하는 코드를 제작해보도록 하겠다.

 

self.account, 데이터프레임화

class algorithm1():
    def __init__(self, start_date, end_date, all_range):
        self.all_range = all_range
        self.today = start_date

        self.account = {'date':[], 'code':[], 'buy_price':[], 'quantity':[]}
        self.init_money = 10000000
        self.money_by_unit = 1000000

        while self.today != end_date:
            self.yesterday = self.cal_subday(self.today, 1)
            print("현재 조회일자:", self.today, "(self.today값), ", self.yesterday, "(self.yesterday값)")
            self.buy_list = self.check_list()
            self.buy()

            print("현재 보유 종목")
            
            #################
            ## 수정된 부분 ##
            self.df_account = pd.DataFrame(self.account, columns=['date', 'code', 'buy_price', 'quantity'])
            print(self.df_account)
            print("현재 잔고:", self.init_money)
            #################
                        
            self.today = self.cal_addday(self.today, 1)

## 출력 결과 ##
현재 조회일자: 20200103 (self.today값),  20200102 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
현재 잔고: 9122500

현재 조회일자: 20200106 (self.today값),  20200103 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
현재 잔고: 9122500

 

 

대출이라도 받아서 샀나?

아래의 결과물을 보면 알겠지만, 2020년 1월 16일에 매수한 이후 약 228만원이 남았지만, 그 다음 날인 1월 17일에는 매수하고 나니 잔고가 마이너스 67만원이 되었다. (돈이 어디있다고.. 돈이 없으면 사질 말아야지)

## 출력 결과 ##

현재 조회일자: 20200116 (self.today값),  20200115 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
1  20200113  000100     44692        22
2  20200113  002450      1980       505
3  20200114  003580      8400       119
4  20200115  000080     31650        31
5  20200115  001630    113000         8
6  20200115  002320     30426        32
7  20200115  003690      9310       107
현재 잔고: 2284824


현재 조회일자: 20200117 (self.today값),  20200116 (self.yesterday값)
현재 보유 종목
        date    code buy_price  quantity
0   20200103  004170    292500         3
1   20200113  000100     44692        22
2   20200113  002450      1980       505
3   20200114  003580      8400       119
4   20200115  000080     31650        31
5   20200115  001630    113000         8
6   20200115  002320     30426        32
7   20200115  003690      9310       107
8   20200117  000720     42050        23
9   20200117  001390     12800        78
10  20200117  003620      2180       458
현재 잔고: -679166

 

그렇다면 우리가 종목을 매수하도록 하는 절차를 구축한 함수는 buy()함수이므로, 그 함수를 수정해주어야 한다. 하지만 계산을 어떻게 해야 할까? 지금 우리의 매수 수량 계산식은 아래와 같다.

  • self.money_by_unit : 종목 당 최대 매수 금액 설정(1,000,000원)
  • self.init_money : 초기 시작 금액 설정(10,000,000원)
  • quantity : 1,000,000원 한도 내에서 매수할 수 있는 최대 수량 계산
  • row[3] : 해당 종목이 매수 조건을 충족시킨 일자의 종가로, 우리의 매수 가격과 같음

여기서의 수량(quantity)은 이미 종목 당 최대 매수 금액(self.money_by_unit)을 바탕으로 계산된 값이다. 따라서 추가적으로 매수할 수 있는지 없는지를 보기 위해서는 수량(quantity)과 매수 가격(row[3])을 곱한 값과 잔고(self.init_money) 금액을 비교함으로써 매수 가능 여부를 판단할 수 있다. 그럼 이제 buy() 함수에 아래와 같은 조건문(if 문)을 추가해보자. 그리고 else: 문에 해당하는 경우는 매수할 수 있는 돈이 없는 경우이기 때문에, "돈이 없어요.."를 출력하도록 했다. 앞에서 오류가 발생했던 2020년 1월 17일에 어떤 결과값이 출력되는지 확인해보도록 하자.

def buy(self):

    for row in self.buy_list.itertuples():
        quantity = int(self.money_by_unit / int(row[3]))

        if self.init_money >= int(row[3]) * int(quantity):
            self.account['code'].append(row[1])
            self.account['date'].append(row[2])
            self.account['buy_price'].append(row[3])
            self.account['quantity'].append(quantity)

            self.init_money = self.init_money - (int(row[3]) * int(quantity))

        else:
            print("돈이 없어요..")

## 출력 결과 ##
현재 조회일자: 20200116 (self.today값),  20200115 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
1  20200113  000100     44692        22
2  20200113  002450      1980       505
3  20200114  003580      8400       119
4  20200115  000080     31650        31
5  20200115  001630    113000         8
6  20200115  002320     30426        32
7  20200115  003690      9310       107
현재 잔고: 2284824

현재 조회일자: 20200117 (self.today값),  20200116 (self.yesterday값)
돈이 없어요..
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
1  20200113  000100     44692        22
2  20200113  002450      1980       505
3  20200114  003580      8400       119
4  20200115  000080     31650        31
5  20200115  001630    113000         8
6  20200115  002320     30426        32
7  20200115  003690      9310       107
8  20200117  000720     42050        23
9  20200117  001390     12800        78
현재 잔고: 319274

 

현재 조회일자 : 20200117 바로 밑에서 돈이 없어요.. 라는 결과값이 출력되는 것을 확인할 수 있다. 바로 위에 있는 결과값과 비교해보면, 해당 조건문을 추가하기 전에는 20200117을 기준으로 보유한 종목이 10개였지만 조건문을 추가하니 10번째 종목이 사라졌다. 왜냐하면 돈이 없어서 못 샀기 때문이다.

 


728x90

 

매도 조건 구축

이제부터는 매도 함수를 제작해보도록 하겠다. 일단 특정 일자를 대상으로 백테스팅을 조회할 때, 매수와 매도를 함께 진행해야 한다. 예를 들어 현재 20200103을 기준으로 백테스팅을 조회하고 있다면 20200103을 기준으로 매수를 한 후에, 현재 보유 중인 종목을 대상으로 20200103을 기준으로 매도 기준이 충족되는지를 확인해야 한다는 것이다. 20200103에 매수해놓고는 20200104에 매도하게 되는 불상사는 없어야 한다. 그러면 이제 매도 함수를 제작해보도록 하자.

일단 매도 함수 내에서는 self.df_account 변수를 대상으로 for문을 돌면서 값을 하나하나 확인하고 매도 조건이 충족되는지를 확인하면 된다. 제작 이후에 보면 매수 코드와는 다소 다른 코드를 가지고 있는 모습을 확인할 수 있는데, 매수는 종목을 찾고 매수를 해야 하지만 매도는 보유 중인 종목 내에서만 이루어질 수 있기 때문이다. 즉, 전 종목을 대상으로 돌려볼 필요가 없다.

def sell(self):

    for row in self.df_account.itertuples():
        print(row)

## 출력 결과 ##
현재 조회일자: 20200103 (self.today값),  20200102 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
현재 잔고: 9122500
Pandas(Index=0, date='20200103', code='004170', buy_price='292500', quantity=3) ## 이 부분이 row

 

다음으로, 우리가 매수 함수를 제작하고 매수 조건을 충족시키는 종목을 찾을 때 사용했던 함수 중에 load_chart_indate()라는 함수가 있다. 이 함수는 start_date, end_date, code라는 세 개의 인자를 요구하고, 우리가 매수할 때에는 각각의 자리에 self.yesterday, self.today, code라는 변수를 입력해서 그 결과값을 반환받았었다. 따라서 sell 함수 내에서도 같은 함수를 이용해서 이동평균선 간 대소 비교를 위한 데이터를 받아와서 출력해보자. 20200103의 경우에는 보유 종목이 하나니 결과값도 하나만 불러오지만, 20200113의 경우에는 보유 종목이 세 종목이니 결과값도 세 개인 것을 알 수 있다.

def sell(self):

    for row in self.df_account.itertuples():
        print(row)

        self.chart_data = self.load_chart_indate(self.yesterday, self.today, row[2])
        print(self.chart_data)

## 출력 결과 ##
현재 조회일자: 20200103 (self.today값),  20200102 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
현재 잔고: 9122500

Pandas(Index=0, date='20200103', code='004170', buy_price='292500', quantity=3)
          date    open    high  ...      MA20           MA60          MA120
1227  20200102  289500  296500  ...  285800.0  263558.333333  256820.833333
1228  20200103  296500  297000  ...  286325.0  264433.333333  257000.000000
[2 rows x 14 columns]

(중략)

현재 조회일자: 20200113 (self.today값),  20200110 (self.yesterday값)
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
1  20200113  000100     44692        22
2  20200113  002450      1980       505
현재 잔고: 7139376
Pandas(Index=0, date='20200103', code='004170', buy_price='292500', quantity=3)
          date    open    high  ...      MA20           MA60          MA120
1233  20200110  313000  319000  ...  291950.0  269416.666667  258416.666667
1234  20200113  314000  328500  ...  293800.0  270708.333333  258854.166667
[2 rows x 14 columns]
Pandas(Index=1, date='20200113', code='000100', buy_price='44692', quantity=22)
          date   open   high  ...      MA20      MA60         MA120
1233  20200110  43735  44405  ...  43868.35  41435.60  41191.941667
1234  20200113  44596  44788  ...  44028.20  41481.25  41220.108333
[2 rows x 14 columns]
Pandas(Index=2, date='20200113', code='002450', buy_price='1980', quantity=505)
          date  open  high  ...     MA20         MA60        MA120
1233  20200110  1800  1840  ...  1832.00  1794.416667  1800.916667
1234  20200113  1825  1990  ...  1841.75  1796.916667  1801.041667
[2 rows x 14 columns]

 

결과값을 받아왔으니, 이제는 self.chart_data에 입력되어 있는 데이터를 대상으로 이동평균선 간 대소 비교를 통해 매도를 진행해보자.

def sell(self):

    for row in self.df_account.itertuples():

        self.chart_data = self.load_chart_indate(self.yesterday, self.today, row[2])
        print(self.chart_data)

        yester_ma5 = self.chart_data['MA5'].iloc[0]
        yester_ma20 = self.chart_data['MA20'].iloc[0]
        today_ma5 = self.chart_data['MA5'].iloc[1]
        today_ma20 = self.chart_data['MA20'].iloc[1]

        if yester_ma5 > yester_ma20 and today_ma5 < today_ma20:
            print("매도 진행")
        else:
            pass

## 출력 결과 ##
현재 보유 종목
       date    code buy_price  quantity
0  20200103  004170    292500         3
1  20200113  000100     44692        22
2  20200113  002450      1980       505
3  20200114  003580      8400       119
4  20200115  000080     31650        31
5  20200115  001630    113000         8
6  20200115  002320     30426        32
7  20200115  003690      9310       107
8  20200117  000720     42050        23
9  20200117  001390     12800        78
현재 잔고: 319274

          date    open    high  ...      MA20           MA60          MA120
1238  20200117  323500  326500  ...  300000.0  276041.666667  260420.833333
1239  20200120  326000  332000  ...  301100.0  277241.666667  260704.166667
[2 rows x 14 columns]

date   open   high  ...      MA20          MA60         MA120
1238  20200117  43926  44309  ...  44251.75  41716.066667  41292.466667
1239  20200120  43735  44596  ...  44300.65  41765.950000  41312.825000
[2 rows x 14 columns]
매도 진행

 

다음 게시글에서는 매도 진행이라는 결과값이 출력되는 print() 문을 삭제하고 매도를 할 경우 보유 종목 현황에서 종목 데이터를 어떻게 삭제하는지, 그리고 수익금액은 어떻게 계산하는지 등에 대해 살펴보도록 하자.

 

 


728x90
반응형
Contents

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

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