TQuant Lab 乖離率策略

本文重點概要

  • 文章難度:★☆☆☆☆
  • 以乖離率判斷進場、出場時機。
  • 本文改編自乖離率交易策略,以 TQuant Lab 回測平台撰寫交易策略並回測風險與績效。

前言

乖離率是常見的技術指標之一,使用當前的股價與N天的移動平均價進行比較,反映出當前股價相較於過去歷史是否過高或過低。普遍來說,當股價持續高過移動平均價稱為「正乖離」;反之持續低於移動平均價則稱為「負乖離」,因此當正負乖離持續擴大時,就會被解讀為市場正發生持續性的超買或是超跌的情況,進而作為進出場的判斷依據。但單純只用乖離率容易產生過多的交易訊號,因此我們額外加上過去N天的最高和最低價作為第二層濾網。

交易策略

實際策略如下:

當收盤價高於過去7天最高價,同時乖離率為負值時,隔天開盤價進場建倉。

當收盤價低於過去7天最低價,同時乖離率為正值時,隔天開盤價出場平倉。

編輯環境

本文使用MacOS並以jupyter作為編輯器。

import os
import pandas as pd
import numpy as np
import tejapi
import matplotlib.pyplot as plt

模組需求

資料導入階段,我們使用 os.environ 設置環境變數,分別設定:

  1. TEJAPI_BASE: 設定 tej api 網域名稱。
  2. TEJAPI_KEY: 為購買 T Quant Lab 隨附的 api key,用於驗證個人身分。
  3. mdate: 所欲抓取資料的時間範圍,格式為 “西元年份月份日期 西元年份月份日期”。
  4. ticker: 所欲抓取資料的股價代碼。

於本次案例我們抓取台積電資料,時間區間設定為 2005–07–02 到 2023–07–02 之間。

os.environ['TEJAPI_BASE'] = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = 'your_key'
os.environ['mdate'] = '20050702 20230702'
os.environ['ticker'] = '2330'
!zipline ingest -b tquant

導入所需套件

from zipline.api import (set_slippage,
set_commission,
set_benchmark,
attach_pipeline,
symbol,
pipeline_output,
record,
order,
order_target
)
from zipline.pipeline.filters import StaticSids
from zipline.finance import slippage, commission
from zipline import run_algorithm
from zipline.pipeline import CustomFactor, Pipeline
from zipline.pipeline.data import EquityPricing
from zipline.pipeline.factors import ExponentialWeightedMovingAverage

建立 Pipeline 函式

Pipeline() 提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:

  • 股價的 7 日指數移動平均
  • 過去 7 日的股價最高價 (自定義 factor 函式: NdaysMaxHigh)
  • 過去 7 日的股價最低價 (自定義 factor 函式: NdaysMinLow)
  • 當日收盤價
def make_pipeline():
ema = ExponentialWeightedMovingAverage(inputs = [EquityPricing.close],window_length = 7,decay_rate = 1/7)
high = NdaysMaxHigh(inputs = [EquityPricing.close], window_length = 8) # window_length 設定為 8,因為 factor 會包含當日價格。
low = NdaysMinLow(inputs = [EquityPricing.close], window_length = 8)
close = EquityPricing.close.latest
return Pipeline(
columns = {
'ema':ema,
'highesthigh':high,
'lowestlow':low,
'latest':close
}
)
class NdaysMaxHigh(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmax(data[:-2], axis=0)
class NdaysMinLow(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmin(data[:-2], axis=0)

建立 initialize 函式

inintialize 函式用於定義交易開始前的每日交易環境,與此例中我們設置:

  • 流動性滑價
  • 交易手續費
  • 買入持有台積電的報酬作為基準
  • 將 Pipeline 導入交易流程中
def initialize(context):
set_slippage(slippage.VolumeShareSlippage())
set_commission(commission.PerShare(cost=0.00285))
set_benchmark(symbol('2330'))
attach_pipeline(make_pipeline(), 'mystrategy')

建立 handle_date 函式

handle_data 函式用於處理每天的交易策略或行動,其中:

  • condition1: 當日收盤價大於過去 7 日最高價且產生正乖離時,產生賣出訊號。
  • condition2: 當日收盤價小於過去 7 日最低價且產生負乖離時,產生買入訊號。
def handle_data(context, data):

pipe = pipeline_output('mystrategy')

for i in pipe.index:
ema = pipe.loc[i, 'ema']
highesthigh = pipe.loc[i, 'highesthigh']
lowestlow = pipe.loc[i, 'lowestlow']
close = pipe.loc[i, 'latest']
bias = close - ema
residual_position = context.portfolio.positions[i].amount # 當日該資產的股數
condition1 = (close > highesthigh) and (bias > 0) and (residual_position > 0) # 賣出訊號
condition2 = (close < lowestlow) and (bias < 0) # 買入訊號

record( # 用以紀錄以下資訊至最終產出的 result 表格中
con1 = condition1,
con2 = condition2,
price = close,
ema = ema,
bias = bias,
highesthigh = highesthigh,
lowestlow = lowestlow
)

if condition1:
order_target(i, 0)
elif condition2:
order(i, 10)
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("Price (NTD)")
perf.price.plot(ax=ax2)
ax2.plot( # 繪製買入訊號
perf.index[perf.con2],
perf.loc[perf.con2, 'price'],
'^',
markersize=5,
color='red'
)
ax2.plot( # 繪製賣出訊號
perf.index[perf.con1],
perf.loc[perf.con1, 'price'],
'v',
markersize=5,
color='green'
)
plt.legend(loc=0)
plt.gcf().set_size_inches(18,8)
plt.show()

執行交易策略

使用 run_algorithm 執行上述所編撰的交易策略,設置交易期間為 2015-01-05 到 2022-07-02,所使用資料集為 tquant,初始資金為 10,000 元。其中輸出的 results 就是每日績效與交易的明細表。

results = run_algorithm(start = pd.Timestamp('20150106', tz='UTC'),
end = pd.Timestamp('20221125', tz='UTC'),
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e4,
handle_data = handle_data
)
每日績效
每日績效
投組資產價值、交易買賣點與交易結果明細
投組資產價值、交易買賣點與交易結果明細

績效評估與視覺化

緊接著我們使用 TQuant Lab 隨附的 Pyfolio 模組進行投組的績效與風險分析,首先我們使用 extract_rets_pos_txn_from_zipline() 計算報酬、部位與交易紀錄。

import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)

日報酬紀錄 (returns)

紀錄每天的投組報酬率。

每日投組報酬
每日投組報酬

持有部位紀錄 (positions)

  • Equity(0 [2330]): 台積電持有部位
  • Cash: 現金部位
持有部位紀錄
持有部位紀錄

交易紀錄 (transaction)

  • sid: 股票編號
  • symbol: 股票代碼
  • price: 交易價格
  • order_id: 交易單號
  • amount: 交易量
  • commission: 手續費
  • dt: 交易時間
  • txn_dollar: 交易總金額
交易紀錄
交易紀錄

製作績效總表

使用 show_perf_stats() 製作績效表,可以快速計算投資常用績效與風險指標。

import pyfolio as pf
pf.plotting.show_perf_stats(
returns,
benchmark_rets,
positions=positions,
transactions=transactions)
績效表
績效表

繪製基準 (benchmark) 與投組累積報酬率

使用 plot_rolling_returns() 繪製,本案例的基準為台積電買入持有。

benchmark_rets = results['benchmark_return'] 
pf.plotting.plot_rolling_returns(returns, factor_returns=benchmark_rets)
大盤與投組累積報酬率
大盤與投組累積報酬率

結語

本次介紹的乖離率策略,屬於均值回歸的交易策略之一,當市場呈現超跌(乖離率<0)的情況且收盤價高於過去一段期間的最高價位時,判斷股價將逐漸回歸移動平均價位,因此進場做多;反之,當股價呈現超買(乖離率>0)且收盤價低於過去一段期間的最低價位時,認為股價已經漲過頭且有下跌的趨勢時,將原本做多的倉位平倉。但要注意由於本策略擁有較高頻率的交易策略,在一來一往中容易被手續費和交易稅給吃掉大量的獲利。

乖離率是一種技術分析工具,不應該單獨用於做出交易決策。成功的交易策略通常結合多種指標和分析方法,並且需要謹慎的風險管理。此外,市場情況會不斷變化,所以投資者應該定期更新RSI值並綜合其他市場信息來做出決策。

溫馨提醒,本次策略與標的僅供參考,不代表任何商品或投資上的建議。之後也會介紹使用TEJ資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TEJ E-Shop 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。

完整程式碼

延伸閱讀

相關連結

返回總覽頁
Procesing