Table of Contents
過去有關股價動能的研究,研究發現在美國股票市場上,26 周的股價乖離率與股票的未來報酬率呈現正相關;Jegadeesh and Titman 發現美國股票市場存在動能效應-利用買進過去 6-12 個月報酬率最佳的股票,並同時賣出過去 6-12 個月報酬率最差的股票所建立的投資策略,可以獲得經濟與統計上顯著的超額報酬率。後續許多學者研究也發現,動能效應普遍存在國際股票市場與不同類別的資產當中。
而近年來主管機關對於股票市場的資訊揭露的不遺於力,以及對股票市場的漲跌幅逐步放寬,使台股市場的效率性迅速提高。因此,台股市場的股價動能與股票期望報酬率的關係值得進一步去檢驗。為了探討股價動能與股票期望報酬率之間的關係,學界與業界也發展許多衡量股票動能的指標,除了有利用過去累積報酬率所計算的動能的指標、也有應用波動度調整後股票累積報酬率、長短均線的乖離率、CAPM 殘差的累計值…等,本文將運用 TEJ 過去的研究結論:
為此本文將主要運用 JTMOM3 與 MAD 動能變量進行回測分析。
更多詳細內容請至:股價動能與股票期望報酬率的關係
本文使用 Mac 作業系統以及 Jupyter Notebook 作為編輯器。
import tejapi
import os
os.environ['TEJAPI_KEY'] = "your key"
os.environ['TEJAPI_BASE'] = "https://api.tej.com.tw"
start = '2018-02-01'
end = '2023-12-31'
os.environ['ticker'] ='2308 2311 2317 2324 2325 2327 2330 2347 2353 2354 2615 2618 2633 2801 2823 2880 3034 3037 3045 3231 3474 3481 3673 3697 3711 4904 4938 5854 5871 5876 5880 6239 6415 6505 6669 6770 8046 8454 9904 9910 IX0001'
os.environ['mdate'] = start+' '+end
!zipline ingest -b tquant
資料期間從 2018-02-01 至 2023–12–31,隨機挑選 40 檔股票,並加入台股加權指數 IX0001,作為大盤比較。
Custom Factor 可以讓使用者自行設計所需的客製化因子,於本次案例我們用以處理:
Momentum
,詳情請見 Custom Factors)from zipline.pipeline.filters import StaticAssets
class Momentum(CustomFactor):
inputs = [TWEquityPricing.close]
window_length = 63 # finding past n days' return
def compute(self, today, assets, out, close):
monthly_return_1 = ((close[21-1]-close[0])/close[0])*100
monthly_return_2 = ((close[42-1]-close[20])/close[0])*100
monthly_return_3 = ((close[63-1]-close[41])/close[0])*100
formation_return = (((monthly_return_1)+(monthly_return_2)+(monthly_return_3))/3).round(5) # 3 months average return
out[:] = formation_return
from numpy import average, abs as np_abs
from zipline.pipeline.factors.basic import _ExponentialWeightedFactor, exponential_weights
class WeightedMovingAbsDev(_ExponentialWeightedFactor):
def compute(self, today, assets, out, data, decay_rate):
weights = exponential_weights(len(data), decay_rate = 1)
mean = average(data, axis=0, weights=weights)
abs_deviation = average(np_abs(data - mean), axis=0, weights=weights)
out[:] = abs_deviation
Pipeline()
提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
WeightedMovingAbsDev
,詳情請見 Pipeline built-in factors)window_length
設置為 21 天,長均線 MAD 的 window_length
設置為 120 天。High
為 True
表示報酬率最佳的 8 檔股票,反之 Low
為 True
表示報酬率最差的 8 檔股票。(使用者可只抓取 40 * 5% = 各 2 支股票交易,不過建議擴大股票池,否則交易股數太少。在這邊先以各 8 支股票做計算。)start_dt, end_dt = pd.Timestamp(start, tz='utc'), pd.Timestamp(end, tz='utc')
bundle = bundles.load('tquant')
benchmark_asset = bundle.asset_finder.lookup_symbol('IX0001',as_of_date = None)
def make_pipeline():
mom = Momentum()
mad_short = WeightedMovingAbsDev(inputs = [TWEquityPricing.close], window_length = 21, decay_rate = 1)
mad_long = WeightedMovingAbsDev(inputs = [TWEquityPricing.close], window_length = 120, decay_rate = 1)
curr_price = TWEquityPricing.close.latest
return Pipeline(
columns = {
'curr_price': curr_price,
'Momentum': mom,
'High': mom.top(8),
'Low': mom.bottom(8),
'MAD_short': mad_short,
'MAD_long': mad_long,
},
screen = ~StaticAssets([benchmark_asset])
)
my_pipeline = run_pipeline(make_pipeline(), start_dt, end_dt)
my_pipeline.tail(20)
inintialize()
函式用於定義交易開始前的每日交易環境,與此例中我們設置:
from zipline.finance import slippage, commission
from zipline.api import *
from zipline.api import set_slippage, set_commission, set_benchmark, attach_pipeline, order, order_target, symbol, pipeline_output, record
def initialize(context):
set_slippage(slippage.VolumeShareSlippage())
set_commission(commission.PerShare(cost = 0.001425 + 0.003 / 2))
attach_pipeline(make_pipeline(), 'mystrats')
set_benchmark(symbol('IX0001'))
context.day_count = 0
context.high_list = []
context.low_list = []
context.history_high = []
context.history_low = []
handle_data()
為構建股價動能因子策略的重要函式,會在回測開始後每天被呼叫,主要任務為設定交易策略、下單與紀錄交易資訊。
關於本策略的交易詳細規則請至:動能因子.ipynb
# record month -> record 3 months' momentum
if (context.day_count == 1) and (High == True) and (len(context.high_list) < 45):
context.high_list.append(i)
if (context.day_count == 1) and (Low == True) and (len(context.low_list) < 45):
context.low_list.append(i)
# trade month (Long, Short) -> first day in trade month, context.high_list and context.low_list have values
if (context.day_count == 0) and (i in context.high_list) and (cash_position > curr_price * 2000):
order(i, 2000)
buy = True
record(buy = buy)
context.high_list = [x for x in context.high_list if x != i]
elif (context.day_count == 0) and (i in context.low_list) and (cash_position >= 0):
order(i, -2000)
buy = True
record(buy = buy)
context.low_list = [x for x in context.low_list if x != i]
# Exiting the position (clearing the position from the previous trading month on the 21st day of the recording month) -> MAD_short > MAD_long or MAD_short > MAD_long
if (21 <= context.day_count <= 63) and (MAD_short > MAD_long) and (stock_position > 0) and (i in context.history_high):
order_target(i, 0)
sell = True
record(sell = sell)
context.history_high = [x for x in context.history_high if x != i]
elif (21 <= context.day_count <= 63) and (MAD_short < MAD_long) and (i in context.history_low):
order_target(i, 0)
sell = True
record(sell = sell)
context.history_low = [x for x in context.history_low if x != i]
使用 run_algorithm()
執行上述設定的動能因子策略,設置交易期間為 start_dt
(2018-02-01) 到 end_dt
(2023-12-31),使用資料集 tquant,初始資金為一千萬元。其中輸出的 results
就是每日績效與交易的明細表。
from zipline import run_algorithm
results = run_algorithm(
start = start_dt,
end = end_dt,
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e7,
handle_data = handle_data
)
results
import pyfolio as pf
from pyfolio.utils import extract_rets_pos_txn_from_zipline
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results.benchmark_return
# Creating a Full Tear Sheet
pf.create_full_tear_sheet(returns, positions = positions, transactions = transactions,
benchmark_rets = benchmark_rets,
round_trips=False)
可以看到股價動能因子策略在這 68 個月中我們獲得了約 16.56% 的年化報酬,累積報酬 140.25%,獲利表現可說相當不俗;不過相對來說因為策略本身之特性,會有入場滯後性的可能,舉例來說,這個月表現較好的股票我們無法立刻跟進,而是要等一段時間才買進,因此錯過入場時機,這可能是導致 20 年 Q1 到 Q2 表現與大盤相悖的原因,不過相反來說,這種滯後性也可能為策略帶來更多超額報酬,例如在某板塊行情回溫時,由於之前的動能好,策略就已經買了,相當於提前佈局,所以推測出現如 21 年和 23 年後半大幅超越大盤的表現。不過關於滯後性的問題,我們可以搭配如 RSI (TQuant Lab RSI 均線策略,找出低檔反向操作)或是波動率相關的指標來彌補,可留待日後再進行更深入的探討。
長部位總金額較短部位多,但是短部位有更多的交易個股數。
本次策略應用股價動能與股票期望報酬率的關係之結論,並運用動能因子「之前表現好的股票,未來也會表現好,反之亦然」的觀念模擬回測,驗證「強者恆強、弱者恆弱」在台股之可獲利性,歡迎投資朋友參考。之後也會持續介紹使用 TEJ 資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
溫馨提醒,本次策略僅供參考,不代表任何商品或投資上的建議。
電子報訂閱