<4. 지표 혼합 수익률 백테스팅 프로그래밍 - (1) >



이전 포스트까지 각 지표 순위별 수익률을 백테스팅해봤다. 이번 포스트부터는 시가 총액, PER, PBR 3가지 지표를 혼합해 선택된 주식들을 가지고 백테스팅할 것이다. 아래는 프로그램 결과 화면이다. 시가총액과 PBR 하위 10%(PBR>0.2) 의 코스닥 종목들을 골라 변동성 돌파 전략을 수행했고 연마다 리밸런싱했다. 백테스팅 기간은 2008년 1월 1일부터 2018년 1월 1일로 했다. 슬리피지 값은 0.4% 로 했다. 


프로그램 설명


코드를 보기 전에 위 프로그램의 주요 기능을 살펴보자. 프로그램에 대해 알아야 코드가 쉽게 이해 된다. 사용자는 어떤 지표로 주식을 선정할 지 설정한다. 필자가 제시하는 코드는 (시가 총액, PBR), (시가총액, PER), (시가총액, PSR), (PBR, PER), (시가 총액, PER, PBR) 묶음을 지표로 선정할 수 있다. 코드를 이해한다면 다른 조합도 쉽게 추가할 수 있다. 선택된 조합에 대해서 몇 퍼센트 범위의 주식을 선정할 지도 사용자가 선택한다. "5"를 주면 각 지표를 5등분해 하위 또는 상위 20% 주식을 선정한다. 물론 코스피에 상장된 주식을 대상으로 할지, 코스닥에 상장된 주식으로 할지 아니면 코스닥/코스피 전체에 상장된 주식으로 백테스팅할 지 선택할 수 있다. 또한 이전 포스트에서와 같이 Scope 값도 선택 가능하다. 마지막으로 백테스팅할 기간을 설정한다. 백테스팅 기간은 2008년 1월 1일부터 2018년 1월 1일까지 1년 단위로 선택한다. 



프로그램 코드


먼저 DB관련 코드, 실제 계산 코드 등 공통적으로 사용할 상수나 함수를 정의한 코드에 대해 알아보자. 먼저, 전체 코드를 보고 세부 사항을 설명하겠다.

import logging as log import datetime from dateutil.relativedelta import relativedelta KOSPI = "kospi" KOSDAQ = "kosdaq" ALL = "all" TMV_PBR = "Total Market Value and PBR" TMV_PER = "Total Market Value and PER" TMV_PSR = "Total Market Value and PSR" PER_PBR = "PER and PBR" PBR_PSR = "PBR and PSR" TMV_PBR_PER = "Total Market Value and PBR and PSR" TMVALUE = "tmvalue" PER = "per" PSR = "psr" PBR = "pbr" start_2008 = "2008-01-01" start_2009 = "2009-01-01" start_2010 = "2010-01-01" start_2011 = "2011-01-01" start_2012 = "2012-01-01" start_2013 = "2013-01-01" start_2014 = "2014-01-01" start_2015 = "2015-01-01" start_2016 = "2016-01-01" start_2017 = "2017-01-01" start_2018 = "2018-01-01" _07_3Q = "07_3Q" _07_4Q = "07_4Q" _08_1Q = "08_1Q" _08_2Q = "08_2Q" _08_3Q = "08_3Q" _08_4Q = "08_4Q" _09_1Q = "09_1Q" _09_2Q = "09_2Q" _09_3Q = "09_3Q" _09_4Q = "09_4Q" _10_1Q = "10_1Q" _10_2Q = "10_2Q" _10_3Q = "10_3Q" _10_4Q = "10_4Q" _11_1Q = "11_1Q" _11_2Q = "11_2Q" _11_3Q = "11_3Q" _11_4Q = "11_4Q" _12_1Q = "12_1Q" _12_2Q = "12_2Q" _12_3Q = "12_3Q" _12_4Q = "12_4Q" _13_1Q = "13_1Q" _13_2Q = "13_2Q" _13_3Q = "13_3Q" _13_4Q = "13_4Q" _14_1Q = "14_1Q" _14_2Q = "14_2Q" _14_3Q = "14_3Q" _14_4Q = "14_4Q" _15_1Q = "15_1Q" _15_2Q = "15_2Q" _15_3Q = "15_3Q" _15_4Q = "15_4Q" _16_1Q = "16_1Q" _16_2Q = "16_2Q" _16_3Q = "16_3Q" _16_4Q = "16_4Q" _17_1Q = "17_1Q" _17_2Q = "17_2Q" _17_3Q = "17_3Q" yearly_dates = [start_2008, start_2009, start_2010, start_2011, start_2012, start_2013, start_2014, start_2015, start_2016, start_2017, start_2018] quarters = [{"_1qt" : _08_1Q, "_3qt" : _07_3Q}, {"_1qt": _09_1Q, "_3qt": _08_3Q}, {"_1qt": _10_1Q, "_3qt": _09_3Q}, {"_1qt": _11_1Q, "_3qt": _10_3Q}, {"_1qt": _12_1Q, "_3qt": _11_3Q}, {"_1qt": _13_1Q, "_3qt": _12_3Q}, {"_1qt": _14_1Q, "_3qt": _13_3Q}, {"_1qt": _15_1Q, "_3qt": _14_3Q}, {"_1qt": _16_1Q, "_3qt": _15_3Q}, {"_1qt": _17_1Q, "_3qt": _16_3Q},] def get_date_ranges(start, end): try: start_index = yearly_dates.index(start) end_index = yearly_dates.index(end) start_days = yearly_dates[start_index : end_index] end_days = yearly_dates[start_index+1: end_index+1] except IndexError as e: log.warning("Settings index Error : {}".format(repr(e))) raise return zip(start_days, end_days) def get_quarter_ranges(start, end): try: start_index = yearly_dates.index(start) end_index = yearly_dates.index(end) quarter_list = quarters[start_index: end_index] except IndexError as e: log.warning("Settings index Error : {}".format(repr(e))) raise return quarter_list

이전 포스트들을 읽었다면 알 수 있을 부분을 제외하고 설명한다. "get_date_ranges" 는 start와 end 기간동안을 연 단위로 쪼개서 리턴한다. 해당 함수가 필요한 이유는 구현할 프로그램이 연마다 리밸런싱 하기 때문에 기간을 쪼개야 한다. 예로 get_date_ranges("2009-01-01", "2011-01-01")을 호출하면 zip(["2009-01-01", "2010-01-01"], ["2010-01-01", "2011-01-01"])을 리턴한다. 따라서 ("2009-01-01", "2010-01-01"), ("2010-01-01", "2011-01-01") 을 얻어 기간을 연 단위로 분리할 수 있다. get_quarter_ranges() 또한 마찬가지다. 시가총액에는 1분기 데이터를 적용하고 나머지 지표들은 3분기 데이터를 적용한다. 따라서 "quarters" 변수에 1,3분기 데이터를 넣었고 get_quarter_ranges() 메서드로 start와 end기간 동안 필요한 데이터를 리턴한다. 코드를 보면 쉽게 이해할 수 있을 것이다. 


다음은 DB 관련 코드를 살펴볼 것이다. 하지만 DB 관련 코드가 길기에 다음 포스트에 이어서 연재할 예정이다. 길지만 동일한 로직이 많아 쉽게 이해할 수 있을 것이다.




참고로 필자는 컴퓨터 공학과를 재학 중인 대학생입니다. 따라서 코드가 완벽할 수 없습니다. 알고리즘이나 코드가 비효율적이거나 오류가 있다면 댓글 달아주세요..

+ Recent posts