-
영구 포트폴리오 직장인에게 최적화하기 (2)재테크 2021. 1. 16. 03:07
이번 글은 지난 글에 이어서 직장인에 맞는 적금식 영구 포트폴리오의 운용과 조금이라도 나은 수익률을 위한 운용 전략을 시뮬레이션을 통하여 찾아보고 그 전략을 공유하는 글이다. 먼저 나는 매월 발생하는 투자금액을 균등하게 나누어서 무조건 자산을 구매하는 전략을 취했을 때 연평균 6.14%의 복리 이자의 효과를 거둘 수 있음을 확인했다. 이번에는 자산 가격의 변화에 따라 분할 매수/분할 매도를 했을 때 수익률이 어떻게 달라질지를 분석하였다.
전략 2. 각 자산 가격의 변동성을 보고 분할 매수/매도 하기
이 전략의 투자금액은 전략 1과 똑같이 매월 80만 원 기준으로 20만 원씩 분배한다. 그러나 각 자산별로 1) 수익 상황에 따라(수익/손실) 혹은 자산 가격의 변동성에 따라(가격의 상승/하락) 분할 매수/매도를 하여 이익을 극대화하는 전략이다. 이를 더 자세하게 상황별로 정리하면 아래와 같다.
수익을 내고 있을 때
- 자산 가격이 A% 이상 상승한 경우 보유 주식의 B%만큼 분할 매도 (수익 실현)
- 자산 가격이 C% 이상 하락한 경우 보유 주식의 D%만큼 분할 매도 (수익 실현)
- 그 외, 보유 예수금의 E%만큼 분할 매수
손실을 보고 있을 때
- 자산 가격이 F% 이상 하락한 경우 보유 주식의 G%만큼 분할 매도 (손실 최소화)
- 그 외, 보유 예수금의 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의 결과를 정리하면 아래와 같다.
주식 운용 전략
수익을 내고 있을 때
- 자산 가격이 10.8%이상 상승한 경우 보유 주식의 23.6%만큼 분할 매도 (수익 실현)
- 자산 가격이 8.3%이상 하락한 경우 보유 주식의 19.6%만큼 분할 매도 (수익 실현)
- 그 외, 보유 예수금의 76%만큼 분할 매수
손실을 보고 있을 때
- 자산 가격이 4.9%이상 하락한 경우 보유 주식의 30.7%만큼 분할 매도 (손실 최소화)
- 그 외, 보유 예수금의 55.3%만큼 분할 매수
채권 운용 전략
수익을 내고 있을 때
- 자산 가격이 29.3%이상 상승한 경우 보유 주식의 48.4%만큼 분할 매도 (수익 실현)
- 자산 가격이 7.9%이상 하락한 경우 보유 주식의 19.8%만큼 분할 매도 (수익 실현)
- 그 외, 보유 예수금의 29.7%만큼 분할 매수
손실을 보고 있을 때
- 자산 가격이 12.2%이상 하락한 경우 보유 주식의 58.8%만큼 분할 매도 (손실 최소화)
- 그 외, 보유 예수금의 17%만큼 분할 매수
금 운용 전략
수익을 내고 있을 때
- 자산 가격이 24%이상 상승한 경우 보유 주식의 36%만큼 분할 매도 (수익 실현)
- 자산 가격이 1%이상 하락한 경우 보유 주식의 63%만큼 분할 매도 (수익 실현)
- 그 외, 보유 예수금의 81.9%만큼 분할 매수
손실을 보고 있을 때
- 자산 가격이 3%이상 하락한 경우 보유 주식의 20.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과 비교해서 어떻게 수익금이 달라질지 분석해 볼 것이다.
'재테크' 카테고리의 다른 글
주가 데이터 자동으로 가져오기 (gspread 이용) (1) 2021.06.22 삼성전자 주식 조금이라도 싸게 모을 수 있을까? (2) - RSI 보조지표의 활용 (0) 2021.03.01 삼성전자 주식 조금이라도 싸게 모을 수 있을까? (0) 2021.01.31 영구포트폴리오 직장인에게 최적화하기 (3) (0) 2021.01.24 영구 포트폴리오 직장인에게 최적화하기 (1) (1) 2021.01.13