Shift#

Shift 은 이전 row 나 이후 row 에 있는 값을 가져올 수 있는 메소드입니다. 일단 삼성전자 일봉을 가져오겠습니다.

import FinanceDataReader as fdr 

code = '005930' # 삼성전자
stock_data = fdr.DataReader(code, start='2021-01-03', end='2021-12-31') 

stock_data.head().style.set_table_attributes('style="font-size: 12px"')
  Open High Low Close Volume Change
Date            
2021-01-04 00:00:00 81000 84400 80200 83000 38655276 0.024691
2021-01-05 00:00:00 81600 83900 81600 83900 35335669 0.010843
2021-01-06 00:00:00 83300 84500 82100 82200 42089013 -0.020262
2021-01-07 00:00:00 82800 84200 82700 82900 32644642 0.008516
2021-01-08 00:00:00 83300 90000 83000 88800 59013307 0.071170

일봉 데이터에서 전날의 종가를 당일로 가져와 보겠습니다. 아래 예제를 보시면 2021년 1월 5일 'Previous Close' 컬럼에 1월 4일 종가가 들어가 있습니다. 1월 4일은 전날이 없어서 NaN (값없음) 처리 되었습니다.
stock_data['Previous Close'] = stock_data['Close'].shift(1)
stock_data.head(6).style.set_table_attributes('style="font-size: 12px"')
  Open High Low Close Volume Change Previous Close
Date              
2021-01-04 00:00:00 81000 84400 80200 83000 38655276 0.024691 nan
2021-01-05 00:00:00 81600 83900 81600 83900 35335669 0.010843 83000.000000
2021-01-06 00:00:00 83300 84500 82100 82200 42089013 -0.020262 83900.000000
2021-01-07 00:00:00 82800 84200 82700 82900 32644642 0.008516 82200.000000
2021-01-08 00:00:00 83300 90000 83000 88800 59013307 0.071170 82900.000000
2021-01-11 00:00:00 90000 96800 89500 91000 90306177 0.024775 88800.000000

이제 아주 단순한 전략을 구현해 보겠습니다. 구현해 볼 단순 전략은 '전날 종가보다 오늘 종가가 높으면 내일 시가에 매수하고 내일 종가에 매도' 입니다. 결과가 어떨지 정말 궁금합니다. 이 전략을 구현하면 수익율이 어떻게 될 지 테스트 해보겠습니다. 먼저 전날 종가보다 오늘 종가가 높은 날을 찾아야 합니다. 전날 종가는 이미 만들어서 'Previous Close' 컬럼에 저장해 두었습니다. 오늘 종가와 전날 종가를 비교한 후, True 이면 1 되도록 하겠습니다. 조건 (stock_data['Close'] > stock_data['Previous Close']) 는 True/False 를 반환합니다. 그래서, astype(int) 를 이용해서 정수로 변환합니다.

그 다음 수익율 데이터를 만들어 보겠습니다. 내일의 시가는 stock_data[‘Open’].shift(-1), 내일의 종가는 stock_data[‘Close’].shift(-1) 로 가져오면 됩니다. 결과를 컬럼 ‘return’ 에 넣겠습니다. shift(1) 는 전날의 정보를 shift(-1) 은 다음날의 데이터를 가져옵니다.

stock_data['buy'] = (stock_data['Close'] > stock_data['Previous Close']).astype(int) # 매수 시그널 생성
stock_data['return'] = stock_data['Close'].shift(-1) / stock_data['Open'].shift(-1) # 전략의 수익율
stock_data.head(6).style.set_table_attributes('style="font-size: 12px"')
  Open High Low Close Volume Change Previous Close buy return
Date                  
2021-01-04 00:00:00 81000 84400 80200 83000 38655276 0.024691 nan 0 1.028186
2021-01-05 00:00:00 81600 83900 81600 83900 35335669 0.010843 83000.000000 1 0.986795
2021-01-06 00:00:00 83300 84500 82100 82200 42089013 -0.020262 83900.000000 0 1.001208
2021-01-07 00:00:00 82800 84200 82700 82900 32644642 0.008516 82200.000000 1 1.066026
2021-01-08 00:00:00 83300 90000 83000 88800 59013307 0.071170 82900.000000 1 1.011111
2021-01-11 00:00:00 90000 96800 89500 91000 90306177 0.024775 88800.000000 1 1.003322

이제 buy 시그널이 1 인 날의 수익율과 0 인 날의 수익율을 groupby 을 이용해서 비교해보겠습니다. 결과가 실망입니다. 좋은 전략이 아닌 것 같습니다. 100 원을 투자했으면 평균 기대수익율이 99.8 원입니다. 여기서 평균 수익율은 buy 가 1 인 날 중 랜덤한 날에 투자했을 때 기대할 수 있는 수익율이 0.998 (0.2% 손실) 이라는 의미입니다. describe 메소드로 수익율의 분포도 확인해 보겠습니다. buy 가 1 인 날(매수)은 0 인 날에 비하여 평균도 낮고, 변동성(std) 이 더 큽니다. 차라리 전날 종가보다 오늘 종가가 높을 때 매수하는 것이 더 좋을 것 같습니다.
import pandas as pd
pd.options.display.float_format = '{:,.3f}'.format

stock_data.dropna(inplace=True) # NaN(값 없음) 열 전부 제거
print(stock_data.groupby('buy')['return'].mean()) # 평균 비교
print('\n')
print(stock_data.groupby('buy')['return'].describe()) # 분포 비교
buy
0   0.999
1   0.998
Name: return, dtype: float64


      count  mean   std   min   25%   50%   75%   max
buy                                                  
0   136.000 0.999 0.011 0.970 0.992 1.000 1.005 1.033
1   110.000 0.998 0.013 0.975 0.990 0.997 1.004 1.066

위에서 구현한 단순 전략은 손실을 보는 전략입니다. 이번에는 만약 우리가 100 원을 투자했으면 110 영업일 이후에 얼마나 손해를 보는 지 확인 해 보겠습니다. 위 describe 결과에서 buy 가 1 인 날은 110일 입니다. 이번에 쓸 메소드는 prod 입니 다. prod 는 값을 다 곱하라는 뜻입니다. 만약 당일 수익율이 0.9 이고 다음날 1.1 이면, 최종 수익율은 0.99 (=0.9 x 1.1) 가 됩니다. 아래 결과에서와 같이 단순 전략으로 2021년 초에 삼성전자에 100원을 투자하면 110 일 이후인 2021년 연말에는 잔고가 81.1 원이 됩니다. 약 19% 의 손실이 발생했습니다.
print(stock_data.groupby('buy')['return'].prod())
buy
0   0.843
1   0.811
Name: return, dtype: float64