MACD指標回測實戰

macd
Photo Credits: Unsplash

本文重點概要

  • 文章難度:★★☆☆☆
  • MACD指標回測與視覺化買賣點
  • 閱讀建議:本文針對市場上常用的MACD指標進行更貼近現實的回測,並以視覺化的方式觀察買賣點。而本文主要是提供讀者回測所需考慮的面向與架構,而非支持技術指標的實用性。若讀者欲了解其他常用的技術指標,可閱讀 技術分析簡介與回測,進而發想出有利可圖的交易策略!

前言

MACD的中文為平滑異同移動平均線,為一種判斷股價中長期趨勢的指標。當快線(DIF)由下而上穿越慢線(MACD)時,代表股價有上漲的動能存在;反之快線(DIF)向下跌破慢線(MACD)時,代表股價下跌的機率相對高。由於此類型的指標有明確的買賣訊號,所以非常適用利用回測來驗證該策略表現。

而一個有效的回測,除了有明確的買賣點之外,買賣股票的成本也必須考量到買賣訊號產生的時機、手續費與證交稅,甚至是最低消費20元的限制,計算報酬率時亦要考慮手中現金部位,才會最貼近實際採用該策略的表現!

編輯環境及模組需求

本文使用 Windows OS 並以 Jupyter Notebook 作為編輯器

#基本功能
import numpy as np
import pandas as pd
#繪圖
import plotly.graph_objects as go
from plotly import subplots
#TEJ
import tejapi
tejapi.ApiConfig.api_key = "Your Key"
tejapi.ApiConfig.ignoretz = True

資料庫使用

  • 免費資料庫 : 資料庫代碼 ‘ TRAIL/TAPRCD ’,包含上市櫃公司於2020年間的未調整股價

資料處理

Step 1. 股價資料撈取

stock_data = tejapi.get('TRAIL/TAPRCD',
            coid= '3481',
            mdate={'gte': '2020-01-01', 'lte': '2020-12-31'},
            opts={'columns': ['coid', 'mdate', 'open_d','close_d']},
            chinese_column_name=True,
            paginate=True)

本文以撈取以群創光電(3481)於2020年間股價做為示範,欄位選擇開盤價與收盤價,前者計算買賣價格與成本,後者用於計算買賣訊號

Step 2. 訊號撰寫

stock_data['12_ema'] = stock_data['收盤價(元)'].ewm(span = 12).mean()
stock_data['26_ema'] = stock_data['收盤價(元)'].ewm(span = 26).mean()
stock_data['dif'] = stock_data['12_ema']  - stock_data['26_ema']
stock_data['macd'] = stock_data['dif'].ewm(span = 9).mean()

接下來以收盤價計算MACD。首先利用 ewm().mean() 的方式計算12天與26天的指數移動平均線,兩者之間的差即為快線DIF,再以此快線計算9日指數移動平均,則可以得到慢線MACD

stock_data['買賣股數'] = 0
#如果黃金交叉,隔天開盤買進
stock_data['買賣股數'] = np.where((stock_data['dif'].shift(1)>stock_data['macd'].shift(1)) & (stock_data['dif'].shift(2)<stock_data['macd'].shift(2)), 1000, stock_data['買賣股數'])
#如果死亡交叉,隔天開盤賣出
stock_data['買賣股數'] = np.where((stock_data['dif'].shift(1)<stock_data['macd'].shift(1)) & (stock_data['dif'].shift(2)>stock_data['macd'].shift(2)), -1000, stock_data['買賣股數'])

有了慢線與快線之後,即可建立買賣股數訊號。當兩日前的DIF仍小於MACD線stock_data[‘dif’].shift(2)<stock_data[‘macd’].shift(2),但卻於昨日DIF大於MACD stock_data[‘dif’].shift(1)>stock_data[‘macd’].shift(1),代表昨日出現黃金交叉買點,而因為這個訊號於昨日收盤才能確定,因此於今日開盤才購買1000股,若不符合買點條件,則維持原欄位內容stock_data[‘買賣股數’]。同理,出現死亡交叉時,亦是隔日開盤時才賣出1000股。

Step 3. 計算交易成本

stock_data['手續費'] = stock_data['開盤價(元)']* abs(stock_data['買賣股數'])*0.001425
stock_data['手續費'] = np.where((stock_data['手續費']>0)&(stock_data['手續費'] < 20), 20, stock_data['手續費'])
stock_data['證交稅'] = np.where(stock_data['買賣股數']<0, stock_data['開盤價(元)']* abs(stock_data['買賣股數'])*0.003, 0)
stock_data['摩擦成本'] = (stock_data['手續費'] + stock_data['證交稅']).apply(np.floor)

摩擦成本包含了手續費(0.1425%)與證交稅(0.3%)。當買入股票時,所需負擔的是手續費,但要注意的是如果券商沒有提供相關優惠,則往往會有低消20元限制;而賣出時要負擔的是手續費與證交稅。

報酬率計算

stock_data['股票價值'] = stock_data['買賣股數'].cumsum() * stock_data['收盤價(元)']
stock_data['現金價值'] = 10000 - stock_data['摩擦成本'] + (stock_data['開盤價(元)']* -stock_data['買賣股數']).cumsum() 
stock_data['資產價值'] = stock_data['股票價值'] + stock_data['現金價值']

假設初始現金有10000元台幣,資產價值為股票部位價值與現金部位價值的加總。股票價值隨著股票持有數量、股價波動;現金價值則隨著買賣操作變動,買入股價時減少,賣出時增加,並考慮摩擦成本。

stock_data['當日價值變動(%)'] = (stock_data['資產價值']/stock_data['資產價值'].shift(1) - 1)*100
stock_data['累計報酬(%)'] = (stock_data['資產價值']/10000 - 1)*100

接著即可利用資產價值的每日變化,計算每日報酬率;也能以初始現金計算累計報酬。

視覺化

Step 1. 買賣點觀察(詳見完整程式碼)

可以看到MACD策略的確可以掌握部分波段,但在盤整時有反覆買賣的風險,仍需特別注意。

Step 2. 策略表現(詳見完整程式碼)

可以看到採用MACD策略,資產價值每日的波動在8%上下以內,若波動為0代表目前手中僅有現金部位。最後的累計報酬約為63%,優於同時期0050約30%左右報酬。

結論

讀者應該可以發現,當買賣點觸發過於頻繁時,則累積的摩擦成本不容小覷,導致表現結果可能還不如期初買入並持有。而再反覆調整初始資金後也能發現資產配置的重要性,因為當手中現金部位過大時,會拉低整體的報酬率,但相對地每日價值波動幅度也較低。其實如果要貼近現實的回測,也必須考慮持有期間的除權息,但本文為了搭配當時股價顯示買賣點,才選擇採用未調整的股價計算報酬率;若操作方向為做空的話,更需要考慮保證金問題,而本文首筆交易為買入,故皆為一買一賣操作。若讀者想回測更長的區間、使用調整後的股價,推薦到 TEJ E-Shop 挑選最適合的方案!

完整程式碼

延伸閱讀

相關連結

返回總覽頁
Procesing