楊育晟(Peter Yang)

嗨, 我叫育晟, 部落格文章主題包含了程式設計、財務金融及投資...等等,內容多是記錄一些學習的過程和心得,任何想法都歡迎留言一起討論。



Email: ycy.tai@gmail.com
LinkedIn: Peter Yang
Github: ycytai

股票技術分析交易策略回測-均線策略與布林通道

前言

在投資的世界中存在著許多分析方法,例如基本面、技術面、籌碼面...,投資人也有自己偏好觀察的指標,然而實際上指標的運用,以及能夠達到的投資成效究竟如何,是需要經過數據檢驗的。

此時回測的重要性就出現了,所謂的回測就是將投資邏輯實現,觀察套用在過去的數據上能夠獲得多少利潤,而Python可以輕易地協助我們完成投資回測的工作。

本篇文章將回測兩個交易構想,觀察這兩項交易邏輯套用過去的走勢究竟能不能賺錢? 也提供程式碼讓一樣有回測需求的人有個建構的方向。

什麼是技術分析?

在開始實作之前,必須先帶到技術分析的介紹,大多投資策略都和技術分析的指標脫不了關係,因為技術分析中存在著數不清的指標可以參酌,而量化投資在決定進出點位時大多也都以特定的指標作為訊號。

所謂技術分析就是透過股票的價格、成交量...等數據建構的指標進行投資決策,例如比較常聽到的均線(moving average)、乖離率(bias)。

黃金交叉、死亡交叉

黃金交叉就是短天期均線向上穿過長天期均線,隱含著典型認為即將上漲的訊息,死亡交叉就是短天期均線向下穿過長天期均線,隱含著典型認為即將下跌的訊息。

接著以台積電為例,檢驗黃金交叉買,死亡交叉賣的投資方法管不管用。

1. 均線策略

讀取資料

首先先讀取資料,並將Date欄位設為索引值,以及改成日期的資料型態,並看一下資料內容,除了日期外,也有典型的股票價格檔案包含的開盤價、最高價、最低價、收盤價以及成交量。

import pandas as pd
df = pd.read_csv("2330.TW.csv", parse_dates=['Date'], index_col='Date')
df.head()

計算移動平均價

接著用rolling().mean()來計算股票的移動平均價,像rolling(5)的欄位就沒有前四天的資料,從第五天才開始有值,值為過去五天的平均,以此類推。

df['ma_5'] = df['Close'].rolling(5).mean()
df['ma_10'] = df['Close'].rolling(10).mean()
df.head(15)

取出需要的資料

再來把實際回測時會用到的欄位取出來,命名為working_df,名稱並無限制,依個人喜好即可,同時也用dropna()把因為計算移動平均的空值列給剔除。

working_df = df[['Close', 'ma_5', 'ma_10']].dropna()

回測設定

要開始回測前,為了仿真,要設定一下起始擁有的金額,以下設定為100萬,在買入股票時,以全額手續費(0.001425)計算,賣出時也是,並包含了證交稅(0.003)。

回測邏輯為:當黃金交叉出現時,在隔天的收盤價買入,當死亡交叉出現時,在隔天的收盤價賣出

# 回測初始設定
FEE_RATE = 0.001425
FEE_DISCOUNT = 0.28
TRADING_TAX = 0.003

initial_cash = 10**6
current_cash = initial_cash
total_shares = 0
asset_values = []
for i in range(1, len(working_df)):
 
    # t期的移動平均 和 t+1其的收盤價
    ma_5 = working_df['ma_5'][i-1]
    ma_10 = working_df['ma_10'][i-1]
    close = working_df['Close'][i]
 
    # 買入賣出的現金流
    buy_cashflow = close*1000*(1 + FEE_RATE*FEE_DISCOUNT)
    sell_cashflow = close*1000*(1 - FEE_RATE*FEE_DISCOUNT - TRADING_TAX)
 
    # 黃金交叉
    if ma_5 > ma_10 \
    and current_cash >= buy_cashflow:
 
        total_shares += 1000
        current_cash -= buy_cashflow
 
    # 死亡交叉
    if ma_5 < ma_10 \
    and total_shares >= 1000:
 
        total_shares -= 1000
        current_cash += sell_cashflow
 
    # 記錄每日資產變化
    total_asset = total_shares*close + current_cash
    asset_values.append(total_asset)
 
total_return = round((total_asset / initial_cash - 1)*100, 2)
print(f'Total Return:{total_return}%')

計算得的報酬率為 60.37%,買進持有好像簡單多了XD。

資產變動視覺化

把回測期間的資產變化紀錄出來,在2017-2019時,這策略並沒有賺錢,最大虧損出現在2019年初,而隨後台積電股價開始不斷上漲,策略雖然獲利,但賺到的部份仍佔漲幅的少數。

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize = (14,5))
ax.plot(working_df.index[1:], asset_values)

2. 布林通道策略(bollinger bands)

第二個要介紹的策略為布林通道策略,所謂的布林通道是由資產的移動平均價格標準差建構而成。

以下圖為例,陰影填滿的部分,上下緣形成一個通道的形狀,而上下緣的計算分別為移動平均加上正負兩倍標準差。

$$ LowerBound=MA(n, p, t)-2 \times STD(n,p,t) $$

$$ UpperBound=MA(n, p, t)+2 \times STD(n,p,t) $$

而典型的技術分析認為,當股價觸到布林通道的上緣時,即為賣出訊號,股價觸到下緣時即為買進訊號,接著也用Python來實際回測看看,這樣的策略表現如何

先計算布林通道上下緣

df['ma_20'] = df['Close'].rolling(20).mean()
df['std_20'] = df['Close'].rolling(20).std()
df['upper_bound'] = df['ma_20'] + 2*df['std_20']
df['lower_bound'] = df['ma_20'] - 2*df['std_20']

working_df = df[['Close', 'lower_bound', 'upper_bound']].dropna().copy()

實作回測

# 回測初始設定
initial_cash = 10**6
current_cash = initial_cash
total_shares = 0
asset_values = []
for i in range(1, len(working_df)):
 
    lower_bound = working_df['lower_bound'][i-1]
    upper_bound = working_df['upper_bound'][i-1]
    close_tm1 = working_df['Close'][i-1]
    close = working_df['Close'][i]
 
    # 紀錄買賣訊號的出現
    buy_signal = close_tm1 < lower_bound
    sell_signal = close_tm1 > upper_bound
 
    # 買入賣出的現金流
    buy_cashflow = close*1000*(1 + FEE_RATE*FEE_DISCOUNT)
    sell_cashflow = close*1000*(1 - FEE_RATE*FEE_DISCOUNT - TRADING_TAX)
 
    # 觸及布林通道上緣
    if (buy_signal) and (current_cash >= buy_cashflow):
 
        total_shares += 1000
        current_cash -= buy_cashflow
 
    # 觸及布林通道下緣
    if (sell_signal) and (total_shares >= 1000):
 
        total_shares -= 1000
        current_cash += sell_cashflow
 
    # 記錄每日資產變化
    total_asset = total_shares*close + current_cash
    asset_values.append(total_asset)
 
total_return = round((total_asset / initial_cash - 1)*100, 2)
print(f'Total Return:{total_return}%')

計算得的報酬率為 30.84%,雖然也是正報酬,但表現比剛剛的均線策略還要來得不理想,從下方的資產變化來看,就能發現變動集中在某些時間,大多時候資產是沒有太多變化的。

import matplotlib.pyplot as plt
 
fig, ax = plt.subplots(figsize = (14,5))
ax.plot(working_df.index[1:], asset_values)

主要是因為布林通道的上下緣以兩倍的標準差作為門檻,而要價格要持續頻繁的高於上緣或低於下緣是不合理的,因此交易次數來得較少。

不過實際上以布林通道作為交易策略時,會搭配其他的技術指標觀察,也會利用中間的均線做判斷,這邊的回測簡化的過程,主要想說明回測的作法,當學會寫程式後,就能將腦中的投資構想全部實際的驗證一遍,來看看是否可行

Tags:
# python
# finance
# backtest
# investing
# technical analysis
# moving average
# stock