자산배분

TQQQ, TMF 평균모멘텀 전략의 비중 계산 Algo 클래스

unius 2023. 1. 19. 14:27
728x90
반응형

6편과 7편에서 평균모멘텀 전략과 해당 전략의 수익곡선 모멘텀까지 적용한 전략을 살펴보고 백테스트 결과를 공유하였다. 결과를 공유하며 평균모멘텀 전략에서 해당 전략의 모멘텀을 산출하여 각 자산의 비중을 산출하는 Algo클래스를 상속받은 WeighAMS 클래스의 설명을 생략하였는데 오늘은 WeighAMS() 만 다루도록 하겠다.

 

WeighAMS 클래스는 투자자산의 12개월 평균모멘텀을 산출하여 비중을 할당하는 기본 기능과 수익률 곡선이 있는 경우 수익곡선 모멘텀을 산출하여 안전자산의 최소비중을 우선 할당한 후에 투자자산의 평균모멘텀에 따라 각 자산의 투자비중을 계산하는 기능을 포함하고 있다.

 

생성자를 먼저 살펴보자.

def __init__(self, lag=1, cash_weight=0.0, returns=pd.DataFrame(), ylookback=12):
        if (not returns.empty) & (ylookback < 1):
            raise ValueError("lookback of Yield can't be less than 0")
        
        super(WeighAMS, self).__init__()
        self.lag = lag
        self.cash_weight = cash_weight
        self.returns = returns
        self.ylookback = ylookback

첫번째 파라미터인 lag는 지연일을 의미하며 리밸런싱 하는 당일에 모멘텀 판단을 위한 반추기간의 마지막 날짜의 기준이다. 즉, 지연일의 기본값인 '1' 이 의미하는 바는 리밸런싱 당일에 모멘텀을 판단하기 위한 반추기간의 날짜를 하루전까지의 데이터를 사용한다는 의미이다. 두번째 파라미터인 cash_weight은 안전자산의 최소 비중을 의미한다. 기본값인 '0.0' 은 투자자산의 초기 비중을 50:50으로 한다는 의미가 된다. 총 투자금의 10%를 안전자산으로 유지하고자 한다면 '0.1' 로 설정하면 된다. 세번째 파라미터는 수익곡선이며 네번째 파라미터는 수익곡선의 모멘텀을 산출할 때의 반추기간을 의미한다.

 

다음은 평균모멘텀 스코어를 계산하는 정적 메소드이다.

    @staticmethod
    def avg_momentum_score(prc_monthly, lookback):
        sumOfmomentum = 0
        sumOfscore = 0
        for i in range(1, lookback+1):
            sumOfmomentum = prc_monthly / prc_monthly.shift(i) + sumOfmomentum
            sumOfscore = np.where(prc_monthly / prc_monthly.shift(i) > 1, 1,0) + sumOfscore
    
        sumOfmomentum[sumOfmomentum > 0] = sumOfscore/lookback
        return sumOfmomentum

월단위로 리샘플링된 가격 데이터와 반추기간을 받아서 평균모멘텀을 산출하여 반환한다. 평균모멘텀에 대한 글은 아래 링크를 참조하면 된다.

다음은 수익률 곡선의 n개월(lookback) 평균모멘텀을 계산하는 정적 메소드이다.

    @staticmethod
    def amsofYield(returns, lag, lookback, end, target):
        if (not returns.empty):
            start = end - pd.DateOffset(months=lookback)
            while not (start in target.universe.index):
                start = start - pd.DateOffset(days=lag)
            prc = returns.loc[start:end]
            prc_monthly = prc.copy()
            prc_monthly = prc_monthly.resample('BM').last().dropna()
            ymomscore = WeighAMS.avg_momentum_score(prc_monthly, lookback)
            yweight = ymomscore.loc[ymomscore.index.max()]
        return yweight

수익률 데이터를 월간 단위로 리샘플링한 후에 평균모멘텀을 계산하기 위해서 상기 평균모멘텀 계산 정적 메소드를 호출하도록 되어 있다.

마지막으로 안전자산, 투자자산의 최종 투자 비중을 산출하는 메소드이다.

    def __call__(self, target):
        selected = target.temp['selected']

        end = target.now - pd.DateOffset(days=self.lag)
        while not (end in target.universe.index):
            end = end - pd.DateOffset(days=self.lag)

        s0 = target.now
        if (not self.returns.empty):
            s0 = self.returns.index.min() + pd.DateOffset(months=self.ylookback)
        # 1. 전략수익률 데이터 추출 & 모멘텀 가공
        if (not self.returns.empty) & (target.now >= s0):
            start = end - pd.DateOffset(months=self.ylookback)
            while not (start in target.universe.index):
                start = start - pd.DateOffset(days=self.lag)
            prc = self.returns.loc[start:end]
            prc_monthly = prc.copy()
            prc_monthly = prc_monthly.resample('BM').last().dropna()
            ymomscore = WeighAMS.avg_momentum_score(prc_monthly, self.ylookback)
            # ① 투자자산 전체 비중 산출
            yweight = ymomscore.loc[ymomscore.index.max()]
        
        # 2. 투자자산 반추기간 데이터 추출, 모멘텀 산출
        start = end - pd.DateOffset(months=12)
        while not (start in target.universe.index):
            start = start - pd.DateOffset(days=self.lag)
        prc = target.universe.loc[start:end, selected]

        prc_monthly = prc.copy().drop(['cash'], axis=1)
        prc_monthly = prc_monthly.resample('BM').last().dropna()
        # ② 투자자산의 각 자산별 12개월 평균모멘텀 산출
        weights = WeighAMS.avg_momentum_score(prc_monthly, lookback=12)
        
        # 3. 투자자산 및 안전자산 비중 결정
        weights = weights.loc[weights.index.max()]
        if (not self.returns.empty) & (target.now >= s0):
            # ④ 투자자산의 각 자산별 투자비중 산출
            weights = weights * yweight.iloc[0] / len(weights.index)
        else:
            weights = weights * (1-self.cash_weight) / len(weights.index)
        # ⑤ 안전자산 비중 산출
        weights['cash'] = 1 - weights.sum()
        target.temp['weights'] = weights
        return True

코드가 약간 길어보이지만 3개의 부분으로 구성되어 있다. 첫번째 부분은 수익률 데이터로 n개월 평균모멘텀을 산출하여 총 투자금에서 투자자산에 할당될 비중(①)을 산출한다. 그리고 두번째 부분에서 투자자산의 12개월 평균모멘텀(②)을 산출하고 마지막 부분에서 투자자산에 할당될 비중(①)을 투자자산의 개수로 나누어 각 투자자산의 고정비중(③)을 계산하고 여기에 각 자산의 12개월 평균모멘텀(②)을 곱하여 각 투자자산의 투자비중(④)을 결정하고 남은 비중의 투자금은 안전자산에 할당(⑤)하여 반환하도록 구현되어 있다.

 

1편 : TQQQ, TMF 고정 비중 백테스트
2편 : TQQQ, TMF 상대 모멘텀 백테스트
3편 : TQQQ, TMF 절대 모멘텀 백테스트(1)
4편 : TQQQ, TMF 절대 모멘텀 백테스트(2)
5편: TQQQ,TMF 듀얼 모멘텀 백테스트
6편: TQQQ, TMF 평균 모멘텀 스코어 백테스트
7편: TQQQ, TMF 수익곡선 모멘텀 백테스트

8편: TQQQ, TMF 평균모멘텀 전략의 비중 계산 Algo 클래스

9편: TQQQ, TMF 가속듀얼모멘텀 백스트

 

☞ 이 글은 순수하게 개인적인 의도로 테스트한 결과이며 내용상 오류가 있을 수도 있습니다.

.

 

728x90
반응형