-
삼성전자 주식 조금이라도 싸게 모을 수 있을까?재테크 2021. 1. 31. 01:40
삼성전자는 우리나라 최고의 대장주이고 오랜 기간 동안 엄청난 성장률을 보여주었다. 나를 비롯한 많은 장기 투자자들은 삼성전자 혹은 다른 미래에 성장할 좋은 회사에 투자하여 많은 수익을 내고 싶어 한다. 좋은 회사는 미래에 높은 확률로 가격이 오를 것이기 때문에 시드머니가 있다면 투자를 하기로 마음을 먹은 후 최대한 빠른 시간 내에 전부를 투자해야 장기적으로 수익이 좋을 것이다 (미래에 가격이 오를 것이기 때문에). 하지만 나는 많은 시드머니를 가지고 있지 않고 월마다 월급으로 투자를 하는 직장인에 불과하다. 그렇기 때문에 목표하는 가격에 도달할 때까지 매주, 매달 좋은 주식을 몇 주씩 살 예정인데 문득, 이왕 가격이 오를거면 조금이라도 싸게 살 수 있는 전략이 없을까라는 의문이 생겼고 이를 검증해보고 싶어 졌다.
데이터 수집
각종 회사의 일별 주가 정보를 가져오려면 google spreadsheet가 가장 간단한 방법이다. 전략을 세우고 백테스트를 해보기 위해서는 삼성전자 주가 데이터가 필요했다. 이는 구글 스프레드시트의 도움을 통해서 아주 쉽게 얻을 수 있었는데, googlefinance("KRX:005930","all","2000. 1. 1","2021. 1. 26","DAILY") 함수를 이용하면 바로 전체 데이터의 일별 가격 정보를 알 수 있었다. 더 자세한 도움말은 해당 함수를 스프레드시트에 입력하고 자세히 알아보기 버튼이나 다른 블로그 등에서 찾아볼 수 있다. 2000년 1월부터 정보를 가져오고 싶었지만 2010년 10월 11일부터 나오는 것으로 봐서 저장되어있는 데이터의 한계를 가지고 있는 걸로 보였다.
전략 설정
원래는 한 달에 한 번 월급을 받고 투자를 한다. 하지만 데이터의 수가 너무 적어져서 백테스트의 안정성이 떨어지기 때문에 일주일에 1주 꼴로(거래일 기준으로 5일에 1주) 매수를 했을 때 평균 매수 가격이 가장 낮아지도록 전략을 설정하였다. 또한 앞으로도 누구나 쉽게 적용할 수 있는 전략이 여야 하기 때문에 매수 전략은 가능한 한 단순하게 설계를 했다. 다소 작위적이지만 직관에 따라 세워본 3가지 간단한 전략은 다음과 같다:
기다렸다가 싸게 사기: 원래 사기로 한 날짜 기준으로 최대 4일까지 A%만큼 가격이 싸지는 것을 기다렸다가 1주를 매수한다. 만약 4일까지 A%만큼 싸지지 않는다면 마지막 4일째 가격으로 1주 매수한다.
싸졌을 때 최대한 많이 사기 1: 일주일간의 가격 변동을 보고 B% 이상 가격이 하락했을 때 C 주만큼 미리 매수하고 남은 (C - 1) 주일 동안 거래를 하지 않는다. 너무 미리 많이 살 수 있으면 적금식으로 사모으는 것에 의미가 떨어지기 때문에 C 값은 2 ~ 12 값 사이로 결정된다.
싸졌을 때 최대한 많이 사기 2: 거래일 기준 전고점 대비 D% 이상 떨어졌을 때 E주만큼 미리 매수하고 남은 (E -1) 주일 동안 거래를 하지 않는다. 너무 미리 많이 살 수 있으면 적금식으로 사모으는 것에 의미가 떨어지기 때문에 E값은 2 ~ 12 값 사이로 결정된다.
먼저 가장 단순한 방법인 전략 없이 매주 1주씩 사는 전략을 기본 전략이라고 했을 때 이 전략은 위의 기간 동안 총 506주를 매수했으며 1월 26일 86700원 기준으로 평균 매수 가격은 34883원으로 248.5%의 이익을 보고 있다. 위의 3가지 전략이 기본 전략보다 더 좋으려면 평균 매수 가격이 34883원보다 유의미하게 싸야 한다는 뜻이다. 그나저나 이런 단순한 방법으로 최근에 주식을 살 수록 수익률이 계속 희석되어도 약 250%의 수익률을 가져다주는 삼성전자는 정말 대단한 주식이다.
시뮬레이션 결과
각 전략마다 모두 5000번의 시뮬레이션을 했고 평균 매수 가격이 가장 낮은 하이퍼 파라미터 상위 10개와 그 가격들의 평균 가격을 계산해보았다.
기다렸다가 싸게 사기
최적의 A는 0.18%로서 조금이라도 싸지면 사는 전략이 최선의 전략으로 나왔다. 최적의 전략 기준으로 평균 매수 가격은 34796원으로 총 249.2%의 이익을 보았다. 이는 총 505번의 거래 동안 352번, 즉 70%의 확률로 원래 거래일보다 싸게 샀지만 평균 1.4% 싸게 샀고 결국 4 거래일 뒤에 사는 153번의 경우에서 평균 3.3% 비싸게 사게 되어서 크게 싸게 사기 힘든 전략이 되었다.
싸졌을 때 최대한 많이 사기 1
최적의 전략은 일주일간의 가격 변동 기준으로 2.59% 이상 가격이 하락했을 때 12주를 미리 사고 남은 11주일 동안 기다리는 전략이다. 이는 평균 매수 가격은 34147원으로 253.9%의 이익을 보았다. 거래를 마음먹은 날이 됐을 때 꽤 떨어졌다는 생각이 들면 적금식으로 가능한 만큼 가장 많은 주식을 미리 사고 그만큼의 시간을 미래에 가격이 오르기를 기다리는 전략이 효율적이라는 뜻이다.
싸졌을 때 최대한 많이 사기 2
최적의 전략은 전 고점 대비 3.3% 이상 가격이 하락했을 때 10주를 미리 사고 남은 9주일 동안 기다리는 전략이다. 이는 평균 매수 가격은 34193원으로 253.6%의 이익을 보았다. 마찬가지로 전 고점에서 어느 정도 떨어져 있거나 혹은 떨어지는 동안은 가능한 한 최대한 미리 주식을 많이 사고 미래에 가격이 올라가는 방법이 좋다는 뜻이다. 싸졌을 때 많이 사기 전략들은 전체적인 방향이 크게 다르지 않고 10년이라는 긴 시간에서는 수익률의 차이가 거의 나지 않았다.
논의
먼저, 수익률적인 측면의 결과는 매우 실망스럽지만 장기적인 매매기법에 대한 많은 인사이트를 얻을 수 있었다. 주식은 사기로 마음을 먹었을 때 바로 사는 것이 최적의 타이밍이라는 말이 있듯이, 투자 금액이 생길 때마다 사는 기본전략을 유의미하게 이기는 전략을 적어도 간단하게는 세우기 어려웠다. 위의 전략들 말고도 다른 직관적으로 그럴듯한(?) 전략을 설계하여 적용해봤지만 수익을 내는 기간이 길어짐에 따라 유의미한 차이를 내기 어려웠다. 일단 기다렸다가 싸게 사기 전략은 장기적으로 오를 주식에는 떨어져야만 하는 이유를 알지 않는 한 더 나중에 사는 전략이 이기기가 힘들었다. 각 싸졌을 때 많이 사기 전략에서 주었던 인사이트는 생각보다 가격이 안 내려갔어도 더 떨어지는 것을 기다리는 것보다 최대한 빨리 많이 사두는 것이 장기적으로는 이익이라는 사실이 흥미로웠다. 그러나 미래에 비싸질 가능성이 높은 주식을 최대한 미리 사는 것이 장기적으로는 당연히 수익률이 좋을 수밖에 없는데 이 당연한 추론보다도 유의미하게 좋은 수익률을 주지 않아서 아쉬웠다.
결론적으로 이번 분석이 준 인사이트는 다음과 같다. 적금식으로 우량한 회사의 주식을 사모을 때, 기업에 대한 정보와 추세 등을 보며 단기적으로 주식이 오를지 혹은 떨어질지 확실히 모를 때만 다음의 전략을 사용한다. 신고점을 갱신하며 올라갈 때는 천천히 사고 떨어질 때는 최대한 살 수 있는 만큼 많이 사고 기다린다. 하지만 그렇다 하더라도 수익률에 큰 차이는 없다.
구현
Python 3.6 기반이며, 바로 실험해볼 수 있게 기본 모듈로만 구현을 하였다.
import random import csv from datetime import datetime from statistics import mean # 기간에 따른 가격 변동률 계산 def update_change(records, key, duration): for i in range(duration): records[i][key] = 0 for i, record in enumerate(records[duration:], duration): prev_price = records[i - duration]['price'] price = record['price'] change = (price - prev_price) / prev_price record[key] = float(round(change, 4)) # Google spreadsheet의 결과를 csv 파일로 저장한 것을 불러온다 def load_stocks(name): with open(f'./stocks/{name}.csv', encoding='utf-8-sig') as data_file: records = [] lines = csv.reader(data_file, delimiter=',') next(lines) for line in lines: date = datetime.strptime(line[0][:-11], '%Y. %m. %d') price = int(line[4]) records.append({'date': date, 'price': price}) update_change(records, 'day_change', 1) update_change(records, 'week_change', 5) update_change(records, 'month_change', 20) return records # MDD의 계산 def get_mdd(results): mdd = 0 max_result = results[0] for i, result in enumerate(results[1:], 1): if result > max_result: max_result = result diff = (result - max_result) / max_result if diff < -mdd: mdd = -diff return mdd # 기본 전략에 대한 시뮬레이션 def base_simulate(records): buy_prices = [] for i in range(5, len(records), 5): record = records[i] buy_prices.append(record['price']) return mean(buy_prices) # 기다렸다가 싸게 사기 def simulate1(records): a = random.random() * 0.1 buy_prices = [] for i in range(5, len(records) - 5, 5): record = records[i] price = record['price'] for j in range(1, 5): next_price = records[i + j]['price'] if next_price < (1 - a) * price: buy_prices.append(next_price) break else: next_price = records[i + 4]['price'] buy_prices.append(next_price) return mean(buy_prices), a # 싸졌을 때 최대한 많이 사기 1 def simulate2(records): borrow = 0 a = random.random() * min_week_change b = random.randint(2, 12) buy_prices = [] for i in range(5, len(records), 5): record = records[i] price = record['price'] if borrow > 0: borrow -= 1 continue if record['week_change'] < a: borrow = b - 1 buy_prices += [price] * b else: buy_prices.append(price) return mean(buy_prices), a, b # 싸졌을 때 최대한 많이 사기 2 def simulate3(records): a = random.random() * mdd b = random.randint(2, 12) max_price = records[0]['price'] buy_prices = [] borrow = 0 for i in range(5, len(records)): record = records[i] price = record['price'] if price > max_price: max_price = price if i % 5 != 0: continue if borrow > 0: borrow -= 1 continue lmdd = (max_price - price) / max_price if lmdd > a: borrow += b - 1 buy_prices += [price] * b else: buy_prices.append(price) return mean(buy_prices), a, b # 5000번의 시뮬레이션을 통한 최적의 전략 탐색 def search(simulate_func, records): results = [] for _ in range(5000): results.append(simulate_func(records)) sorted_results = sorted(results, key=lambda x: x[0]) mean_min_price = mean([x[0] for x in sorted_results[:10]]) print(f'A: {mean([x[1] for x in sorted_results[:10]]):.4f}') if len(sorted_results[0]) >= 3: print(f'B: {mean([x[2] for x in sorted_results[:10]]):.4f}') print([f'{x[1]:.4f}' for x in sorted_results[:10]]) if len(sorted_results[0]) >= 3: print([x[2] for x in sorted_results[:10]]) print(f'Price: {mean_min_price:.1f}') print(f'Rate of Return: {records[-1]["price"] / mean_min_price:.3f}') records = load_stocks('samsung') # 하이퍼 파라미터의 범위를 구하기 위하여 최대 변화량 계산 min_week_change = min([r['week_change'] for r in records]) mdd = get_mdd([r['price'] for r in records]) # 기본 전략 결과 계산 base_result = base_simulate(records) print(f'MDD: {mdd:.3f}') print(f'Base Price: {base_result:.1f}') print(f'Base Rate of Return: {records[-1]["price"] / base_result:.3f}') # 최적의 전략 탐색 search(simulate1, records) search(simulate2, records) search(simulate3, records)
'재테크' 카테고리의 다른 글
주가 데이터 자동으로 가져오기 (gspread 이용) (1) 2021.06.22 삼성전자 주식 조금이라도 싸게 모을 수 있을까? (2) - RSI 보조지표의 활용 (0) 2021.03.01 영구포트폴리오 직장인에게 최적화하기 (3) (0) 2021.01.24 영구 포트폴리오 직장인에게 최적화하기 (2) (0) 2021.01.16 영구 포트폴리오 직장인에게 최적화하기 (1) (1) 2021.01.13