지난 2장에 이어 3장도 따라가보겠다. 3장에서는 나도 가끔씩 잡주계좌에서 단기적으로 이 지표들을 이용하여 소액 매매를 하곤 하는데, 항상 맞는 건 아니어도 꽤나 유용하게 써먹을 때가 종종 있다. 책에서는 MACD, Stochastic, RSI, Envelope, 볼린저밴드 등을 소개한다.
● MACD - 이동평균수렴확산지수
- 개념
MACD는 Moving Average Convergence Divergence의 약자이다. 기간이 다른 2개의 이동평균선을 그린 후 기간이 짧은 이동평균선이 위에 있을 때, 상승 추세라고 판단하는 것이다. 증권사 HTS는 통상 short 12, Long 26, Signal 9 이렇게 설정해서 많이 본다. 단기 이동평균선은 12일, 장기 이동평균선은 26일을 사용하되 Signal은 MACD의 이동평균을 또 사용하는 것이다. 이동평균의 이동평균이라고 보면 된다. (Signal이 더 후행 성이라고 보면 된다.) 통상 MACD값이 Signal을 상향/하향 돌파할 때 매수 혹은 매도의 행위 등을 많이 한다.
저번에 Chat Gpt에게 물어봤던 50일 이동평균선이 200일 이동평균선 보다 위에 있으면 매수하고 (상승 추세에서의 모멘텀 전략), 50일 이동평균선이 200일 이동편균선 보다 아래 있으면 매도한다. 의 개념과 유사하다.
https://jhhistory.tistory.com/21
그런데, 이때 이동평균을 사용해버리면 실제 주가의 추이 대비 MACD의 경향이 너무 느리게 나타나는 단점이 있다. 그래서, 이를 보완하기 위해 EMA (exponential Moving Average)인 지수이동평균을 사용한다. 지수이동평균은 단순 이동평균보다 최근의 경향을 조금 더 잘 반영한다. 수식은 생략한다. 의미만 알면된다. 그래서 우리가보는 MACD는 사실 EMA에 의한 계산이다.
결론적으로 우리가 HTS에서 주로 보는 것은 아래와 같다.
MACD = EMA(12)-EMA(26)
MACD signal = EMA (MACD, 9)
MACD Oscillator = MACD - MACD signal
12일 짜리 지수이동평균과 26일 짜리 지수이동평균을 뺀 값을 MACD라하고 그 값을 9일 동안 이동평균한 값을 Signal이라 하고, MACD와 Signal 값을 뺀 값을 Oscillator 값이라고 한다.
이렇게 주황선이 MACD, 보라선이 Signal 왔다갔다리 하고 있는 하늘색 주황색 막대그래프가 Oscillator 이렇게 된다. 보통 Oscillator가 0이상 일때, 매수를 추천한다.
- 투자전략
1. MACD가 +면 매수, -면 매도
2. Oscillator가 +면 매수, -면 매도
우선 함수를 불러와서 MSFT의 2020-01-01 ~ 2020-12-31 까지의 그래프를 그려보자.
import finterstellar as fs
symbol = 'MSFT'
df = fs.get_price(symbol, start_date='2020-01-01',end_date='2020-12-31')
fs.draw_chart(df, right=symbol)
그러면 책에 나와있는 것과 똑같이 MSFT의 차트가 그려진다. 이때, fs내의 macd()함수를 이용해서 전략을 실행시켜보자. 지정하지 않으면 기본적으로 short 12, long 26, signal 9가 설정되는 것으로 내장되어 있다고 한다.
fs.macd(df)
MACD, MACD_signal, MACD_oscillator가 모두 생성되었다. 이걸 차트로 그려보면
fs.draw_chart(df, right=['macd','macd_signal','macd_oscillator'])
이렇게 형성이 된다. MACD에 관련된 모든 세 그래프가 2020년 3월~4월 빼고는 거의 0위에 위치하고 있다. 이제 작전대로 MACD Oscillator가 0이상인 경우에 매수하는 시그널을 만들어보면 2장 때와 마찬가지로 indicator_to_signal()함수를 사용한다.
fs.indicator_to_signal(df, factor='macd_oscillator',buy=0,sell=0)
이제 position()함수를 이용하여 그래프로 포지션 변동상황에 대한 추이를 살펴보자.
fs.position(df)
fs.draw_chart(df, right='position_chart',left='macd_oscillator')
빨간색이 주식의 포지션을 나타내는 그래프인데, 이 선이 1에 있으면 주식이 보유하는 것이고 0에있으면 주식이 없는 제로포지션이다. macd_oscillator가 0이상일 때는 롱, 0이하 일 때는 제로 포지션인셈이다. 그래서 이 전략대로 트레이딩을 한다고 했을 때 성과를 산출하면 performance() 함수를 사용한다.
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
거래비용은 0.1%인 것이고, 기준이되는 무위험이자는 1% 그리고 그것에 대한 trade result를 보는 함수를 그래프로 그려라 라는 의미이다.
이렇게 되면 아래와 같은 결과를 분석해볼 수 있다.
1) 연평균 수익률은 23.49% (CAGR)
2) 누적 수익률 23.49%
3) 평균수익률은 2.15%, (수익를 낸 수익률과 마이너스를 낸 수익률를 모두 함께 평균을 낸 수치)
4) 벤치마크 수익률이 38.48% (걍 들고있었으면 38.48% 임)
5) 거래횟수는 11번 (전략 발동이 11번)
6) 성공한 거래는 6번
7) 성공률은 54.55%
8) 투자기간은 1년
9) 샤프비율은 0.77 - 준수한 수준이라고 함.
10) MDD는 -11.28%
11) 벤치마크 MDD는 -28.24% (가만히 들고있었으면 수익률은 컸으나, 위험관리 측면에서 전략을 쓰는게 더 우수)
● RSI - 상대강도지수
너무 유명한 RSI라는 지표이다. 주가의 상승 강도와 하락 강도를 측정하기 위해 만들어진 것 인데, 일정 기간 동안 일간 상승폭과 하락폭을 각각 계산하고 움직인 총 폭 중에서, 그중 상승폭이 얼마나 큰지 비율을 계산해서 백분율로 나타낸 값이다. 정확한 계산과정은 생략하고 '개념'만 챙기도록 한다.
통상 RSI가 70이상이면 과매수, 30이하이면 과매도 구간으로 분류한다. 그래서 횡보하는 주식일 때, 이 지표를 활용해서 수익을 얻을 수도있겠다. RSI를 구하는 기간은 14일이 기본으로 설정되어 있다. 애플이나 마이크로소프트 같이 대세 우상향한 종목말고 버라이즌(VZ)같은 안정적으로 횡보하면서 평균회귀하는 통신회사를 가지고 백테스를 해보자.
- 투자전략
- RSI가 70이상이면 매도 / RSI가 30이상이면 매수
symbol='VZ'
df=fs.get_price(symbol,start_date='2020-01-01',end_date='2020-12-31')
fs.draw_chart(df,right=symbol)
같은 방법으로 코드를 입력했을 때, 아래와 같은 그래프를 도시하여준다.
이때, dataframe에다가 rsi()함수를 담아서 실행시키고 draw_chart()함수를 통해 차트를 그려본다.
fs.rsi(df,w=14)
fs.draw_chart(df,left='rsi',right=symbol)
이제, 전략을 수행해보자.
fs.indicator_to_signal(df,factor='rsi',buy=30,sell=70)
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
1행은 전략 시그널을 생성, 2행에서는 position을 산출, 3행에서 거래비용 0.1%로 수익률 산출, 4행에서는 성과 산출 (무위험 이자율 1%), 5행에서는 수익률과 포지션을 차트로 그린 것이다.
1) 연평균수익률 7.22%
2) 누적수익률 6.8% (거래 기간이 1년이 안되기 때문)
3) 성공과 실패를 모두 합산한 평균 수익률은 4.17%
4) 가만히 가지고 있었으면 -2.86%
5) 거래 횟수 2회
6) 성공횟수 1회
7) 성공확률 50%
8) 거래 기간 0.9yrs ,( RSI계산 기간인 14일 때문에 때문에 그렇다.)
9) 샤프비율 : 0.21 (별로인듯?)
10) MDD : -17.1%
11) 벤치마크 MDD : -18.2% (그냥 들고 있었을 때)
● Envelope - 이동평균선보다 n% 떨어져있는 가격대
특정 이동평균선 보다 5% 높은 가격, 5%낮은 가격 이런식으로 차트를 그려서 위쪽은 저항선, 아래쪽은 지지선 이렇게 부르는 것이다. 이동평균보다 가격이 얼마나 떨어져있는지 한눈에 알기 쉬운 형태이다.
주가가 이동평균선 위쪽의 n%선 위에서 매수, n%선 밑으로 주가가 내려오면 매도해서 모멘텀을 노리는 전략이 유효할 수도있고, 횡보하는 주식으로 전략을 짠다면, 오히려 주가가 이동평균선 아래쪽 n%선에 내려오면 매수, 아래쪽 n%선위 혹은 이동평균선 위까지 올라오면 매도해서 전략을 짤 수도 있다.
- 백테스트
보잉(BA)의 주가로 백테스팅을 진행해본다. 일단 기본적인 그래프랑 envelop그래프를 먼저 그려보자.
import finterstellar as fs
symbol = 'BA'
df = fs.get_price(symbol,start_date='2020-01-01',end_date='2020-12-31')
fs.draw_chart(df,right=symbol)
여기서 envelope()함수를 이용해본다. w는 이동평균 계산에 사용하는 기간이고, spread는 영역의 사이즈이다. (%로 지정이 됨)
fs.envelope(df,w=20,spread=0.1)
df.tail()
df에 평균값(center), 상단 값(ub, upper bound), 하단 값(lb, lower bound)이 추가된 것을 볼 수 있다. draw_band_chart()함수를 사용해서 그래프를 그려보면 아래와 같다. 함수사이에 band가 여태까지 사용했던 함수 형태와 다르다.
마찬가지로 signal을 체크해본다. 20일 이동평균선 10%선 위에 주가가 있으면 A 영역, 20일 이동평균선 10%선 위와 20일 이동평균선 사이에 있으면 B영역 이라고 했을때, A에서는 buy, B에서는 sell하는 전략을 따라가보자.
fs.band_to_signal(df,buy='A',sell='B')
그다음은 늘 하던대로 포지션을 체크하고 평가 및 운용 수익을 체크해보자.
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
1. 연평균 수익률은 38.38%
2. 누적 수익률은 35.33%
3. +와 - 수익률을 합친 평균 수익률은 5.58%
4. 가만히 들고 있었으면 -33.79%이고
5. 거래 횟수는 6번
6. 전략 성공횟수는 4번
7. 그래서 전략 성공확률은 66.67%
8. 전략 투자 기간은 0.9yrs
9. 샤프 비율은 0.88로 꽤나 우수하다.
10. MDD는 -26.49%
11. 가만히 들고있었을 때의 벤치마크 MDD는 -72.7%이다.
이번에는 이동평균선 10%보다 밑에 있을 때는 매수하고 이동평균선 10%위 ~ 이동평균선에 있을 때는 매도하는 저점매수 및 단기 고점매도 전략을 사용해보자.
fs.band_to_signal(df,buy='D',sell='B')
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
이동평균선 10%보다 밑에 있는 영역을 D, 이동평균선~ 이동평균선 10%위의 영역은 B라고 한 것이고, 포지션 확인 이후 거래비용이 0.1%, 무위험 이자율이 1%일때, 거래 결과를 확인한 것이다.
연평균 수익률이 -22.1%가 나왔다. 볼 필요 없을 것 같다. 보잉은 성장주가 아닌데도 모멘텀을 타고 강한 매수세가 있을때 샀다가 파는게 더 효과적이었다. 여러 기간을 레퍼런스로 많이 백테스트 해봐야한다.
● 볼린저밴드 - 전통적인 단타 전략
위의 envelope는 이동평균선의 n%떨어져있는 구간을 기준으로 판단했다면, 이것은 표준편차를 기준으로 판단하는 것이다. BB(N,k)로 표시하는데, N일 짜리 이동평균선에 대해 표준편차의 k배를 밴드의 사이즈로 잡는 것이다. 정규분포의 '표준화'작업에서 아이디어를 얻은 전략이다. 표준편차가 곧 변동성이기 때문에 변동성 대비 어느 정도 수준에 위치해 있는지 표현하게 된다.
- 투자전략
마찬가지로 모멘텀 투자와 평균회귀하는 성향에 따라 전략이 달라진다. 평균회귀 성향이 있는 종목은 밴드하단매매를, 모멘텀이 있는 종목은 밴드 상단 매매를 진행하는데, 마찬가지로 표준편차 상단의 영역(저항선)을 A, 표준편차 상단선과 이동평균선 사이의 영역을 B, 이동평균선과 표준편차 하단선영역을 C, 표준편차 하단선 영역을 D라고 한다면
1) A에서 사서 B에서 매도하는 방법 (치고 올라갈 때 샀다가, 조금 추세가 진정되면 매도)
2) D에서 사서 B에서 매도하는 방법 (상대적 저점에 샀다가, 추세가 과매수 되었다 싶으면 매도)
이런셈이다.
- 백테스트
책에서 내 사랑 TSMC로 모멘텀 전략을 백테스트 해본다.
symbol='TSM'
df=fs.get_price(symbol,start_date='2020-01-01',end_date='2020-12-31')
fs.draw_chart(df,right=symbol)
이렇게 TSM에 대한 data를 얻었다. (후 저때 팔았어야 했는데~) 볼린저 밴드 함수는 bollinger()함수이다.
fs.bollinger(df,w=20,k=2)
df.tail()
볼린저밴드에 대한 값을 df에 추가해주고 마지막 5행에 대한 정보를 보는 함수로 확인을 했다. (tail()함수) 차트를 그려보면 아래와 같다.
fs.draw_band_chart(df)
이제 시그널 산출부터 백테스트까지 한 번에 추려보면
fs.band_to_signal(df,buy='D',sell='B')
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
와 무려 연평균 수익률이 9.12%인데, 벤치마크 수익률은 95.41%이다. (^^;; 이때 실제로 그래서 수익률을 놓쳤다.) 평균회귀 전략을 사용하면
fs.band_to_signal(df,buy='D',sell='B')
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
수익률이 조금 좋아졌으나, 존버를 이길 수 없는 전략이었다.
전략을 섞어쓰면 어땠을까?
df=fs.get_price(symbol, start_date='2020-01-01',end_date='2020-12-31')
df=fs.bollinger(df,w=20,k=1)
df['s1']=fs.band_to_signal(df,buy='A',sell='B')
df['s2']=fs.band_to_signal(df,buy='D',sell='B')
df리스트에 s1과 s2열을 만들어서 시그널을 각각 뽑아준다.
그럼 이렇게 s1과 s2열이 생긴다. 여기다가 finterstellar라이브러리인 fs.combine_signal_or()을 이용해서 매수 매도 시그널을 섞는다.
fs.combine_signal_or(df,'s1','s2')
그럼 이렇게 섞였는데, 여기서 df에 대한 성과를 확인해보자.
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
이런식으로 섞어서 전략을 썼을 때, 더 좋은 수익률을 보일 수 있다고 한다. 다만, 이런 저런 전략을 너무 많이 섞어쓰면 과최적화의 오류를 겪게 될 수도 있다고 저자는 경고한다.
● 스토캐스틱 - 마법의 지표
이건 나도 실제로 단기적으로 ISA계좌에서 많이 써먹는 방법이다. 지표의 개념은 N일간의 주가 범위중에 지금 주가가 어디있냐? 라는 건데, (현재가격- N일최저가) / ( N일 동안의 최고가 - 최저가) 이런식으로 산출된다.
이때, 그날 그날의 주가 수준은 잘 보여주지만, 너무 들쭉날쭉이 심해서 스토캐스틱 값의 이동평균을 활용한다. 이를 Slow K값이라고 하는데, 이 Slow K값을 한 번 더 이동평균 낸 값이 Slow D 값이라고 한다. 그래서 Sto(N, m, t)의 3가지 지표가 필요하다.
통상 14일 간의 가격 데이터로 3일간의 스토캐스틱 slow K와 3일간의 slow K의 이동평균인 slow D를 활용한다. 나는 14일 보다 한 발자국 앞서려고 9일 간의 가격 데이터를 활용하되, 9일 중의 최고 최저를 조금 더 넓게 보기 위해 5일 간의 스토캐스틱 slow K와 5일간의 slow K의 이동평균 slow D를 활용하는 편이다.
- 투자전략
1. 스토캐스틱이 20이하일 때 매수, 80이상일 때 매도 (과매도구간 매수, 과매수구간 매도)
2. Slow K - Slow D 가 양수면 상승 추세라고 판단해서 매수, 음수라면 매도 (모멘텀 전략)
- 백테스트
스토캐스틱은 고가와 저가 시가 종가가 다 필요하다. (시고저종) 그래서 get_price() 함수 대신 get_ohlc()를 사용한다.
symbol='DAL'
df=fs.get_ohlc(symbol, start_date='2020-01-01',end_date='2020-12-31')
df.tail()
Open(시가) High (고가) Low (저가) Close(종가)를 모두 불러왔다. 그리고 스토캐스틱 data도 가져온다.
fs.stochastic(df,symbol,n=14,m=3,t=3)
slow_k와 slow_d가 함께 계산되기 때문에 이제 트레이딩 시그널로 slow K 값을 기준으로 그래프를 그려보면 아래와 같다.
fs.draw_chart(df,left='slow_k',right=symbol)
빨간색이 주가 이고, 파란색이 slow K 값이다. 20이하 일때와 80이상 일때의 시그널을 만들고 백테스트 고고해보자.
fs.indicator_to_signal(df,factor='slow_k',buy=20,sell=80)
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
그냥 이건 종목을 잘못 고른 수준이다. 전략이 무의미하다. 모멘텀 전략으로 바꿔보자. indicator열에다가 slow_k와 slow_d를 뺀 열을 추가하는 형태이다.
df=fs.get_ohlc(symbol,start_date='2020-01-01',end_date='2020-12-31')
fs.stochastic(df,symbol,n=14,m=3,t=3)
df.tail()
df['indicator']=df['slow_k']-df['slow_d']
df.tail()
indicator값이 0보다 크면 매수, 0보다 작으면 매도하고 트레이딩 시그널을 만들어 백테스트하면
fs.indicator_to_signal(df, factor='indicator',buy=0,sell=0)
fs.position(df)
fs.evaluate(df,cost=0.001)
fs.performance(df,rf_rate=0.01)
fs.draw_trade_results(df)
와우 벤치마크 수익률이 -30.05%였는데, 연평균 수익률을 -2.96%로 엄청나게 아웃퍼폼했다. STO(14,3,3)뿐만 아니라 STO(5,3,3)등 여러가지 선으로 섞어서 전략을 짜면 더 개선된 전략이 나올 수도 있다.
1년간의 보조지표를 통한 단기 지표를 보면서 이게 참 맞을 때도 있고 안 맞을 때도 있구나 라는 생각이 많이 든다. 이 장을 공부해보니 횡보장에서는 약간의 도움은 되겠으나, 절대적인 알고리즘으로 단기 매매는 잘못된 결과를 낳을 확률이 많구나라는 것을 깨달았다. 어지간하면 잦은 매매는 하지 말아야겠다 가 느낀점이다.
'미래걱정 > 주식' 카테고리의 다른 글
가치주를 찾는 기술 (feat. PER Band Chart 핀터스텔라) - 미국주식으로 시작하는 슬기로운 퀀트투자 5장(1) (2) | 2023.02.27 |
---|---|
Finterstellar (핀터스텔라)를 활용한 3년간 S&P500에 대한 단기투자 백테스팅 (0) | 2023.02.13 |
슬기로운 퀀트투자 - 2장 일단 해보자 편 (1) | 2023.02.08 |
파이썬으로 배우는 포트폴리오 2장 (투자와 자산배분) (2) | 2023.02.06 |
Python의 Quandl을 사용하여 간단한 백 테스팅 프로그램 의뢰하기, 50일 이동평균선 200일 이동평균선 골드크로스 데드크로스 전략 (w (0) | 2023.01.31 |
댓글