ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MSCI 편입/편출 종목 매매는 효과적일까?
    재테크 2021. 8. 14. 00:55

    2020년 이후로 뉴스나 유튜브에서 심심찮게 MSCI 편입/편출 예상과 이에 대한 매매 기법에 대한 소개가 보인다. MSCI Global Standard는 모건 스탠리에서 운용하는 거대한 자금이 추종하는 지수로 알고 있다. 따라서 이 지수에 편입되면 보유 비중을 위해서 패시브 자금이 들어오는 것을 이용한 매매 기법이라는 뜻이다. 패시브 자금과 더불어 패시브 자금이 들어온다는 기대와 함께 추가적인 매수세를 기대할 수 있어서 이론상 괜찮다고 생각했다. 그렇다면 이 매매 기법이 KOSPI의 수익률을 초과할 것인지를 검증하기 위하여 지난 글에서 사용한 시뮬레이션을 통하여 검증해보았다.

    데이터 수집

    데이터를 수집할 수 있는 공간이 따로 보이지 않아서 구글링을 통해서 2018년부터 2020년까지 최대한 모을 수 있을 만큼 주식 종목을 모았다. Small cap 지수에 대한 부분은 더 찾기가 어려워서 standard 지수의 편입/편출 종목만 찾았고 정리하면 아래와 같다. (신라젠과 DGB금융지주는 gspread 데이터가 없어서 제외시켰다.

    이보다 많은 데이터를 얻을 수 있는 방법을 알거나 가지고 있는 분이 댓글을 달아주시면 감사하겠습니다.

     

    발표 날짜 편입 종목 편출 종목
    2018/05/15 펄어비스, 헬릭스미스, 셀트리온제약, 에이치엘비, 삼성엔지니어링 현대위아, 한화에어로스페이스, SK네트웍스
    2019/05/14 메리츠종금증권 KT
    2019/11/08 케이엠더블유 한미사이언스, 셀트리온제약
    2020/05/13 더존비즈온, 셀트리온제약 OCI, 메디톡스, KCC, 한화생명, 현대산업개발
    2020/08/13 씨젠, 알테오젠, 신풍제약 헬릭스미스, 현대백화점, 대우건설
    2020/11/11 SK바이오팜, SK케미칼, 두산중공업 포스코인터네셔널, 아모레퍼시픽
    2021/05/12 HMM, SKC, 하이브, 녹십자 현대해상, 한화, 롯데지주, GS리테일, 삼성카드, 한국가스공사, 오뚜기

    백테스팅 방법

    먼저, 나는 발표하자마자 매매하는 상황은 고려하지 않기 위하여 발표날 종가에 매수한다고 가정하고 하루부터 30 거래일까지 보유하고 있을 때 편입 종목, 편출 종목들의 수익률의 평균을 계산하였다. 또한 단순히 양의 수익률을 내는 것이 중요한 것이 아니라 코스피 지수의 수익률을 상회하는 것이 중요하기 때문에 비교군에 담았다.

    결과

    보유기간 [01일 기준] - 편입 수익률: -2.08%, 편출 수익률: 0.02%, KOSPI 평균 수익률: -0.29%
    보유기간 [02일 기준] - 편입 수익률: -0.72%, 편출 수익률: -0.65%, KOSPI 평균 수익률: -0.61%
    보유기간 [03일 기준] - 편입 수익률: 0.11%, 편출 수익률: -0.25%, KOSPI 평균 수익률: -0.00%
    보유기간 [04일 기준] - 편입 수익률: 0.48%, 편출 수익률: -0.53%, KOSPI 평균 수익률: 0.19%
    보유기간 [05일 기준] - 편입 수익률: 0.43%, 편출 수익률: -0.76%, KOSPI 평균 수익률: 0.12%
    보유기간 [06일 기준] - 편입 수익률: -0.08%, 편출 수익률: 0.09%, KOSPI 평균 수익률: 0.48%
    보유기간 [07일 기준] - 편입 수익률: 1.33%, 편출 수익률: 0.14%, KOSPI 평균 수익률: 1.13%
    보유기간 [08일 기준] - 편입 수익률: 0.99%, 편출 수익률: 0.81%, KOSPI 평균 수익률: 1.50%
    보유기간 [09일 기준] - 편입 수익률: 2.60%, 편출 수익률: 1.73%, KOSPI 평균 수익률: 1.66%
    보유기간 [10일 기준] - 편입 수익률: 3.92%, 편출 수익률: 0.94%, KOSPI 평균 수익률: 1.63%
    보유기간 [11일 기준] - 편입 수익률: 4.32%, 편출 수익률: 1.45%, KOSPI 평균 수익률: 1.93%
    보유기간 [12일 기준] - 편입 수익률: 5.43%, 편출 수익률: 1.49%, KOSPI 평균 수익률: 2.05%
    보유기간 [13일 기준] - 편입 수익률: 5.28%, 편출 수익률: 1.26%, KOSPI 평균 수익률: 2.65%
    보유기간 [14일 기준] - 편입 수익률: 4.00%, 편출 수익률: 1.35%, KOSPI 평균 수익률: 3.01%
    보유기간 [15일 기준] - 편입 수익률: 5.87%, 편출 수익률: 0.72%, KOSPI 평균 수익률: 3.22%
    보유기간 [16일 기준] - 편입 수익률: 10.18%, 편출 수익률: -1.14%, KOSPI 평균 수익률: 3.27%
    보유기간 [17일 기준] - 편입 수익률: 10.23%, 편출 수익률: -0.92%, KOSPI 평균 수익률: 3.36%
    보유기간 [18일 기준] - 편입 수익률: 10.10%, 편출 수익률: -0.95%, KOSPI 평균 수익률: 3.88%
    보유기간 [19일 기준] - 편입 수익률: 10.62%, 편출 수익률: -0.75%, KOSPI 평균 수익률: 4.04%
    보유기간 [20일 기준] - 편입 수익률: 10.95%, 편출 수익률: -0.39%, KOSPI 평균 수익률: 4.45%
    보유기간 [21일 기준] - 편입 수익률: 9.86%, 편출 수익률: 0.54%, KOSPI 평균 수익률: 5.65%
    보유기간 [22일 기준] - 편입 수익률: 8.92%, 편출 수익률: 0.82%, KOSPI 평균 수익률: 6.62%
    보유기간 [23일 기준] - 편입 수익률: 9.98%, 편출 수익률: 0.91%, KOSPI 평균 수익률: 7.53%
    보유기간 [24일 기준] - 편입 수익률: 9.11%, 편출 수익률: 1.27%, KOSPI 평균 수익률: 8.03%
    보유기간 [25일 기준] - 편입 수익률: 8.40%, 편출 수익률: 1.33%, KOSPI 평균 수익률: 8.10%
    보유기간 [26일 기준] - 편입 수익률: 7.48%, 편출 수익률: 1.27%, KOSPI 평균 수익률: 8.00%
    보유기간 [27일 기준] - 편입 수익률: 8.00%, 편출 수익률: 1.33%, KOSPI 평균 수익률: 7.82%
    보유기간 [28일 기준] - 편입 수익률: 9.43%, 편출 수익률: 3.68%, KOSPI 평균 수익률: 8.67%
    보유기간 [29일 기준] - 편입 수익률: 10.66%, 편출 수익률: 1.97%, KOSPI 평균 수익률: 8.56%

    먼저, 데이터의 양이 매우 적은 백테스트였기 때문에 신뢰성이 많이 떨어지는 결과이다. 그럼에도 불구하고 정리를 해보자면 16일에서 20일 사이로 보유한다고 가정했을 때 코스피에 대비 매우 높은 평균 수익률을 나타냈다. 평균적으로 6~7%의 초과수익률을 냈는데 이 매매기법은 한 번쯤 시도할만한 가치가 있음을 보여준다.

    반면에, 편출 종목의 평균 수익률은 역시 KOSPI에 비해 처참한 모습을 확인 할 수 있다. 그럼에도 불구하고 KOSPI의 평균 수익률과 비교했을 때 장기 보유할수록 차이가 벌어지는 것을 확인할 수 있다. 즉, 보유하고 있는 종목이 지수에서 편출 된다고 발표가 나면 최대한 빠르게 정리하는 것이 좋아 보인다.

     

    구현

    Python 3.6을 기반으로 하였으며 내장 패키지 외에 gspread 패키지를 사용하여 google sheet를 통해 데이터를 수집하였다.

    코스피와 코스닥 중 어떤 종목인지를 리포트에 나온 종목명만으로는 알 수가 없어서 먼저 코스피 기준(KRX)으로 검색해보고 결과 값이 제대로 안 돌아오면 코스닥 기준(KOSDAQ)으로 다시 검색해서 사용하였다.

    import csv
    import time
    import json
    import gspread
    import datetime
    import os
    
    from statistics import mean
    from pathlib import Path
    
    
    # csv 파일을 통하여 gspread를 통해 주가 데이터를 저장
    # 신라젠과 DGB금융지주는 데이터가 제대로 안되어있어서 제외
    def save_stock_records(sh):
        with open('msci.csv', 'r') as f:
            lines = csv.reader(f)
            next(lines)
            keys = set()
            for line in lines:
                name, ticker = line[3:5]
                keys.add((name, ticker))
    
            for name, ticker in keys:
                save_price_data(sh, ticker, name, 2018, 5, 1)
    
            save_price_data(sh, 'KOSPI', 'KOSPI', 2019, 1, 1)
    
    
    # Ticker 정보를 통하여 특정 기간동안의 날짜 데이터를 가져와서 json 파일로 저장
    def save_price_data(sh, ticker, name, year, month, day):
        print(f'Save data: {name}: {ticker}')
        time.sleep(1)
        sh.clear()
        # KOSPI 데이터 로드
        values = load_price_values(sh, ticker, year, month, day)
        # KOSPI에 없는 종목은 KOSDAQ에서 로드
        if len(values) < 2:
            values = load_price_values(sh, ticker, year, month, day, platform='KOSDAQ')
        ret = {}
        for value in values[1:]:
            year, month, day, _, __ = value[0].split(' ')
            price = value[1]
            ret[f'{year[:-1]}/{month[:-1]}/{day}'] = price
    
        with open(f'data/{ticker}_{name}.json', 'w') as df:
            json.dump(ret, df)
    
    
    # googlefinance 함수를 통하여 주가 데이터를 시작 날짜를 기준으로 400일간 받아서 값을 가져옴 
    def load_price_values(sh, ticker, year, month, day, platform='KRX'):
        start_date = datetime.datetime(year, month, day)
        end_date = start_date + datetime.timedelta(days=1150)
        query = (
            f'=googlefinance("{platform}:{ticker}", "price", "{year}. {month}. {day}", '
            f'"{end_date.year}. {end_date.month}. {end_date.day}", "DAILY")'
        )
        time.sleep(1)
        sh.update_cell(1, 1, query)
        time.sleep(1)
        return sh.get_all_values()
    
    
    # json 파일로 저장된 주가 데이터를 로드
    def load_stocks():
        ret = dict()
        for path in os.listdir('./data'):
            ticker, name = os.path.splitext(path)[0].split('_')
            with open(f'./data/{path}', 'r') as f:
                data = json.load(f)
                ret[(ticker, name)] = data
        return ret
    
    
    # 주가 데이터의 특정 종목에 대하여 특정 날짜의 가격과
    # 그 날로 부터 특정 보유기간 (offset_days) 후의 가격을 가져와서 수익률을 계산
    def obtain_return_rate(stocks, ticker, name, year, month, day, offset_days):
        start_price = None
        end_price = None
        start_date_key = ''
        end_date_key = ''
        offset = 0
        stock = stocks[(ticker, name)]
        while start_price is None:
            delta = datetime.timedelta(days=offset)
            date = datetime.datetime(year, month, day) + delta
            date_key = f'{date.year}/{date.month}/{date.day}'
            if date_key in stock:
                start_date_key = date_key
                start_price = float(stock[date_key])
            else:
                offset += 1
    
        offset = offset + offset_days
        while end_price is None:
            delta = datetime.timedelta(days=offset)
            date = datetime.datetime(year, month, day) + delta
            date_key = f'{date.year}/{date.month}/{date.day}'
            if date_key in stock:
                end_date_key = date_key
                end_price = float(stock[date_key])
            else:
                offset += 1
    
        return (end_price - start_price) / start_price
    
    
    # 특정 보유기간 동안 추천 종목을 보유했을 때 예상 수익률과 같은 기간 동안의 코스피 지수 수익률을 계산
    def simulate(stocks, offset):
        add_rates = []
        remove_rates = []
        refer_rates = []
        with open('msci.csv', 'r') as f:
            lines = csv.reader(f)
            next(lines)
            for line in lines:
                year, month, day, name, ticker, add = line
                month = int(month)
                day = int(day)
                year = int(year)
                add = add == '1'
                try:
                    return_rate = obtain_return_rate(stocks, ticker, name, year, month, day, offset)
                    refer_rate = obtain_return_rate(stocks, 'KOSPI', 'KOSPI', year, month, day, offset)
                    if add:
                        add_rates.append(return_rate)
                    else:
                        remove_rates.append(return_rate)
                    refer_rates.append(refer_rate)
                except Exception as e:
                    print(e)
                    print(f'오류 발생: 2019-{month}-{day}, {ticker}, {name}')
    
        print(f'보유기간 [{offset:0>2}일 기준] - '
              f'편입 수익률: {mean(add_rates) * 100:.2f}%, '
              f'편출 수익률: {mean(remove_rates) * 100:.2f}%, '
              f'KOSPI 평균 수익률: {mean(refer_rates) * 100:.2f}%')
    
    
    if __name__ == '__main__':
        gc = gspread.service_account(filename='./service_account.json')
        sh = gc.open('gspread-test').sheet1
        Path('./data').mkdir(exist_ok=True)
        save_stock_records(sh)
        stocks = load_stocks()
        for offset in range(1, 30):
            simulate(stocks, offset)

    댓글

Designed by Tistory.