본문 바로가기
파이썬/주식

Python으로 추세추종(모멘텀) 전략 백테스팅

by 행복론자 2020. 1. 2.

매매를 하는데 있어 수익을 얻기 위해서는 싸게 사서 비싸게 팔아야한다는 것을 누구나 다 알고 계실겁니다. 

그렇다면 주식이 언제쯤 "싸다"고 할 수 있을까요?

가격이 계속떨어진다고 하여 싸졌다고 할 수도 있지만 내가 매수한 다음날 가격이 떨어졌다고 하면

과연 싸게 샀다가 할 수 있을까요? 그렇지 않을 겁니다.

 

메리츠종금증권의 예를 들어 보겠습니다. 

 

 

메리츠종금은 2019.10월을 기점으로 최고가 5,160원에 도달하고 곧 한달 사이에 4,170원이 되었습니다.

그전에 메리츠종금의 주가가 4천 중후반 ~ 5천 중반까지 갔던 것을 감안하면 4170원은 충분히 가격이라고 할 수 있습니다. 

 

그래 좋다. 싸다 싶어서 4,170원에 주식을 매수했다고 해보겠습니다. 

 

 

채 몇 주가 지나지 않아 10%가 더 넘게 빠져버렸습니다. 

이렇듯 평소보다 가격이 빠진다고 하여 저점이라고 판단하기에는 무리가 있습니다.

떨어질 때 사면 더 떨어져버리고 비쌀 때 사면 조금 있다가 또 떨어져버리는 주식의 가격을 예측하는 것이 애당초 무의미한 일이기 때문입니다.

 

그러면 도대체 우리는 언제 주식을 사야 할까요?

이를 가늠해보기 위해서 알아야 할 것이 바로 주가의 중요한 속성인 추세입니다. 

주가는 떨어지기 시작하면 계속 떨어지고, 오르기 시작하면 계속 해서 오르는 성질이 있습니다.

싸다고 생각해서 샀는데 다음날, 또 다음날 계속해서 떨어진다면 결코 싸게 샀다고 할 수 없습니다.

 

이는 하방 추세에 매수하는 경우에 해당합니다.

주가는 하락하는 중이고 언제까지 하락하는지 알 수가 없는 마당에 내가 산 시점에 추세가 바뀌어 상승하면 다행이지만 그렇지 않고 계속 하방 추세가 유지된다면 계좌가 박살이 나겠지요.

 

물론 가격이 떨어지는 추세라고 할지라도 오르는 날도 당연히 존재하여 주가가 올랐다 내렸다를 반복할 수 있지만 결국에는 그 차이가 존재하여 계속 조금씩 떨어지는 상태가 될 것입니다. 그 반대의 상황도 마찬가지입니다. 

 

그렇기 때문에 오히려 저점을 잡겠다고 하기 보다는 추세가 전환된 다음에 매수하는 것이 더 좋을 수 있습니다.

무슨 말이냐면,

예를 들어 최근 20일 동안의 평균 가격보다 오늘 주가가 더 비쌀 경우 매수하는 것이 오히려 수익을 얻을 확률이 더 높다고 할 수도 있습니다. 

비록 이는 최근 주가 흐름에서 비쌀 때 샀다지만 상승 추세이기 때문에 더 비싼 가격에 팔 수 있다!는 아이디어입니다.

 

반대로 파는 시점은 20일 동안의 평균 가격보다 오늘 주가가 더 싸졌을 때라고 설정해보겠습니다.

이 의미는 하방 추세가 시작됐다고 판단되면 마찬가지로 하락한다는 추세에 맞춰 어떻게든 계속 떨어질 것이니 미리 매도하여 손실을 줄이겠다는 전략입니다. 

 

이 전략(추세추종 전략)을 삼성전자를 대상으로 2019년 한해 동안 시드 100만원을 갖고 수행해보겠습니다.

import datetime
import backtrader as bt

# Create a subclass of Strategy to define the indicators and logic
class Momentum(bt.Strategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=20  # period for the slow moving average
    )

    def __init__(self):
        self.dataclose = self.datas[0].close
        self.smaSlow = bt.ind.SimpleMovingAverage(period=self.p.pslow)
        self.smaFast = bt.ind.SimpleMovingAverage(period=self.p.pfast)
        self.order = None

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def notify_order(self, order):
        # 1. If order is submitted/accepted, do nothing
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 2. If order is buy/sell executed, report price executed
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log('BUY EXECUTED, Price: {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm: {3:8.2f}'.format(
                    order.executed.price,
                    order.executed.size,
                    order.executed.value,
                    order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log('SELL EXECUTED, {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm{3:8.2f}'.format(
                    order.executed.price,
                    order.executed.size,
                    order.executed.value,
                    order.executed.comm))

            self.bar_executed = len(self)  # when was trade executed
        # 3. If order is canceled/margin/rejected, report order canceled
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        self.order = None

    def next(self):
        cash = self.broker.get_cash()
        value = self.broker.get_value()
        size = int(cash / self.data.close[0])
        # Order가 Pending인지 확인, 그렇다면 다시 주문할 수 없음
        if self.order:
            return

        if not self.position:  # not in the market
            if self.smaSlow < self.data.close[0]:
                self.order = self.buy(size=size)

        elif self.getposition().size > 0: #in the market
            if self.smaSlow > self.data.close[0]:
                self.order = self.sell(size=self.getposition().size)

def run(args=None):
    cerebro = bt.Cerebro()  # create a "Cerebro" engine instance

    cerebro.broker.setcash(1000000) #초기자금
    #데이터 생성
    data = bt.feeds.YahooFinanceData(dataname='005930.KS',
                                     fromdate=datetime.datetime(2019, 1, 1),
                                     todate=datetime.datetime(2019, 12, 31))

    cerebro.adddata(data) #데이터 삽입
    cerebro.addstrategy(Momentum) #전략적용
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.run() #수행
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.plot() #plot

if __name__ == '__main__':
    run()

 

 

다음은 수행결과입니다.

 

 

100만원으로 123만원이 되었으니 23만원을 벌었습니다!

 

다음은 그 반대 전략입니다. (추세반대전략, 역추세전략)

20일 평균보다 가격이 떨어진 경우 싸다고 생각해서 사고 20일 평균보다 가격이 비싸진 경우에는 충분히 비싸다고 생각하여 매도하는 전략을 수행해보겠습니다.

 

이를 코드로 나타내고자한다면 self.sma와 self.data.close[0]의 비교를 바꿔주시면 됩니다. 

  def next(self):
        cash = self.broker.get_cash()
        value = self.broker.get_value()
        size = int(cash / self.data.close[0])
        # Order가 Pending인지 확인, 그렇다면 다시 주문할 수 없음
        if self.order:
            return

        if not self.position:  # not in the market
            if self.smaSlow < self.data.close[0]:
                self.order = self.buy(size=size)

        elif self.getposition().size > 0: #in the market
            if self.smaSlow > self.data.close[0]:
                self.order = self.sell(size=self.getposition().size)

 

 

그렇게 변경 후 실행해보면 결과는 이렇습니다.

 

 

마지막으로 매도 포지션을 잡고 난뒤 캐쉬가 111만원이 남았습니다. 11% 수익으로 앞의 전략과 꽤 차이가 있습니다.

 

혹 어느분께서는 23%나 11%나 둘 다 돈 번 것 아니냐? 돈 벌면 됐지 뭐가 중요하냐고 하실 수도 있겠지만

이는 결국에는 특정 기간동안 우상향한 삼성전자라는 주식의 특성상 두 전략 모두 돈을 번 것이지 다른 주식, 다른 기간이었으면 그렇지 못한 경우도 수두룩 빽빽일 수 있습니다. 물론 둘 다 돈을 벌어 다행이지만 강조 하고 싶은 것은 주가란 오르고 내리고를 반복하는 성질, 즉 추세라는 것이 존재하고 우리는 이를 거스를 수가 없다는 것입니다. 

 

한번 떨어지기 시작한 주식은 그 하락폭을 가늠할 수가 없어서 하락 추세에 내가 매수 포지션을 잡았다고 하면 손실이 없으면 다행이지만 떨어지면 또 언제까지 떨어질지 모릅니다. 그렇기 때문에 하락폭이 크게 열려있는 구조입니다.

 

그 반면 추세를 따르는 매매를 할 경우, 상승추세에 매수하여 큰 상승을 기대할 수 있는 상승폭을 크게 열어놓은 구조라는 장점이 있습니다. 

 

그렇다고 해서 추세반대 전략이 잘못된 전략이라는 말은 절대 아닙니다. 당연히 수익이 있을 수 있습니다.

하지만 구조적으로 추세추종 전략은 하락폭은 제한된 반면 상승폭은 크게 열려있고 추세반대 전략은 하락폭이 크게 열려있고 상승폭이 제한(비싸다고 판단하면 팔아버리기에 상승중인 추세에 더 큰 상승의 효과를 보지 못함)되어 있다는 점에서 장기적으로 보았을 때 추세추종 전략이 더 높은 손익비를 가져올 것으로 기대합니다. 


같이 보시면 좋을 글

2021/02/10 - [파이썬/주식 자동매매] - 주식매매프로그램 개발 노하우, 소스를 담은 전자책이 발간되었습니다.

 

주식매매프로그램 개발 노하우, 소스를 담은 전자책이 발간되었습니다.

주식매매프로그램 개발 노하우에 관한 제 전자책이 발간되었습니다. https://kmong.com/gig/292764 주식매매프로그램, 쉽게 따라 만드는 노하우와 소스를 드립니다. | 36000원부터 시작 가능한 총 평 0개

jsp-dev.tistory.com

 

반응형
이 포스팅은 쿠팡파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글