본문 바로가기
python/금융데이터분석

python 증권데이터 분석 - 듀얼 모멘텀 투자

by orangecode 2023. 1. 10.
728x90

 

모멘텀 Momentum
직역 : 물체를 움직이는 힘을 의미

주식시장 : 움직이기 시작한 주식 가격이 계속 같은 방향으로 나아가려는 성질

 

모멘텀 현상

모멘텀 현상은 행동재무학에서 군집 행동, 정박효과, 확중 편향, 처분 효과 등 행동편향(action bias)에 의해 발생한다.

 

주가 움직임이 미래에도 이어질 것이라 믿는 확증편향을 가진 투자자들이 최근 상승주에 더 투자하기에, 모멘텀이 이어지는 현상을 말할 수 있다.

 

확증 편향으로 인해 과소평가, 과대평가가 생겨나며 비효율적인 가격을 형성해 투자자들의 급진적인 행동을 가져온다.

 

-  : 

확증 편향(action bias)
군집행동(herding) 다수 그룹의 행동을 따라하라는 경향
정박 효과(anchoring) 정보를 처음 제공받은 시점에 지나치게 의존하는 경향
확증편향(confirmation bias) 본인의 믿음과 반대되는 정보를 무시하는 경향
처분효과(disposition effect) 수익이 난 주식을 금방 팔고, 손해본 주식을 계속 보유하는 경향

 

현대의 모멘텀

현대의 모멘텀momentumgth)은 알프레드 카울스, 허버트 존스가 1937년 최초로 체계화했다.

 

알프레드 카울스와 허버트 존스는 전년도에 수익률이 가장 높았던 종목들이 이듬해에도 수익률이 높은 경향을 보임을 발견했다. 

 

로버트 레비Robert Levy가 컴퓨터를 이용한 모멘텀을 연구했고, 상대강도(Relative Strength) 용어를 만들었다.

 

상대강도란 26주 이동평균선을 기준으로 높은 종목을 상대강도가 높다, 26주 이동평균선 기준 낮은 종목을 상대강도가 낮다고 판단한다.

 

1993년 모멘텀의 연구가 나라심한 제가디시와 쉐리탄 티드먼에 의해 엄청나게 성장한다.

 

 

모멘텀에 대한 많은 연구덕에 모멘텀 투자 전략이 모든 자산 유형에 유효한데, 효율적 시장 가설 창시자가 유진 파마가 2008년에 "모멘텀은 제1의 시장 이례 현상이다"라고 인정했다고 한다.

 

 

듀얼 모멘텀 투자 - 클래스 구조 stub코드

 

 

듀얼 모멘텀 투자(Dual Momentum Investing)은 게리 안토나치가 개발한 매매기법으로 상대강도가 센 주식 종목들에 투자하는 상대적 모멘텀 전략과 과거 6~12개월의 수익이 단기 국채 수익률을 능가하는 강세장에서 투자하는 절대적 모멘텀 전략을 하나로 합친 전략이다.

 

- 상대적 모멘텀 전략 : 상대강도가 센 주식 종목들에 투자

- 절대적 모멘텀 전략 : 과거 6~12개월의 수익이 단기 국채 수익률을 능가하는 강세장에서 투자

 

절대 모멘텀 전략은 상승장에서 투자하고 하락장은 관망하는 전략이지만, 상대적 모멘텀과 함께 사용하여 상대 모멘텀과 사용했을 때보다 MDD(고점대비 최대손실)를 줄이고 더 높은 수익률 달성이 가능하다.

 

 

듀얼 모멘트 전략의 스텁코드

듀얼 모멘텀은 상대적 모멘텀을 구한뒤, 절대적 모멘텀을 호출할 때 인수로 상대 모멘텀을 넣어주게 된다.

 

# 듀얼모멘텀 전략 구조
dm = DualMomentum()
상대모멘텀 = dm.get_rltv_momentum(시작일, 종료일, 종목수)
절대모멘텀 = dm.get_abs.momentum(상대 모멘텀, 시작일, 종료일)

 

import pandas as pd
import pymysql
from datetime import datetime
from datetime import timedelta
import sys
sys.path.append(r'C:\Users\kwonk\Downloads\개인 프로젝트\juno1412-1\증권데이터분석\DB_API')
import Analyzer

class DualMomentum:
    def __init__(self):
        """생성자: KRX 종목코드(codes)를 구하기 위한 MarkgetDB 객체 생성"""
        self.mk = Analyzer.MarketDB()
    
    def get_rltv_momentum(self, start_date, end_date, stock_count):
        """특정 기간 동안 수익률이 제일 높았던 stock_count 개의 종목들 (상대 모멘텀)
            - start_date  : 상대 모멘텀을 구할 시작일자 ('2020-01-01')   
            - end_date    : 상대 모멘텀을 구할 종료일자 ('2020-12-31')
            - stock_count : 상대 모멘텀을 구할 종목수
        """       

    def get_abs_momentum(self, rltv_momentum, start_date, end_date):
        """특정 기간 동안 상대 모멘텀에 투자했을 때의 평균 수익률 (절대 모멘텀)
            - rltv_momentum : get_rltv_momentum() 함수의 리턴값 (상대 모멘텀)
            - start_date    : 절대 모멘텀을 구할 매수일 ('2020-01-01')   
            - end_date      : 절대 모멘텀을 구할 매도일 ('2020-12-31')
        """

 

 

듀얼 모멘텀 1 - 상대적 모멘텀 구하기

상대적 모멘텀(Relative Momentum)이란 특정 기간동안 상대적으로  수익률이 좋았던 n개의 종목을 구하는 방법이다.

사용자의 시작일, 종료일을 입력받아 상대적 모멘텀을 구할 수 있다.

    def get_rltv_momentum(self, start_date, end_date, stock_count):
        """특정 기간 동안 수익률이 제일 높았던 stock_count 개의 종목들 (상대 모멘텀)
            - start_date  : 상대 모멘텀을 구할 시작일자 ('2020-01-01')   
            - end_date    : 상대 모멘텀을 구할 종료일자 ('2020-12-31')
            - stock_count : 상대 모멘텀을 구할 종목수
        """       

        # 1. daily_price 테이블에서 사용자가 입력한 일자와 같거나 작은 일자를조회해 실제 거래일을 구한다.
        connection = pymysql.connect(host='localhost', port=3307, db='INVESTAR', user='root', passwd='mariadb', autocommit=True)
        cursor = connection.cursor()

        sql = f"select max(date) from daily_price where date <= '{start_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("start_date : {} -> returned None".format(sql))
            return
        # 2. DB에서 조회된 거래일을 %Y-%m-%d 포맷 문자열로 변환해 사용자가 입력한 조회 시작일자 변수에 반영
        start_date = result[0].strftime('%Y-%m-%d')


        sql = f"select max(date) from daily_price where date <= '{end_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("end_date : {} -> returned None".format(sql))
            return
        end_date = result[0].strftime('%Y-%m-%d')

        # 3. 종목별 수익률 계산
        # 상대 모멘텀은 종목별 수익률을 구하는 것
        rows = []
        columns = ['code', 'company', 'old_price', 'new_price', 'returns']
        for _, code in enumerate(self.mk.codes):
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{start_date}"
            
            cursor.execute(sql)
            result = cursor.fetchone()
            
            if (result is None):
                continue
            old_price = int(result[0])
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{end_date}"
            cursor.execute(sql)
            result = cursor.fetchone()
            if (result is None):
                continue

            new_price = int(result[0])
            returns = (new_price / old_price - 1) * 100
            rows.append([code, self.mk.close[code], old_price, new_price, returns])


            # 상대 모멘텀 데이터 프레임 생성
            # 2차원 리스트에 저장한 종목별 수익률을 데이터프레임으로 변환해 수익률이 높은 순서로 출력한다
            df = df.DataFrame(rows, columns=columns)
            df = df[['code', 'company', 'old_price', 'new_price', 'returns']]
            df = df.sort_values(by='returns', ascending = False)
            df = df.head(stock_count)
            df.index = pd.Index(range(stock_count))

            connection.close()
            print(df)
            print(f"\nRelative Momentun ({start_date} ~ {end_date}) :"\
                f"{df['returns'].mean():.2f}% \n")
            
            return df

 

 

듀얼 모멘텀 2 - 절대적 모멘텀 구하기

 

 

 

 

한국형 듀얼 모멘텀 전략

 

듀얼 모멘텀 전략 전체 코드
import pandas as pd
import pymysql
from datetime import datetime
from datetime import timedelta
import sys
sys.path.append(r'C:\Users\kwonk\Downloads\개인 프로젝트\juno1412-1\증권데이터분석\DB_API')
import Analyzer 

class DualMomentum:
    def __init__(self):
        """생성자: KRX 종목코드(codes)를 구하기 위한 MarkgetDB 객체 생성"""
        self.mk = Analyzer.MarketDB()
    
    def get_rltv_momentum(self, start_date, end_date, stock_count):
        """특정 기간 동안 수익률이 제일 높았던 stock_count 개의 종목들 (상대 모멘텀)
            - start_date  : 상대 모멘텀을 구할 시작일자 ('2020-01-01')   
            - end_date    : 상대 모멘텀을 구할 종료일자 ('2020-12-31')
            - stock_count : 상대 모멘텀을 구할 종목수
        """       

        # 1. daily_price 테이블에서 사용자가 입력한 일자와 같거나 작은 일자를조회해 실제 거래일을 구한다.
        connection = pymysql.connect(host='localhost', port=3307, db='INVESTAR', user='root', passwd='mariadb', autocommit=True)
        cursor = connection.cursor()

        sql = f"select max(date) from daily_price where date <= '{start_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("start_date : {} -> returned None".format(sql))
            return
        # 2. DB에서 조회된 거래일을 %Y-%m-%d 포맷 문자열로 변환해 사용자가 입력한 조회 시작일자 변수에 반영
        start_date = result[0].strftime('%Y-%m-%d')


        sql = f"select max(date) from daily_price where date <= '{end_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("end_date : {} -> returned None".format(sql))
            return
        end_date = result[0].strftime('%Y-%m-%d')

        # 3. 종목별 수익률 계산
        # 상대 모멘텀은 종목별 수익률을 구하는 것
        rows = []
        columns = ['code', 'company', 'old_price', 'new_price', 'returns']
        for _, code in enumerate(self.mk.codes):
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{start_date}"
            
            cursor.execute(sql)
            result = cursor.fetchone()
            
            if (result is None):
                continue
            old_price = int(result[0])
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{end_date}"
            cursor.execute(sql)
            result = cursor.fetchone()
            if (result is None):
                continue

            new_price = int(result[0])
            returns = (new_price / old_price - 1) * 100
            rows.append([code, self.mk.close[code], old_price, new_price, returns])


            # 상대 모멘텀 데이터 프레임 생성
            # 2차원 리스트에 저장한 종목별 수익률을 데이터프레임으로 변환해 수익률이 높은 순서로 출력한다
            df = df.DataFrame(rows, columns=columns)
            df = df[['code', 'company', 'old_price', 'new_price', 'returns']]
            df = df.sort_values(by='returns', ascending = False)
            df = df.head(stock_count)
            df.index = pd.Index(range(stock_count))

            connection.close()
            print(df)
            print(f"\nRelative Momentun ({start_date} ~ {end_date}) :"\
                f"{df['returns'].mean():.2f}% \n")
            
            return df 
        


    def get_abs_momentum(self, rltv_momentum, start_date, end_date):
        """특정 기간 동안 상대 모멘텀에 투자했을 때의 평균 수익률 (절대 모멘텀)
            - rltv_momentum : get_rltv_momentum() 함수의 리턴값 (상대 모멘텀)
            - start_date    : 절대 모멘텀을 구할 매수일 ('2020-01-01')   
            - end_date      : 절대 모멘텀을 구할 매도일 ('2020-12-31')
        """
        stockList = list(rltv_momentum('code'))
        connection = pymysql.connect(host='localhost', port=3307, db='INVESTAR', user='root', passwd='mariadb', autocommit=True)
        cursor = connection.cursor()

        sql = f"select max(date) from daily_price where date <= '{start_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("start_date : {} -> returned None".format(sql))
            return
        # 2. DB에서 조회된 거래일을 %Y-%m-%d 포맷 문자열로 변환해 사용자가 입력한 조회 시작일자 변수에 반영
        start_date = result[0].strftime('%Y-%m-%d')


        sql = f"select max(date) from daily_price where date <= '{end_date}'"
        cursor.execute(sql)
        result = cursor.fetchone()

        if (result[0] is None):
            print("end_date : {} -> returned None".format(sql))
            return
        end_date = result[0].strftime('%Y-%m-%d')

        # 3. 종목별 수익률 계산
        # 절대 모멘텀은 종목별 수익률을 구하는 것
        rows = []
        columns = ['code', 'company', 'old_price', 'new_price', 'returns']
        for _, code in enumerate(stockList):
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{start_date}"
            
            cursor.execute(sql)
            result = cursor.fetchone()
            
            if (result is None):
                continue
            old_price = int(result[0])
            sql = f"select close from daily_price"\
                f"where code='{code}' and date'{end_date}"
            cursor.execute(sql)
            result = cursor.fetchone()
            if (result is None):
                continue

            new_price = int(result[0])
            returns = (new_price / old_price - 1) * 100
            rows.append([code, self.mk.close[code], old_price, new_price, returns])


            # 절대 모멘텀 데이터 프레임 생성
            # 2차원 리스트에 저장한 종목별 수익률을 데이터프레임으로 변환해 수익률이 높은 순서로 출력한다
            df = df.DataFrame(rows, columns=columns)
            df = df[['code', 'company', 'old_price', 'new_price', 'returns']]
            df = df.sort_values(by='returns', ascending = False)

            connection.close()
            print(df)
            print(f"\nAbsolute Momentun ({start_date} ~ {end_date}) :"\
                f"{df['returns'].mean():.2f}% \n")
            
            return

 

한국형 듀얼 모멘텀 전략

"듀얼 모멘텀 투자 전략" 한국어판 강환국 역자의 말에 따르면, 게리 안토나치는 12개월 듀얼 모멘텀 전략을 사용했다.

 

하지만 21세기 한국 시장에서는 3개월 전략이 수익이 좋다고 한다.

 

따라서 한국 자산만으로 운영하는 전략일 경우 3개월 듀얼 모멘텀을 적용하고, 한국자산과 해외자산을 혼합하는 경우에는 12개월 듀얼 모멘텀을 적용하는 한국형 전략도 고려해볼만 하다고 한다.

 

 

반응형

댓글