ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 영구 포트폴리오 직장인에게 최적화하기 (2)
    재테크 2021. 1. 16. 03:07

    이번 글은 지난 글에 이어서 직장인에 맞는 적금식 영구 포트폴리오의 운용과 조금이라도 나은 수익률을 위한 운용 전략을 시뮬레이션을 통하여 찾아보고 그 전략을 공유하는 글이다. 먼저 나는 매월 발생하는 투자금액을 균등하게 나누어서 무조건 자산을 구매하는 전략을 취했을 때 연평균 6.14%의 복리 이자의 효과를 거둘 수 있음을 확인했다. 이번에는 자산 가격의 변화에 따라 분할 매수/분할 매도를 했을 때 수익률이 어떻게 달라질지를 분석하였다.

     

    전략 2. 각 자산 가격의 변동성을 보고 분할 매수/매도 하기

    이 전략의 투자금액은 전략 1과 똑같이 매월 80만 원 기준으로 20만 원씩 분배한다. 그러나 각 자산별로 1) 수익 상황에 따라(수익/손실) 혹은 자산 가격의 변동성에 따라(가격의 상승/하락) 분할 매수/매도를 하여 이익을 극대화하는 전략이다. 이를 더 자세하게 상황별로 정리하면 아래와 같다. 

     

    수익을 내고 있을 때

    1. 자산 가격이 A% 이상 상승한 경우 보유 주식의 B%만큼 분할 매도 (수익 실현)
    2. 자산 가격이 C% 이상 하락한 경우 보유 주식의 D%만큼 분할 매도 (수익 실현)
    3. 그 외, 보유 예수금의 E%만큼 분할 매수

    손실을 보고 있을 때

    1. 자산 가격이 F% 이상 하락한 경우 보유 주식의 G%만큼 분할 매도 (손실 최소화)
    2. 그 외, 보유 예수금의 H%만큼 분할 매수

    전략을 단순화하기 위하여 가격의 변화는 최근 1달 동안의 변화만 참고하기로 하였고 각 A~H의 변수들에 대해서는 20000번의 Random Search 방식을 통하여 최고 수익금을 발생시키는 변수들을 선택하였다.

     

    백테스트 결과

    Figure 1. 전략 2로 투자했을 때 다른 전략들과의 수익금 비교

    백테스트 결과에 따르면 2005년부터 2020년 1월까지 매 월 80만 원씩 1억 5040만 원의 투자금액을 기준으로 전략 2번을 사용했을 때 2억 8190만 원으로 2억 4835만 원의 전략 1을 약 3355만 원 차이로 앞섰다. 이는 15년간 87.4%의 수익률로 이 전략의 수익률을 예금이자로 계산하면 7.63%로 계산된다. 또한 MDD 값도 2013년 12월 1일에 무려 6.3%로 전략 1의 10.9% 보다 훨씬 방어력이 높은 포트폴리오 운용 전략이 완성되었다. 물론 hyperparameter(A-H)를 과거 데이터에 과적합시킨 결과이기 때문에 완벽하게 신뢰를 하면 안 되지만 전략 1의 underfitting 함도 무시할 수 없기 때문에 일정 조건에 다다랐을 때 분할 매수/매도하는 전략이 더 좋은 수익을 낼 확률이 높다고 정리할 수 있다. 계산된 hyperparameter의 결과를 정리하면 아래와 같다.

     

    주식 운용 전략

    수익을 내고 있을 때

    1. 자산 가격이 10.8%이상 상승한 경우 보유 주식의 23.6%만큼 분할 매도 (수익 실현)
    2. 자산 가격이 8.3%이상 하락한 경우 보유 주식의 19.6%만큼 분할 매도 (수익 실현)
    3. 그 외, 보유 예수금의 76%만큼 분할 매수

    손실을 보고 있을 때

    1. 자산 가격이 4.9%이상 하락한 경우 보유 주식의 30.7%만큼 분할 매도 (손실 최소화)
    2. 그 외, 보유 예수금의 55.3%만큼 분할 매수

    채권 운용 전략

    수익을 내고 있을 때

    1. 자산 가격이 29.3%이상 상승한 경우 보유 주식의 48.4%만큼 분할 매도 (수익 실현)
    2. 자산 가격이 7.9%이상 하락한 경우 보유 주식의 19.8%만큼 분할 매도 (수익 실현)
    3. 그 외, 보유 예수금의 29.7%만큼 분할 매수

    손실을 보고 있을 때

    1. 자산 가격이 12.2%이상 하락한 경우 보유 주식의 58.8%만큼 분할 매도 (손실 최소화)
    2. 그 외, 보유 예수금의 17%만큼 분할 매수

    금 운용 전략

    수익을 내고 있을 때

    1. 자산 가격이 24%이상 상승한 경우 보유 주식의 36%만큼 분할 매도 (수익 실현)
    2. 자산 가격이 1%이상 하락한 경우 보유 주식의 63%만큼 분할 매도 (수익 실현)
    3. 그 외, 보유 예수금의 81.9%만큼 분할 매수

    손실을 보고 있을 때

    1. 자산 가격이 3%이상 하락한 경우 보유 주식의 20.2%만큼 분할 매도 (손실 최소화)
    2. 그 외, 보유 예수금의 86.9%만큼 분할 매수

    주식은 조금이라도 변화 폭이 커지면 3분의 1 정도를 분할 매수/매도하고 변화의 폭이 작아지면 대부분의 예수금을 다시 투자에 활용하는 것이 좋다. 그에 비해 채권은 꽤 많은 양의 수익이 날 때까지도 계속 매수를 해도 괜찮은 모습을 보이고 대신 손실을 보고 있을 때는 빠르게 대부분의 돈을 청산하고 다시 매수를 할 때도 적당한 비율로 조금씩 매수하는 것이 좋다. 금은 오히려 상승에는 둔감하게 반응했지만 하락에는 민감하게 반응하여 파는 것이 좋다는 결과가 나왔다. 매번 시뮬레이션을 돌릴 때마다 최적의 변수는 다양하게 발생할 수 있기 때문에 기호에 맞게 선택해서 쓸 수도 있을 것 같다.

     

    구현

    기존의 utils.py 파일을 그대로 사용하고 simulation_strategy.py 파일을 추가하였다.

    import random
    from utils import (
        load_stock_prices, to_kr_prices, get_dates, save_results, get_mdd
    )
    
    
    # 가격의 최대 변화폭 계산
    def obtain_max_diffs(prices):
        prev_price = prices[0]
        max_increment = 0
        max_decrement = 0
        for price in prices[1:]:
            price_diff = (price - prev_price) / prev_price
            if price_diff > max_increment:
                max_increment = price_diff
            if -price_diff > max_decrement:
                max_decrement = -price_diff
            prev_price = price
    
        return [max_decrement, max_increment]
    
    
    # 예금에대한 처리를 위한 helper function
    def obtain_cash_results(duration, price = 200000):
        results = []
        cash = price
        results.append(cash)
        for _ in range(1, duration):
            cash *= 1.00165
            cash += price
            results.append(int(cash))
        return results
    
    
    # 정해진 hyperparameter에 대한 시뮬레이션 진행
    def simulate(prices, a, b, c, d, e, f, g, h):
        prev_price = prices[0]
        volume = 200000 // prev_price
        balance = volume * prev_price
        deposit = 200000 - balance
        buying_balance = balance
        results = [deposit + balance]
    
        for price in prices[1:]:
            deposit += 200000
            price_diff = (price - prev_price) / prev_price
            updated_balance = price * volume
            # 수익을 보고 있을 때
            if buying_balance < updated_balance:
                # 자산 가격이 A% 이상 상승한 경우 보유 주식의 B%만큼 분할 매도 (수익 실현)
                if price_diff > a:
                    sell_volume = int(volume * b)
                    deposit += sell_volume * price
                    buying_balance -= sell_volume * price
                    volume -= sell_volume
                    balance = volume * price
                # 자산 가격이 C% 이상 하락한 경우 보유 주식의 D%만큼 분할 매도 (수익 실현) 
                elif price_diff < -c:
                    sell_volume = int(volume * d)
                    deposit += sell_volume * price
                    buying_balance -= sell_volume * price
                    volume -= sell_volume
                    balance = volume * price
                # 그 외, 보유 예수금의 E%만큼 분할 매수
                else:
                    buy_volume = int(deposit * e) // (price * 1.003)
                    deposit -= int(buy_volume * price * 1.003)
                    buying_balance += buy_volume * price
                    volume += buy_volume
                    balance = volume * price
            # 손실을 보고 있을 때
            else:
                # 자산 가격이 F% 이상 하락한 경우 보유 주식의 G%만큼 분할 매도 (손실 최소화)
                if price_diff < -f:
                    sell_volume = int(volume * g)
                    deposit += sell_volume * price
                    buying_balance -= sell_volume * price
                    volume -= sell_volume
                    balance = volume * price
                # 그 외, 보유 예수금의 H%만큼 분할 매수
                else:
                    buy_volume = int(deposit * h) // (price * 1.003)
                    deposit -= int(buy_volume * price * 1.003)
                    buying_balance += buy_volume * price
                    volume += buy_volume
                    balance = volume * price
            prev_price = price
            results.append(int(deposit + balance))
        return results
    
    
    # Hyperparameter 탐색을 위하여 각 자산별로 20000번의 시뮬레이션 실행
    def search(prices):
        best_score = -1
        best_results = None
        max_decrement, max_increment = obtain_max_diffs(prices)
        for _ in range(20000):
            a = random.random() * max_increment
            b = random.random()
            c = random.random() * max_decrement
            d = random.random()
            e = random.random()
            f = random.random() * max_decrement
            g = random.random()
            h = random.random()
            results = simulate(prices, a, b, c, d, e, f, g, h)
            if best_score < results[-1]:
                best_score = results[-1]
                best_results = results
        return best_results, (a, b, c, d, e, f, g, h)
    
    
    # 월별 데이터 전처리
    dates = get_dates()
    dollar_prices = load_stock_prices('USD_KRW')
    stock_prices = to_kr_prices(load_stock_prices('SPY'), dollar_prices)
    bond_prices = to_kr_prices(load_stock_prices('TLT'), dollar_prices)
    gold_prices = to_kr_prices(load_stock_prices('IAU'), dollar_prices)
    
    # 시뮬레이션 실행
    stock_results, stock_params = search(stock_prices)
    bond_results, bond_params = search(bond_prices)
    gold_results, gold_params = search(gold_prices)
    cash_results = obtain_cash_results(len(dates))
    only_cash_results = obtain_cash_results(len(dates), price=800000)
    print('Stock params', *[f'{p:.3f}' for p in stock_params])
    print('Bond params', *[f'{p:.3f}' for p in bond_params])
    print('Gold params', *[f'{p:.3f}' for p in gold_params])
    
    total_results = zip(stock_results, bond_results, gold_results, cash_results)
    total_results = [sum([sr, br, gr, cr]) for sr, br, gr, cr in total_results]
    mdd, mdd_date = get_mdd(dates, total_results)
    print('MDD', mdd, mdd_date)
    print('Result', total_results[-1])
    inputs = list(range(800000, 800000 * (len(dates) + 1), 800000))
    total_results = zip(dates, inputs, total_results, only_cash_results)
    save_results(total_results)
    

     

    다음에 탐색할 전략 계획

    보통 영구 포트폴리오 같이 정해진 비율로 자산을 가지고 있는 자산 운용 전략은 수익이 나서 비중이 높아진 자산을 팔고 비중이 적어진 자산을 사서 (rebalancing) 더 좋은 수익을 낸다. 그렇다면 매달 투자 금액이 적금식으로 추가될 때 현재 자산들의 비중에 따라서 투자 금액을 분배하여 매수하면 전략 1과 비교해서 어떻게 수익금이 달라질지 분석해 볼 것이다.

    댓글

Designed by Tistory.