Table of Contents
什麼是行為財務學?是指投資人受到心理偏誤的影響而有不理性的行為,與現代經濟學理論的假設相左。當Daniel Kahneman(康納曼)和他的合作者Amos Tversky(特沃斯基)於2002年獲得諾貝爾獎時,他們被公認為推動了行為經濟學的發展。他們率先觀察到一個人類行為的現象:當人們面臨相等金額的損失和收益時,他們對於損失的痛苦感受比獲得收益時的快樂感覺更加強烈,這一現象被稱為「損失厭惡」。他們以及其他研究學者發現,人們需要比失去的金額高出至少一倍的收益,才能在心理上平衡對風險的承受能力。為了避免量化投資的過度理性,以及對損失波動的忽略,「停損點設置」在策略應用中是不可忽視的一環。
真實波動幅度均值(Average True Range,ATR)是一種衡量金融資產價格波動性的指標。它由 J. Welles Wilder 開發,旨在衡量特定時間段內資產價格的波動程度。ATR通常用作技術分析的工具,幫助交易者了解特定資產的波動情況,進而決定進場、出場和止損點。
ATR的計算基於「真實波動幅度」(True Range)。真實波幅代表特定時間內的最大價格變動範圍,通常由以下三個差值中的最大值來確定:
計算 ATR 的一般步驟如下:
ATR 能夠幫助交易者評估資產的波動情況,當 ATR 值較高時,表示該資產價格波動較大;而 ATR 值較低時,代表該資產價格較為穩定。
本文將以「布林通道 + ATR 損失規避策略」作為實驗組;以一般常見之「布林通道」策略作為對照組,觀察利用損失規避策略的實驗組能否獲得較好的績效表現。
本文使用 Windows 11 並以 Jupyter Lab 作為編輯器。
資料期間從 2022–01–01 至 2023–01–01,以台積電 (2330) 作為實例。
import os
import tejapi
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# set tej_key and base
tej_key = 'Your Key'
api_base = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = tej_key
os.environ['TEJAPI_BASE'] = api_base
# set date
start = '2020-01-01'
end = '2023-01-01'
os.environ['mdate'] = start + ' ' + end
os.environ['ticker'] = '2330'
!zipline ingest -b tquant
Pipeline()
提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
from zipline.data import bundles
from zipline.pipeline import Pipeline
from zipline.TQresearch.tej_pipeline import run_pipeline
from zipline.pipeline.data import EquityPricing, TWEquityPricing
from zipline.pipeline.factors import BollingerBands, TrueRange, CustomFactor
from zipline.utils.math_utils import nanmax
from numpy import dstack
start_time = pd.Timestamp(start, tz = 'UTC')
end_time = pd.Timestamp(end,tz = 'UTC')
bundle = bundles.load('tquant')
class AverageTrueRange(CustomFactor):
inputs = (
EquityPricing.high,
EquityPricing.low,
EquityPricing.close,
)
window_length = 20
outputs = ["TR", "SMA_ATR"]
def compute(self, today, assets, out, highs, lows, closes):
high_to_low = highs[1:] - lows[1:]
high_to_prev_close = abs(highs[1:] - closes[:-1])
low_to_prev_close = abs(lows[1:] - closes[:-1])
tr_current = nanmax(
dstack(
(
high_to_low,
high_to_prev_close,
low_to_prev_close,
)
),
2,
)
sma_atr_values = np.mean(tr_current, axis=0)
out.TR = tr_current[-1]
out.SMA_ATR = sma_atr_values
def make_pipeline():
ATR = AverageTrueRange(inputs = [TWEquityPricing.high,
TWEquityPricing.low,
TWEquityPricing.close])
perf = BollingerBands(inputs=[EquityPricing.close], window_length=20, k=2)
upper,middle,lower = perf.upper, perf.middle, perf.lower
curr_price = EquityPricing.close.latest
return Pipeline(
columns={
'SMA_ATR': ATR.SMA_ATR,
'upper': upper,
'middle': middle,
'lower': lower,
'curr_price': curr_price
},
)
initialize()
函式用於定義交易開始前的每日交易環境,於此例中我們設置:
from zipline.api import *
from zipline.finance import commission, slippage
def initialize(context):
set_slippage(slippage.VolumeShareSlippage())
set_commission(commission.PerShare(cost = 0.001425 + 0.003 / 2))
set_benchmark(symbol('2330'))
attach_pipeline(make_pipeline(), 'mystrategy')
context.last_buy_price = 0
context.stop_loss = 0
handle_data()
為構建交易策略的重要函式,會在回測開始後每天被呼叫,主要任務為設定交易策略、下單與紀錄交易資訊。
def handle_data(context, data):
out_dir = pipeline_output('mystrategy') # 取得每天 pipeline 的布林通道上中下軌 & ATR
for i in out_dir.index:
curr_price = out_dir.loc[i, 'curr_price']
upper = out_dir.loc[i, 'upper']
lower = out_dir.loc[i, 'lower']
atr = out_dir.loc[i, 'SMA_ATR']
cash_position = context.portfolio.cash # 記錄現金水位
stock_position = context.portfolio.positions[i].amount # 記錄股票部位
loss_stopped, buy, sell = False, False, False
record(price = curr_price, upper = upper, lower = lower, stop_loss = context.stop_loss, buy = buy, sell = sell, loss_stopped = loss_stopped)
# 收盤價 > 止損價的狀況:
if (curr_price > context.stop_loss) or (context.last_buy_price == 0):
# 若收盤價 <= 布林下軌,則買入或加碼
if (curr_price <= lower) and (cash_position >= curr_price * 1000):
order(i, 1000)
buy = True
record(buy = buy)
context.stop_loss = curr_price - (0.1 * atr)
# 若收盤價 >= 布林上軌,則出清部位
elif (curr_price >= upper) and (stock_position >= 1000):
order_target(i, 0)
sell = True
record(sell = sell)
else:
pass
else:
pass
# 若收盤價 <= 停損價,則出清部位
if (curr_price <= context.stop_loss) and (stock_position > 0):
order_target(i, 0)
sell = True
loss_stopped = True
record(sell = sell, loss_stopped = loss_stopped)
else:
pass
analyze()
主要用於回測後視覺化策略績效與風險,這裡我們以 matplotlib 繪製投組價值表並畫出台積電股價走勢、布林上下軌以及買賣時機點。
def analyze(context, perf):
fig = plt.figure()
ax1 = fig.add_subplot(211)
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel("Portfolio value (NTD)")
ax2 = fig.add_subplot(212)
ax2.set_ylabel("Stock Price (NTD)")
perf.price.plot(ax=ax2)
perf.upper.plot(ax=ax2)
perf.lower.plot(ax=ax2)
buy_status, sell_status = perf.buy, perf.sell
buy_status.fillna(False, inplace=True)
sell_status.fillna(False, inplace=True)
ax2.plot( # 繪製買入訊號
perf.index[perf.buy],
perf.loc[perf.buy, 'price'],
'^',
markersize=5,
color='red'
)
ax2.plot( # 繪製賣出訊號
perf.index[perf.sell],
perf.loc[perf.sell, 'price'],
'v',
markersize=5,
color='green'
)
plt.legend(loc=0)
plt.gcf().set_size_inches(18,8)
plt.show()
使用 run_algorithm()
執行上述設定的交易策略,設置交易期間為 start_time(2022-01-01) 到 end_time(2022-12-31),所使用資料集為 tquant,初始資金為 10,000,000 元。其中輸出的 results 就是每日績效與交易的明細表。
因本文利用實驗組與對照組來觀察設定止損點的效果,因此以下將分別呈現兩個組別的結果,以利我們進行止損效果的分析。
from zipline import run_algorithm
results = run_algorithm(
start = start_time,
end = end_time,
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e7,
handle_data = handle_data
)
results
將實驗組與對照組兩相比對之下,很明顯地我們可以發現實驗組的資產價值因為有設定止損點,所以僅有小幅的虧損,且整體資產有向上獲利的空間。反觀對照組的資產價值大部分的時間都在承受虧損,最大的虧損超過6%。
再來我們可以透過交易買賣點的圖表觀察實驗組止損的時機。可以看到3月時,我們的策略進行了兩次止損(綠色箭頭為賣出,紅色為買入),使虧損的幅度僅有 0.5%,且很好的規避了4~5 月的空頭走勢。另外,9月及10月也分別進行了一次止損,反觀未進行止損的對照組,在持續的加碼攤平之下,資產價值也逐漸縮水,雖然最後出場時有賺到波段價差,使資產回復初始的水位,但投資人也必須承受9至10月的下行風險。
進行績效評估前,我們需要先將results
中的資料細分為以下部分:
import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results['benchmark_return']
平均而言,有進行止損的實驗組夏普比率有 0.9 左右,而對照組僅有 0.1,且實驗組的夏普比率在回測期間皆大於 0,對照組則低至 – 0.1 左右
from pyfolio.plotting import plot_rolling_sharpe
plot_rolling_sharpe(returns, factor_returns = benchmark_rets)
從圖表中可以看到沒有設定止損的對照組通常會有較大的交易回撤,最大的回撤達到 7.14%,而實驗組的最大回撤則只有 2.8%,且除了最大回撤之外,其餘回撤都小於 1%
from pyfolio.plotting import show_worst_drawdown_periods
show_worst_drawdown_periods(returns, top=5)
本次實作為了達成「損失規避」的效果,我們將布林通道策略搭配真實波動幅度均值(ATR),以達到止損的目的,同時我們將沒有設定止損的布林策略當作對照組,用以凸顯ATR止損的成效。
從以上的分析結果,我們可以看到 ATR 的確幫助我們達成「損失規避」,尤其在空頭走勢中,止損點的設立讓我們有效的避開資產縮水的風險。搭配 TQuant Lab 中的 Pyfolio 績效評估工具來看,我們也可以發現損失規避策略幫助我們有效地將平均夏普比率從 0.1 提升到 0.9,意味著投資人在承受相同的風險下,能獲取較高的報酬。此外,損失規避策略也能大幅縮小交易期間的回撤,使投資人的資金不至於過度流失。
溫馨提醒,本次策略與標的僅供參考,不代表任何商品或投資上的建議。之後也會介紹使用TEJ資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
電子報訂閱