歡迎來到 程式交易實戰 的第五堂課,還記得上堂課實作的網格交易策略嗎?主要是以「定價」的角度建構買賣策略,而定期定額則是以「定時」的角度買進股票,不論股價的漲跌,每隔一段固定的時間就買進相同金額的股數。今天將介紹原來透過程式交易也可以讓你定期定額存好股!
讀完本篇文,您將學會...
- 熟悉經典策略:平均成本法 (Dollar Cost Averaging, DCA)
- 了解定期定額的實際回測績效
- 定期定額實單演練
上堂課實作了「定價」的網格交易策略,當股價漲到一定價格時就賣出,跌到一定價格時就買進,藉此在震盪的盤勢中獲取利潤。而平均成本法,也就是大家常聽到的定期定額則是「定時」買進某檔股票,若看好一家公司的長期趨勢,定期定額的分批操作方式可以有效降低平均持有成本,待股價上漲後會因平均成本已經降低且持有的股數較多而有更可觀的獲利。
定期定額優勢
- 上班族非常適合的投資方法,薪水是每個月撥款,持續穩定買進可以確保長期的持有成本達到相對低。
- 可以把定期定額想像成儲蓄的概念,長期來看與銀行利率或其他儲蓄型保單 相比報酬率有機會高出許多,若有資金上的需求,也更方便買賣換成現金!
- 不需要盯盤交易,克服人性的弱點。下圖為定期定額的加碼方法,做第一次買進後股價馬上就開始下跌,或許就會讓主觀交易者思考是否該停損或是不動作,但如果是定期定額就會非常有紀律的買進以降低成本,以此案例來看定期定額是有優勢的。
定期定額劣勢
- 前提假設是該標的長期趨勢向上才能有這樣的效果。若標的持續下跌,會使得平均成本反而較高,需要更多時間才有機會重新獲利。
- 獲利金額受限,若一開始就擁有一筆資金,那在等待時間買進的過程中,這些閒置資金的機會成本也是必須計算的,因為若將資金投入更多則有機會獲得更高的報酬。
- 通常定期定額的出場點是有資金需求時,若有資金需求時恰巧為股市低迷期,可能無法獲得統計上的平均報酬。
標的選擇
上述提及定期定額必須是長期趨勢向上的標的較能夠獲利,因此選擇標的就相當重要了。單一標的較容易因為公司政策以及獲利狀況等影響變數較大,較難預測單一個股的走勢,建議可考慮涵蓋多產業或是挑選指數型 ETF 來分散風險。
定期定額實作
看到這裡,相信大家都能理解定期定額的優劣了,接下來我們就直接透過富果 API 了解實驗結果及實際下單給大家看吧!
首先先取得 0050 近 10 年的歷史資料,並且不考慮除息狀況,程式碼如下:
# 載入相關套件
import matplotlib.pyplot as plt
from datetime import timedelta
import pandas as pd
import numpy as np
import requests
import datetime
import json
import time
key = "YOUR_TOKEN" # 您的行情 API Token
symbol = "0050" # 股票代碼
candles_list = []
for y in ['2012','2013','2014','2015','2016','2017','2018','2019','2020','2021','2022']:
start_date = y+"-01-01" # 選擇資料起始日期
end_date = y+"-12-31" # 選擇資料結束日期
# API URL
url = f"https://api.fugle.tw/marketdata/v0.3/candles?symbolId={str(symbol)}&apiToken={key}&from={start_date}&to={end_date}"
# 取得歷史資料
historical_data = requests.get(url).json()
# 將資料轉為 dataframe 方便回測
candles_list = candles_list + historical_data['data']
df = pd.DataFrame(candles_list).sort_values('date').set_index('date')
取得歷史資料後,我們將進行以下實驗!
開收盤分批買進更能分散風險
相信各券商已推出不少方便好用的定期定額或定期存股功能,但除了使用自動扣款的定期存股外,我們也可以善用 API 來幫我們更自由地擬定定期存股策略,例如前面是以收盤價作為買進的價格,那萬一交易的標的剛好有開低走高的特性豈不是買進成本相對高一些了呢?如果能夠開盤時買進 50%、收盤時也買進 50% 能夠將風險更分散,以下我們就來實驗看看這樣的買進方法結果是如何吧!
def run_backtest(buy_date, invest_amount):
# 紀錄買進紀錄
buy_record_list = []
date_list = []
for y in range(2012,2023,1):
for m in range(1,13,1):
date_list.append(str(y)+"-"+str(m).zfill(2)+"-"+str(buy_date).zfill(2))
for date in date_list:
target_date = date
if target_date in df.index:
# 開盤使用一半的資金買進的股數
buy_qty = (invest_amount/2)/df.loc[target_date,'open']
# 收盤使用剩於一半的資金買進的股數
buy_qty += (invest_amount/2)/df.loc[target_date,'close']
# 計算平均成本
avg_price = invest_amount / buy_qty
buy_record_list.append({"date":target_date, "buy_qty":buy_qty, 'price': avg_price })
else:
# 若為休假日,就延後買進
count = 0
while target_date not in df.index and count <30:
target_date = str(datetime.datetime.strptime(target_date, "%Y-%m-%d").date() + timedelta(days=1))
# 如果連續找了30天沒找到帶表示資料已經到底了
count = count + 1
# 如果順利找到
if count < 30:
# 開盤使用一半的資金買進的股數
buy_qty = (invest_amount/2)/df.loc[target_date,'open']
# 收盤使用剩於一半的資金買進的股數
buy_qty += (invest_amount/2)/df.loc[target_date,'close']
# 計算平均成本
avg_price = invest_amount / buy_qty
buy_record_list.append({"date":target_date, "buy_qty":buy_qty, 'price':avg_price })
buy_record_df = pd.DataFrame(buy_record_list).set_index('date')
# 計算累積持有股數
buy_record_df['cumsum'] = buy_record_df['buy_qty'].cumsum()
# 計算該筆成本
buy_record_df['cost'] =buy_record_df['buy_qty'] * buy_record_df['price']
# 計算累積成本
buy_record_df['cost_cusum'] =buy_record_df['cost'].cumsum()
# 計算持有股數市值
buy_record_df['market_value'] = buy_record_df['cumsum'] * buy_record_df['price']
# 計算報酬率
buy_record_df['return_rate'] = buy_record_df['market_value'] / buy_record_df['cost_cusum'] *100
buy_record_df['return_rate'].plot(label=buy_date, figsize=(20,8))
return round(buy_record_df.iloc[-1]['return_rate'],2)
# 查看每月 d 日的報酬率
for d in range(1,29):
rate = run_backtest(d, 5000)
print("買進日:"+str(d) +" 報酬:"+str(rate)+"%")
透過實驗結果發現,以 0050 為例月中的表現較佳,可能是因部分投資人或某些基金在月初或月底較常進行一些交易行為,因而造成股價有較大波動。讀者可以自行嘗試其他標的或也可以嘗試將資金分散到不同的扣款日看看是不是會有更好的效果哦!根據上述實驗結果,我們選定每月 5 日作為買進的時間來實作自動下單程式。