백테스팅 구축 (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번째 종목이 사라졌다. 왜냐하면 돈이 없어서 못 샀기 때문이다.
매도 조건 구축
이제부터는 매도 함수를 제작해보도록 하겠다. 일단 특정 일자를 대상으로 백테스팅을 조회할 때, 매수와 매도를 함께 진행해야 한다. 예를 들어 현재 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() 문을 삭제하고 매도를 할 경우 보유 종목 현황에서 종목 데이터를 어떻게 삭제하는지, 그리고 수익금액은 어떻게 계산하는지 등에 대해 살펴보도록 하자.
'AUTO TRADE > Back test' 카테고리의 다른 글
백테스팅 구축 (10) - 차트와 대조하기 (0) | 2021.07.06 |
---|---|
백테스팅 구축 (9) - 실시간 잔고 업데이트 (0) | 2021.07.06 |
백테스팅 구축 (7) - 매수 조건 설정 및 계좌 현황 제작 (0) | 2021.07.05 |
백테스팅 구축 (6) - 오류 수정 (0) | 2021.07.05 |
백테스팅 구축 (5) - 매수 조건 제작 (0) | 2021.07.04 |
소중한 공감 감사합니다