Table of Contents
在過去二十年,趨勢跟蹤交易法一直是許多基金經理人、專業交易者和全球宏觀對沖基金成功應用在全球期貨市場上進行盈利交易的一種交易策略。而近年來關於應用於股票的趨勢跟蹤策略,公開發表的研究及文章也如雨後春筍般增加。越來越多文獻顯示,應用在期貨市場的趨勢效應也同樣能在股票市場上取得類似的效果。
美國知名的趨勢跟蹤公司(TrendFollowing.com)創辦人邁克爾·W.卡沃爾(Michael W. Covel)認為:「在資本市場中,投資者總是渴望尋找漲跌間的『因果關係』,認為這樣能給自己的策略更多的安全感。但趨勢跟蹤策略卻反其道而行之,它不能預測市場的走向,而是更強調自律與精准的交易規則,還有嚴密的風控以應對市場的波動性。」
此外,Cole Wilcox, Eric Crittenden (2005)提及關於美股的趨勢跟蹤策略設置條件:
本文將根據以上觀點,設計出適配台股市場之趨勢跟蹤策略,供投資人參考。
*註:為增加入場機會,本文將「歷史上的最高收盤價」的入場條件改為「一年來最高收盤價」,讀者可自行斟酌是否參考。
本文使用 MacOS 以及 Jupyter Notebook 作為編輯器。
get_universe
函式取得股票池import os
import tejapi
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
tej_key = 'your key'
api_base = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = tej_key
os.environ['TEJAPI_BASE'] = api_base
start = '2019-04-01'
end = '2024-04-01'
get_universe()
抓取股票池from zipline.sources.TEJ_Api_Data import get_universe
pool = get_universe(start, end, mkt = ['TWSE', 'OTC'], stktp_e=['Common Stock-Foreign', 'Common Stock'])
tickers = ' '.join(pool)
start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')
os.environ['ticker'] = ''.join(tickers) +' IR0001'
os.environ['mdate'] = start+' '+end
!zipline ingest -b tquant
資料期間從 2019-04-01 至 2024-04-01,篩選全台股普通股作為股票池,並加入台股加權指數 IR0001,作為大盤比較。最後 ingest bundle
。
*註:由於存取全市櫃普通股,因此需抓取 1890 檔標的資料,資料量龐大,可能會有本地端記憶體不足或 api key
流量大量消耗的情況,請謹慎使用。
Pipeline
函式Custom Factor
函數CustomFactor
可以讓使用者自行設計所需的客製化因子,於本次案例我們用以處理:
AverageTrueRange
)N_Year_Highest
)Pipeline()
提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
vol_rank
)curr_price
)atr
)highest_price
)from zipline.pipeline import Pipeline
from zipline.TQresearch.tej_pipeline import run_pipeline
def make_pipeline():
volume = TWEquityPricing.volume.latest
curr_price = TWEquityPricing.close.latest
highest_price = N_Year_Highest(inputs = [TWEquityPricing.close])
sample = curr_price.__ge__(highest_price) # find stocks that >= highest price
vol_rank = volume.rank(mask = sample & curr_price.__ge__(10)) # rank the price that >= 10 and stocks that >= highest price
ATR = AverageTrueRange(inputs = [TWEquityPricing.high,
TWEquityPricing.low,
TWEquityPricing.close])
return Pipeline(
columns = {
'vol_rank':vol_rank,
'curr_price': curr_price,
'highest_price': highest_price,
'atr': ATR.ATR,
'sample': sample,
'volume':volume
},
screen = ~StaticAssets([benchmark_asset])
)
my_pipeline = run_pipeline(make_pipeline(), start_dt, end_dt)
my_pipeline
initialize
函式initialize()
函式用於定義交易開始前的每日交易環境,與此例中我們設置:
context.stop_loss
變數,將回測中的止損點紀錄from zipline.finance import slippage, commission
from zipline.api import *
def initialize(context):
set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.01))
set_commission(commission.Custom_TW_Commission(min_trade_cost = 20, discount = 1.0, tax = 0.003))
attach_pipeline(make_pipeline(), 'mystrats')
set_benchmark(symbol('IR0001'))
context.stop_loss = {}
handle_data
函式handle_data()
為構建策略的重要函式,會在回測開始後每天被呼叫,主要任務為設定交易策略、下單與紀錄交易資訊。
關於本策略的交易詳細規則請至:趨勢跟蹤.ipynb
def handle_data(context, data):
context.df = pipeline_output('mystrats')
space = 10 - len(context.portfolio.positions)
open_orders = get_open_orders()
if space != 0:
context.latest_target = pipeline_output('mystrats').query("sample == 1")
context.latest_target['vol_rank'] = pd.to_numeric(context.latest_target['vol_rank'], errors='coerce')
context.latest_target = context.latest_target.nlargest(space, 'vol_rank')
for symbol, atr in zip(context.latest_target.index, context.latest_target['atr']):
if symbol not in context.portfolio.positions and symbol not in open_orders and len(context.portfolio.positions) < 10 and atr is not None:
order_target_percent(symbol, 1 / 10 * 0.95)
context.stop_loss[f'{symbol}'] = data.current(symbol, 'close') - atr * 1.25
使用 run_algorithm()
執行上述設定的策略,設置交易期間為 start_dt
(2019-04-16) 到 end_dt
(2024-04-01),使用資料集 tquant,初始資金為一百萬元。其中輸出的 results
就是每日績效與交易的明細表。
from zipline import run_algorithm
start = '2019-04-16'
end = '2024-04-01'
start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')
results = run_algorithm(
start = start_dt,
end = end_dt,
initialize = initialize,
bundle = 'tquant',
analyze = analyze,
capital_base = 1e6,
handle_data = handle_data
)
import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results['benchmark_return']
pf.tears.create_full_tear_sheet(returns=returns,
positions=positions,
transactions=transactions,
benchmark_rets=benchmark_rets
)
可以看到趨勢跟蹤策略在這 57 個月中我們獲得了約 31.1% 的年化報酬,累積報酬 266.536%,除了在回測初期有小幅落後大盤的情況,整體獲利優於大盤;而在 β 值上,β 值在 0.79 代表該策略波動相對總體市場的波動不那麼敏感,反映出趨勢跟蹤本身不關注總體市場波動和不預測市場走向的核心概念,不過相對來說因為策略本身關注股價波動之特性,會鎖定股價突破新高的股票進行買進,直覺來說波動性在較高的檔位是比較難避免的,來到 26.1%。不過這個波動性雖高,但是主要是往多頭的方向波動,這個判斷我們從索提諾比率(Sortino Ratio)上可以得知(Sortino Ratio 越低則表示策略承擔單位風險的負報酬越高),而本次策略來到 1.17,算是理想的數字。
可以看到除了 2022 空頭年,其餘交易年都是處於正報酬。
在長短部位持有變化圖這邊,由於策略的設置,最多持有 10 檔的情況下,我們的最大持有股數大多也在 10 檔左右,最低來到 4 檔。
由於策略進行每日再平衡,換股較為頻繁,週轉率偶爾會有較高的情況發生,可能需要額外注意交易手續費問題。
本次策略應用 Cole Wilcox, Eric Crittenden (2005)之研究,驗證趨勢跟蹤在台股市場之可獲利性,有以下結果:
歡迎投資朋友參考。之後也會持續介紹使用 TEJ 資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
溫馨提醒,本次策略僅供參考,不代表任何商品或投資上的建議。
電子報訂閱