【實戰應用】配對交易

TEJ 台灣經濟新報
TEJ-API 金融資料分析
11 min readJan 11, 2022

以 Python建立長榮海運與陽明海運的配對交易策略

Photo by Towfiqu barbhuiya on Unsplash

本文重點概要

  • 文章難度:★★★☆☆
  • 閱讀建議:配對交易是一種利用兩、三種具有對沖效果的資產組合,消除大部分市場風險,獲得市場中性的效果,並設定進出場條件,在資產的價差序列中產生進出場訊號,獲得既能避險又能盈利的策略。先前有介紹過定態性,而本文進一步將時間序列的理論應用在配對交易上,若想了解更多理論推導,可參考金融科技實戰

前言

當市場資金過度氾濫時,投資人為了規避系統性風險,常透過資產配置的方式同時建立多空部位,來消除大部份市場風險,獲得穩定報酬。而本文挑選長榮與陽明做為配對交易的股票對,並以單根檢定確定兩檔價差存在定態性,即確認長榮與陽明具有共整合關係,從而在價差偏離時,買進被低估的股票,賣出被高估的股票,並在價差修正時,反向平倉,賺取價差。

編輯環境及模組需求

本文使用 Windows OS 並以 Jupyter Notebook 作為編輯器

# 基本功能
import pandas as pd
import numpy as np
from arch.unitroot import ADF
import statsmodels.api as sm
# 繪圖
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
# TEJ API
import tejapi
tejapi.ApiConfig.api_key = 'Your Key'

資料庫使用

報酬率資訊表:上市(櫃)股價報酬(日)-資料代碼為(TWN/EWPRCD2)。

資料處理

從 TEJ資料庫匯入長榮與陽明的股價報酬率。

# 匯入資料
stock = tejapi.get('TWN/EWPRCD2',
coid = ['2603','2609'],
mdate= {'gte': '2019-06-01','lte':'2021-06-30'},
opts={'columns':['coid','mdate','roia']},
chinese_column_name=True,paginate=True)
stock = stock.pivot(index='日期', columns='證券碼', values='日報酬率(%)')
stock.columns = ['2603 長榮','2609 陽明']
stock = stock * 0.01
stock.tail(6)

共整合檢定

Step 1. 股票日報酬率序列有定態性

從下圖看出兩檔日報酬率都在0附近上下波動,可以確定兩檔日報酬率序列存在定態性。

# 長榮與陽明 日報酬率的時序圖
fig = plt.figure(figsize = (15,8))
ax = fig.add_subplot()
ax.plot(stock['2603 長榮'] ,linewidth=2, alpha=1)
ax.plot(stock['2609 陽明'] ,linewidth=2, alpha=0.7)
ax.axhline(0,color = 'black')
ax.set_title('長榮與陽明 日報酬率的時序圖' ,fontsize=20 ,fontweight='bold')
ax.legend(['2603 長榮','2609 陽明'],loc='best')
ax.set_ylabel('報酬率', fontsize=12,rotation=0)
ax.grid(axis='y')

Step 2. 股票對的價差

先將回測期間分成形成期與交易期,我們在計算交易期的價差序列時,為了避免前視偏誤,我們使用形成期價差序列的線性迴歸而得的 alpha係數值與 beta係數值,計算交易期的價差序列。

# 價差
def CointegrationSpread(df,formStart,formEnd,tradeStart,tradeEnd):
formX = df[(df.index >= formStart) & (df.index <= formEnd)]['2603 長榮']
formY = df[(df.index >= formStart) & (df.index <= formEnd)]['2609 陽明']
tradeX = df[(df.index >= tradeStart) & (df.index <= tradeEnd)]['2603 長榮']
tradeY = df[(df.index >= tradeStart) & (df.index <= tradeEnd)]['2609 陽明']

results = sm.OLS(formY,sm.add_constant(formX)).fit()
spread = tradeY - results.params[0] - results.params[1] * tradeX
return spread
Spread_2020_10_12 = CointegrationSpread(stock,'2019-06-01','2020-06-30','2020-10-01','2020-12-31')
Spread_2021_01_03 = CointegrationSpread(stock,'2020-01-01','2020-12-31','2021-01-01','2021-03-30')
# 對兩檔股價的價差序列做定態性檢定
adfSpread = ADF(Spread_2021_10_12, trend='n')
print(adfSpread.summary().as_text())

由下圖可知在1%顯著性水平下,我們可以拒絕虛無假設,說明 2021_10_12價差序列是定態的,即長榮與陽明的日報酬率序列具有共整合關係。

mu = np.mean(Spread_2020_10_12)
sd = np.std(Spread_2020_10_12)

我們計算形成期價差序列的 μ均值與 σ標準差,獲得交易期進出場的條件門檻值。設定 μ±1.5σ和 μ±0.2σ為開倉與平倉的臨界值,而 μ±2.5σ.

。同時我們可以發現形成期價差序列都沒有觸及 μ±2.5σ臨界值。

建立配對交易策略

我們根據開倉平倉點制定交易策略,

  • 當價差上穿 μ+1.5σ時,做空配對股票,反向建倉(賣出陽明;買進長榮)。
  • 當價差下穿 μ+0.2σ時,做多配對股票,反向平倉。
  • 當價差上穿 μ−1.5σ時,做多配對股票,反向建倉(買進陽明;賣出長榮)。
  • 當價差上穿 μ−0.2σ時,做空配對股票,反向平倉。
  • 當價差突破 μ±2.5σ時,即時平倉。
Spread_2021_01_03 = Spread_2021_01_03.to_frame()
Spread_2021_01_03.columns = ['價差']
Spread_2021_01_03['開倉平倉區間'] = \
pd.cut(Spread_2021_01_03['價差'] ,
(float('-inf') ,mu-2.5*sd ,mu-1.5*sd ,mu-0.2*sd ,
mu+0.2*sd ,mu+1.5*sd ,mu+2.5*sd ,float('inf')) ,labels=False)-3
Spread_2021_01_03['交易訊號'] = \
np.select([(Spread_2021_01_03['開倉平倉區間'].shift() == 1) &
(Spread_2021_01_03['開倉平倉區間'] == 2),

(Spread_2021_01_03['開倉平倉區間'].shift() == 1) &
(Spread_2021_01_03['開倉平倉區間'] == 0),

(Spread_2021_01_03['開倉平倉區間'].shift() == 2) &
(Spread_2021_01_03['開倉平倉區間'] == 3),

(Spread_2021_01_03['開倉平倉區間'].shift() == -1) &
(Spread_2021_01_03['開倉平倉區間'] == -2),

(Spread_2021_01_03['開倉平倉區間'].shift() == -1) &
(Spread_2021_01_03['開倉平倉區間'] == 0),

(Spread_2021_01_03['開倉平倉區間'].shift() == -2) &
(Spread_2021_01_03['開倉平倉區間'] == -3)],

[-2,2,3,1,-1,-3],default = 0)
position = [Spread_2021_01_03['交易訊號'][0]]
ns = len(Spread_2021_01_03['交易訊號'])
Spread_2021_01_03['倉位情況'] = pd.Series(position,index=Spread_2021_01_03.index)
Spread_2021_01_03['倉位情況'] = Spread_2021_01_03['倉位情況'].shift() # 隔天開盤才進場
Spread_2021_01_03 = Spread_2021_01_03.join(stock)
Spread_2021_01_03['策略報酬率'] = \
np.select([Spread_2021_01_03['倉位情況'] == 1,
Spread_2021_01_03['倉位情況'] == 0,
Spread_2021_01_03['倉位情況'] == -1],
[Spread_2021_01_03['2609 陽明'] * -1 + Spread_2021_01_03['2603 長榮'] * 1,
0,
Spread_2021_01_03['2609 陽明'] * 1 + Spread_2021_01_03['2603 長榮'] * -1], default=np.nan)
Spread_2021_01_03['累積報酬率'] = (Spread_2021_01_03['策略報酬率'] + 1).cumprod() -1
Spread_2021_01_03.head(10)

我們完成上述策略,並將策略的累積報酬率與最大回檔呈現在下圖。

結論

由最大回檔圖可以發現有兩次的配對交易最大回檔都連續跌破8%,代表策略的停損機制做的不是太好,或是 μ均值與 σ標準差參數失靈。未來可以試著調降2.5σ的標準,或是以rolling的方式計算形成期價差序列的 μ均值與 σ標準差,因為海運股在2021年上半年的波動性極大,而交易期剛好落在2021年1月至3月,形成期則是2020年1月至12月。

本文僅供參考之用,並不構成要約、招攬或邀請、誘使、任何不論種類或形式之申述或訂立任何建議及推薦,讀者務請運用個人獨立思考能力,自行作出投資決定,如因相關建議招致損失,概與作者無涉。

完整程式碼

延伸閱讀

相關連結

給我們鼓勵
之後會持續分享更多財金資料庫的應用
如果你的覺得今天的文章不錯,可以幫我們在下面的 掌聲 icon 點 1下
如果覺得超讚,可以按住 掌聲 icon 不放直到 50 下
有任何想法歡迎點選 留言 icon和我們討論

--

--

TEJ 台灣經濟新報
TEJ-API 金融資料分析

TEJ 為台灣本土第一大財經資訊公司,成立於 1990 年,提供金融市場基本分析所需資訊,以及信用風險、法遵科技、資產評價、量化分析及 ESG 等解決方案及顧問服務。鑒於財務金融領域日趨多元與複雜,TEJ 結合實務與學術界的精英人才,致力於開發機器學習、人工智慧 AI 及自然語言處理 NLP 等新技術,持續提供創新服務