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


1. 시가 총액/PER/PBR 기준 변동성 돌파 전략 분석 프로그래밍 - 개요

2. 각 지표 순위별 수익률 백테스팅 프로그래밍 - (1)

3. 각 지표 순위별 수익률 백테스팅 프로그래밍 - (2)


해당 포스트를 읽기 전에 위 포스트를 읽길 추천한다.


이전 포스트에서 "지표 혼합 수익률 백테스팅" 프로그램과 Utitlity.py 코드에 대해 알아봤다. 이번 포스트에서는 해당 프로그램의 DB 관련 코드를 살펴볼 것이다. 먼저 전체 소스를 보고 세세한 부분을 알아가보자. 

import aiomysql as aio import logging as log import pandas as pd import Utility class StockDB(): def __init__(self, during, type, divide): self.during = during self.type = type self.divide = divide if type == Utility.TMV_PBR: self.get_bactesting_sql = self.__get_tmv_and_pbr_sql elif type == Utility.TMV_PER: self.get_bactesting_sql = self.__get_tmv_and_per_sql elif type == Utility.TMV_PSR: self.get_bactesting_sql = self.__get_tmv_and_psr_sql elif type == Utility.PER_PBR: self.get_bactesting_sql = self.__get_pbr_and_per_sql elif type == Utility.PBR_PSR: self.get_bactesting_sql = self.__get_pbr_and_psr_sql elif type == Utility.TMV_PBR_PER: self.get_bactesting_sql = self.__get_tmv_and_pbr_and_per_sql async def init_pool(self,loop): log.info("Connection to Connection Pool") try: self.__pool = await aio.create_pool(host='127.0.0.1', port=3306, user='root', password='qhdks12#$', db='stock',loop=loop) except: log.warning("Connecting Pool Error {}".format(repr(0))) raise async def req_stock_index_count(self, sql): log.debug("Selecting market index row count") try: async with self.__pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute(sql) result = await cur.fetchone() except aio.Error as e: log.warning("Selecting stock index Error : {}".format(repr(e))) raise log.debug("Stock index count - {}".format(result)) return result[0] async def req_backtesting_data(self, market, start, end, quarter): log.info("Selecting backtesting data") sql = "select kos.Code, kos.Date, kos.Profit from " \ "(select Code, Date, Profit from " + market + "_profit_" + self.during + " where Date>\'" + start + "\' and Date<\'" + end + "\') " \ "as kos join " sql = sql + await self.get_bactesting_sql(market, quarter) try: async with self.__pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute(sql) rows = await cur.fetchall() result = pd.DataFrame.from_records(list(rows)) if result.empty is True: raise ResourceWarning result.columns = ['Code','Date', 'Profit'] except aio.Error as e: log.warning("Selecting backtesting data Error : {}".format(repr(e))) raise except ResourceWarning as re: log.warning("No Data - sql : {}".format(sql)) raise ResourceWarning return result def __get_tmv_count_sql(self, quarter): log.debug("Creating total market value row count sql") sql = "select count(Code) from tmvalue where " + quarter["_1qt"] + " != 0 " return sql def __get_pbr_count_sql(self, quarter): log.debug("Creating pbr row count sql") sql = "select count(Code) from pbr where " + quarter["_3qt"] + " >= 0.2 " return sql def __get_per_count_sql(self, quarter): log.debug("Creating per row count sql") sql = "select count(Code) from per where " + quarter["_3qt"] + " >= 3 " return sql def __get_psr_count_sql(self, quarter): log.debug("Creating psr row count sql") sql = "select count(Code) from psr where " + quarter["_3qt"] + " > 0 " return sql async def __get_tmv_and_pbr_sql(self,market, quarter): tmv_count = await self.req_stock_index_count(self.__get_tmv_count_sql(market, quarter)) pbr_count = await self.req_stock_index_count(self.__get_pbr_count_sql(market, quarter)) tmv_offset = str(int(tmv_count/self.divide)) pbr_offset = str(int(pbr_count/self.divide)) sql = "(select tmv.Code from (select Code from tmvalue where " + quarter["_1qt"] + " != 0 order by " \ + quarter["_1qt"] + " limit 0 , " + tmv_offset + ")" \ " as tmv join (select Code from pbr where " + quarter["_3qt"] + " >=0.2 order by " \ + quarter["_3qt"] + " limit 0 , " + pbr_offset + ") " \ "as pb on tmv.Code = pb.Code) as res on kos.Code=res.Code;" return sql async def __get_tmv_and_per_sql(self,market, quarter): tmv_count = await self.req_stock_index_count(self.__get_tmv_count_sql(market, quarter)) per_count = await self.req_stock_index_count(self.__get_per_count_sql(market, quarter)) tmv_offset = str(int(tmv_count/self.divide)) per_offset = str(int(per_count/self.divide)*5) sql = "(select tmv.Code from (select Code from tmvalue where " + quarter["_1qt"] + " != 0 order by " \ + quarter["_1qt"] + " desc limit 0 , " + tmv_offset + ")" \ " as tmv join (select Code from per where " + quarter["_3qt"] + " >=3 order by " \ + quarter["_3qt"] + " desc limit 0 , " + per_offset + ") " \ "as pe on tmv.Code = pe.Code) as res on kos.Code=res.Code;" return sql async def __get_tmv_and_psr_sql(self,market, quarter): tmv_count = await self.req_stock_index_count(self.__get_tmv_count_sql(market, quarter)) psr_count = await self.req_stock_index_count(self.__get_psr_count_sql(market, quarter)) tmv_offset = str(int(tmv_count/self.divide)) psr_offset = str(int(psr_count/self.divide)) sql = "(select tmv.Code from (select Code from tmvalue where " + quarter["_1qt"] + " != 0 order by " \ + quarter["_1qt"] + " limit 0 , " + tmv_offset + ")" \ " as tmv join (select Code from psr where " + quarter["_3qt"] + " >0 order by " \ + quarter["_3qt"] + " limit 0 , " + psr_offset + ") " \ "as pe on tmv.Code = pe.Code) as res on kos.Code=res.Code;" return sql async def __get_pbr_and_per_sql(self,market, quarter): pbr_count = await self.req_stock_index_count(self.__get_pbr_count_sql(market, quarter)) per_count = await self.req_stock_index_count(self.__get_per_count_sql(market, quarter)) pbr_offset = str(int(pbr_count/self.divide)) per_offset = str(int(per_count/self.divide)) sql = "(select pb.Code from (select Code from pbr where " + quarter["_3qt"] + " >=0.2 order by " \ + quarter["_3qt"] + " limit 0 , " + pbr_offset + ") " \ " as pb join (select Code from per where " + quarter["_3qt"] + " >=3 order by " \ + quarter["_3qt"] + " limit 0 , " + per_offset + ") " \ "as pe on pb.Code = pe.Code) as res on kos.Code=res.Code;" return sql async def __get_pbr_and_psr_sql(self,market, quarter): pbr_count = await self.req_stock_index_count(self.__get_pbr_count_sql(market, quarter)) psr_count = await self.req_stock_index_count(self.__get_per_count_sql(market, quarter)) pbr_offset = str(int(pbr_count/self.divide)) psr_offset = str(int(psr_count/self.divide)) sql = "(select pb.Code from (select Code from pbr where " + quarter["_3qt"] + " >=0.2 order by " \ + quarter["_3qt"] + " limit 0 , " + pbr_offset + ") " \ " as pb join (select Code from per where " + quarter["_3qt"] + " >0 order by " \ + quarter["_3qt"] + " limit 0 , " + psr_offset + ") " \ "as pe on pb.Code = pe.Code) as res on kos.Code=res.Code;" return sql async def __get_tmv_and_pbr_and_per_sql(self,market , quarter): tmv_count = await self.req_stock_index_count(self.__get_tmv_count_sql(market, quarter)) pbr_count = await self.req_stock_index_count(self.__get_pbr_count_sql(market, quarter)) per_count = await self.req_stock_index_count(self.__get_per_count_sql(market, quarter)) pbr_offset = str(int(pbr_count / self.divide)) per_offset = str(int(per_count / self.divide)) tmv_offset = str(int(tmv_count/self.divide)) sql = "(select distinct(tmv.Code) from (select Code from tmvalue where " + quarter["_1qt"] + " != 0 " \ "order by " + quarter["_1qt"] + " limit 0 , " + tmv_offset + ") as tmv join " \ "(select pb.Code from (select Code from pbr where " + quarter["_3qt"] + " >=0.2 " \ "order by " + quarter["_3qt"] + " limit 0 , " + pbr_offset + ") as pb join " \ "(select Code from per where " + quarter["_3qt"] + " >= 3 " \ "order by " + quarter["_3qt"] + " limit 0 , " + per_offset + ") as pe on pb.Code = pe.Code) " \ "as p on tmv.Code=p.Code) as res on kos.Code=res.Code" return sql async def req_kospi_kosdaq_data(self, start, end): log.info("Selecting kospi/kosdaq data") kospi_sql = "select Date, Close from kospi where Date<\'"+end+"\' and Date >=\'"+start+"\';" kosdaq_sql = "select Date, Close from kosdaq where Date<\'"+end+"\' and Date >=\'"+start+"\';" try: async with self.__pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute(kospi_sql) rows = await cur.fetchall() kospi = pd.DataFrame.from_records(list(rows)) kospi.columns = ['Date', 'Close'] kospi = kospi.set_index('Date') await cur.execute(kosdaq_sql) rows = await cur.fetchall() kosdaq = pd.DataFrame.from_records(list(rows)) kosdaq.columns = ['Date', 'Close'] kosdaq = kosdaq.set_index('Date') except aio.Error as e: log.warning("Selecting backtesting data Error : {}".format(repr(e))) raise return [kospi, kosdaq]


먼저 초기화 부분을 알아보자. init_pool()은 디비와의 커낵션 풀을 생성한다. during은 이전 포스트들에서 계속 말했듯이 Scope 값을 결정할 때 사용한다. divide는 지표를 기준으로 주식을 divide 등분으로 나눠 최상위 또는 최하위 주식들을 선택하게 한다. type은 백테스팅하는 데 사용한 지표이다. 이전 포스트에서 본 Utility.py의 상수로 값을 대입한다. 그리고 초기화할 때 받은 type 값으로 self.get_bactesting_sql 을 설정한다. 설정될 각각의 __get~() 메서드는 sql 문을 생성하는 함수이다. 선택하는 각 지표마다 DB에서 접근하는 SQL문이 다르다. 또한 sql문 안에 들어가는 변수 값도 실행할 때마다 다르다. 따라서 초기화 할 때 어떤 sql문을 이용할 지 결정해 매번 DB에 접근할 때마다 어떤 sql문을 이용할 지 계산하는 번거로움을 없앴다. 

    def __init__(self, during, type, divide):
        self.during = during
        self.type = type
        self.divide = divide

        if type == Utility.TMV_PBR:
            self.get_bactesting_sql = self.__get_tmv_and_pbr_sql
        elif type == Utility.TMV_PER:
            self.get_bactesting_sql = self.__get_tmv_and_per_sql
        elif type == Utility.TMV_PSR:
            self.get_bactesting_sql = self.__get_tmv_and_psr_sql
        elif type == Utility.PER_PBR:
            self.get_bactesting_sql = self.__get_pbr_and_per_sql
        elif type == Utility.PBR_PSR:
            self.get_bactesting_sql = self.__get_pbr_and_psr_sql
        elif type == Utility.TMV_PBR_PER:
            self.get_bactesting_sql = self.__get_tmv_and_pbr_and_per_sql

    async def init_pool(self,loop):
        log.info("Connection to Connection Pool")
        try:
            self.__pool = await aio.create_pool(host='127.0.0.1', port=3306, user='root', password='qhdks12#$', db='stock',loop=loop)
        except:
            log.warning("Connecting Pool Error {}".format(repr(0)))
            raise



다음 메서드는 인자로 받은 sql문을 실행한다. 해당 sql문은 특정 지표에 대한 주식 개수를 리턴한다. 해당 함수 인자로 받을 sql문 생성 함수를 알아보자. 

    async def req_stock_index_count(self, sql):
        log.debug("Selecting market index row count")

        try:
            async with self.__pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(sql)
                    result = await cur.fetchone()
        except aio.Error as e:
            log.warning("Selecting stock index Error : {}".format(repr(e)))
            raise

        log.debug("Stock index count - {}".format(result))

        return result[0]


아래 4개의 메서드는 시가총액, pbr, per, psr 기준으로 총 주식 개수를 반환한다. __get_tmv_count_sql()은 인자로 받은 분기에 시가총액이 0이 아닌 주식 개수를 반환한다. pbr은 0.2 이상인 주식 개수, per은 3 이상인 주식 개수, psr은 0보다 큰 주식 개수를 반환한다. 

def __get_tmv_count_sql(self, quarter): log.debug("Creating total market value row count sql") sql = "select count(Code) from tmvalue where " + quarter["_1qt"] + " != 0 " return sql def __get_pbr_count_sql(self, quarter): log.debug("Creating pbr row count sql") sql = "select count(Code) from pbr where " + quarter["_3qt"] + " >= 0.2 " return sql def __get_per_count_sql(self, quarter): log.debug("Creating per row count sql") sql = "select count(Code) from per where " + quarter["_3qt"] + " >= 3 " return sql def __get_psr_count_sql(self, quarter): log.debug("Creating psr row count sql") sql = "select count(Code) from psr where " + quarter["_3qt"] + " > 0 " return sql



아래 메서드는 설정한 지표들의 상위 또는 하위 n% 내에 모두 들어가는 주식의 매수/매도 수익율 데이터를 DB에서 불러오는 메서드이다. sql문을 생성하는 부분을 제외하면 이전 포스트에서 설명했기에 쉽다. 아래 메서드에서 사용하는 sql 문에 대해 알아보자. 아래 sql 문을 보자.

    async def req_backtesting_data(self, market, start, end, quarter):
        log.info("Selecting backtesting data")

        sql = "select kos.Code, kos.Date, kos.Profit from " \
              "(select Code, Date, Profit from " + market + "_profit_" + self.during + " where Date>\'" + start + "\' and Date<\'" + end + "\') " \
              "as kos join "


        sql = sql + await self.get_bactesting_sql(market, quarter)


        try:
            async with self.__pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(sql)
                    rows = await cur.fetchall()
                    result = pd.DataFrame.from_records(list(rows))
                    if result.empty is True:
                        raise  ResourceWarning
                    result.columns = ['Code','Date', 'Profit']
        except aio.Error as e:
            log.warning("Selecting backtesting data Error : {}".format(repr(e)))
            raise
        except ResourceWarning as re:
            log.warning("No Data - sql : {}".format(sql))
            raise ResourceWarning

        return result


아래 sql 문은 17년 1분기 시가 총액이 0이 아닌 종목들 중 하위 1위에서 115등까지의 종목을 불러온다. 또한 16년도 pbr이 0.2 이상인 종목들 중 하위 1위에서 112등까지 종목들을 불러온다. 시가총액과 pbr에서 불러온 종목들을 join해서 공통으로 들어 있는 종목들을 찾는다. 그 종목들 중에 kosdaq_profit_6에 있는 종목들의 17년 1월 1일부터 18년 1월 1일까지 종목들을 불러온다. 아래는 최하위 종목들만 불러온다. 만약 최상위로 바꾸고 싶다면 간단히 "order by 분기 " 바로 뒤에 desc를 넣으면 된다. desc가 없으면 오름차순으로 불러오고 desc가 있다면 내림차순으로 데이터를 불러오기 때문이다.

select kos.Code, kos.Date, kos.Profit from 
(select Code, Date, Profit from kosdaq_profit_6 where Date>'2017-01-01' and Date<'2018-01-01') 
as kos join 
(select tmv.Code from (select Code from tmvalue where 17_1Q != 0 order by 17_1Q limit 0 , 115)
 as tmv join 
(select Code  from pbr where 16_3Q >=0.2 order by 16_3Q limit 0 , 112) as pb on tmv.Code = pb.Code) 
as res on kos.Code=res.Code;


다른 sql 문들도 위와 마찬가지이다. 아래 sql 생성 함수를 보자. 생성함수의 로직이 비슷해서 모든 메서드는 위에서 참조하길 바란다. 아래는 위 sql문을 생성하는 메서드이다. req_stock_index_count로 특정 지표의 종목 수를 불러온다. 결과를 divide로 나눠 n개의 종목을 선택하게 한다. 그리고  해당 값으로 sql문을 생성한다. 

    async def __get_tmv_and_pbr_sql(self,market, quarter):
        tmv_count = await self.req_stock_index_count(self.__get_tmv_count_sql(market, quarter))
        pbr_count = await self.req_stock_index_count(self.__get_pbr_count_sql(market, quarter))

        tmv_offset = str(int(tmv_count/self.divide))
        pbr_offset = str(int(pbr_count/self.divide))

        sql = "(select tmv.Code from (select Code from tmvalue where " + quarter["_1qt"] + " != 0 order by " \
              + quarter["_1qt"] + " limit 0 , " + tmv_offset + ")" \
              " as tmv join (select Code  from pbr where " + quarter["_3qt"] + " >=0.2 order by " \
              + quarter["_3qt"] + " limit 0 , " + pbr_offset + ") " \
              "as pb on tmv.Code = pb.Code) as res on kos.Code=res.Code;"
        return sql


마지막으로 볼 함수는 코스피와 코스닥 지수 종가 데이터를 불러온다. 이는 'buy and hold' 수익율을 구하기 위해 사용한다. 

    async def req_kospi_kosdaq_data(self, start, end):
        log.info("Selecting kospi/kosdaq data")

        kospi_sql = "select Date, Close from kospi where Date<\'"+end+"\' and Date >=\'"+start+"\';"
        kosdaq_sql = "select Date, Close from kosdaq where Date<\'"+end+"\' and Date >=\'"+start+"\';"

        try:
            async with self.__pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute(kospi_sql)
                    rows = await cur.fetchall()
                    kospi = pd.DataFrame.from_records(list(rows))
                    kospi.columns = ['Date', 'Close']
                    kospi = kospi.set_index('Date')


                    await cur.execute(kosdaq_sql)
                    rows = await cur.fetchall()
                    kosdaq = pd.DataFrame.from_records(list(rows))
                    kosdaq.columns = ['Date', 'Close']
                    kosdaq = kosdaq.set_index('Date')
        except aio.Error as e:
            log.warning("Selecting backtesting data Error : {}".format(repr(e)))
            raise

        return [kospi, kosdaq]


이것으로 DB 관련 코드에 대한 설명을 마쳤다. 다음 포스트에서는 DB 관련 코드를 이용해 실제 수익율을 계산해 볼 것이다. 수익율 계산 부분은 "각 지표 순위별 수익률 백테스팅 프로그래밍 "을 봤다고 전제하에 설명할 것이다. 해당 포스트를 이해하면 다음 포스트에서 볼 내용은 바로 이해할 수 있기 때문이다. 참고로 다음 포스트 내용이 더 쉽다. 




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

<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