< 6. 실 거래 후 보완된 ETF 변동성 돌파 전략 - 마지막>
이번 포스트에서는 지금까지 계산한 데이터를 가지고 백테스팅을 수행해 아래와 같이 수익율 차트, MDD, 연 평균 수익율을 출력할 것이다.
2011년 7월부터 18년 3월까지 수익율을 계산한다. 슬리피지는 0.1퍼이다. Noise는 0부터 0.5까지 0.1 단위로 모든 범위를 대상으로 한다. 최근 n일 최적 Scope와 Over_night 유무, 최근 m일 Noise 별로 수익율을 구한다. (n=[10,20,40,60,120], m=[1,10,20,40,60,120])
위 수익율을 계산하기 전에 아래의 sql 문을 이해해야 한다.
select pft.Date, pft.Profit from (select Code, Date, Profit from profit_date_10 where Date>='2011-07-01') as pft inner join (select Code, Date from noise_20 where Noise>=0.1 and Noise < 0.3) as Noi on pft.Code=Noi.Code and pft.Date=Noi.Date order by pft.Date
Noise 값에 따라서 Profit를 구해야 하기 때문에 profit_date_XX 테이블과 noise_XX 테이블을 조인해야 한다.
위 예를 보면 'profit_date_10' 테이블에서 Code, Date, Profit 데이터를 추출한다. 11년 7월부터 백테스팅하기에 Date 조건도 추가한다. 'profit_date_XX' 테이블은 "4. 최적 수익율 계산"에서 설명했다. 0.1에서 0.3 사이의 Noise 값을 가진 데이터를 'noise_20' 테이블에서 추출한다. 'noise_XX' 테이블은 이전 포스트에서 설명했다. 그리고 inner join 결합 기준으로 Code와 Date를 설정해 Noise값과 Profit 값을 결합시킨다. 그 결과로 노이즈 값에 따른 수익율 데이터를 추출한다. 이 데이터를 가지고 수익율 계산만 하면 위 차트를 출력할 수 있다.
먼저 DB 관련 코드를 보자.
from sqlalchemy import create_engine import pymysql pymysql.install_as_MySQLdb() import pandas as pd import logging as log class StockDB(): def __init__(self, password): log.info("Connecting database.....") try: self.engine = create_engine("mysql+mysqldb://root:"+password+"@localhost/etf1", encoding='utf-8') self.conn = pymysql.connect(host='localhost', user='root', password=password, db='etf1', charset='utf8') self.cursor = self.conn.cursor() except Exception as e: log.warning("Connecting database Error : {}".format(repr(e))) def select_test_data(self, scope_during, noise_during, noise_start, noise_end, over_night): log.info("Selecting test data - scope_during : {} , noise_during : {}".format(scope_during, noise_during)) if over_night == True: sql = "select pft.Date, pft.Profit from " \ "(select Code, Date, Profit from profit_date_" + scope_during + "_over where Date>='2011-07-01') as pft inner join " \ "(select Code, Date from noise_" + noise_during + " where Noise>=" + str(noise_start) + " and Noise < " + str(noise_end) + ") " \ "as Noi on pft.Code=Noi.Code and pft.Date=Noi.Date order by pft.Date" else: sql = "select pft.Date, pft.Profit from " \ "(select Code, Date, Profit from profit_date_" + scope_during + " where Date>='2011-07-01') as pft inner join " \ "(select Code, Date from noise_" + noise_during + " where Noise>=" + str(noise_start) + " and Noise < " + str(noise_end) + ") " \ "as Noi on pft.Code=Noi.Code and pft.Date=Noi.Date order by pft.Date" print(sql) try: data = pd.read_sql(sql, self.conn) except Exception as e: log.warning("Selecting test data - scope_during : {} , noise_during : {}, Error : {}".format(scope_during, noise_during,repr(e))) return data
import pandas as pd import pandas import DB as db from mpldatacursor import datacursor from matplotlib.dates import DateFormatter import matplotlib.pyplot as plt import logging as log import sys log.basicConfig(stream=sys.stdout, level=log.DEBUG) dB = db.StockDB("qhdks12#$") def backtesting(scope_during, noise_during, noise_start, noise_end, over_night): # 수익율 데이터 추출 backtest_data = dB.select_test_data(scope_during, noise_during, noise_start, noise_end, over_night) if len(backtest_data)==0: return # 일별로 그룹핑해 수익율의 평균을 계산하여 일별 수익율 계산 profit = backtest_data.groupby('Date')['Profit'].mean() # 누적 수익율 계산 result = pd.DataFrame({'Profit': (profit).cumprod()}) # Drawdown 계산 result['Drawdown'] = result['Profit'] / (result['Profit'].cummax()) result['Return'] = profit # 연평균 수익율 계산 cagr = (result['Profit'][-1] / result['Profit'][0]) ** (1 / 6.75) - 1 result = result.subtract(1) # MDD 추출 mdd = result['Drawdown'].min() # cul_profit.plot(grid=True, logy=True) axes = result.plot(grid=True) lines = axes.get_lines() fmt = DateFormatter('%Y-%m-%d') datacursor(lines, formatter=lambda **kwargs: 'Return : {y:.4f}'.format(**kwargs) + '\ndate: ' + fmt(kwargs.get('x'))) L = plt.legend() L.get_texts()[0].set_text('Cul Return, CAGR : %0.2f' % (cagr)) L.get_texts()[1].set_text('Drawdawn, MDD : %0.2f' % (mdd)) title = "11.07 ~ 18.03 Return - Scope : {}, OverNight : {}, Noise : {} ({}~{})".format(scope_during, over_night, noise_during, noise_start, noise_end) plt.title(title) #plt.show() # 차트를 파일로 저장 file_title = "{},{},{},{}~{}.png".format(scope_during, over_night, noise_during, noise_start, noise_end) plt.savefig(file_title) scope_durings = ['10','20','40','60','120'] noise_durings = ['1','5','10','20','40','60','120'] noise_range = [0,0.1,0.2,0.3,0.4,0.5] over_nights = [True, False] for over_night in over_nights: for scope_during in scope_durings: for noise_during in noise_durings: for i, start_noise in enumerate(noise_range): for end_noise in noise_range[i+1:]: backtesting(scope_during,noise_during,start_noise, end_noise, over_night) log.info("finish {} {} {} {} {}".format(scope_during,noise_during,start_noise, end_noise, over_night))
주석을 보면 코드가 이해 될 것이다. 위와 같이 결과로 나올 수 있는 모든 경우를 계산해 파일로 저장한다.
위 코드를 실행하면 천 개가 넘는 차트를 얻을 수 있다.
그 결과 가장 좋은 수익율을 기록한 백테스팅은 아래와 같다. 연평균 수익율이 28%에 MDD가 -6% 이다.
참고로 필자는 컴퓨터 공학과를 재학 중인 대학생입니다. 따라서 코드가 완벽할 수 없습니다. 알고리즘이나 코드가 비효율적이거나 오류가 있다면 댓글 달아주세요..
'주식 프로그래밍(시스템 트레이딩)' 카테고리의 다른 글
5. 실 거래 후 보완된 ETF 변동성 돌파 전략(파이썬) (0) | 2018.04.05 |
---|---|
4. 실 거래 후 보완된 ETF 변동성 돌파 전략(파이썬) (0) | 2018.04.05 |
3. 실 거래 후 보완된 ETF 변동성 돌파 전략(파이썬) (0) | 2018.04.04 |
2. 실 거래 후 보완된 ETF 변동성 돌파 전략(파이썬) (1) | 2018.04.04 |
1. 실 거래 후 보완된 ETF 변동성 돌파 전략 (0) | 2018.04.04 |