配對交易原理:假設我們持有一組股票X和Y,它們之間存在某種經濟 上的關聯。比如,兩家生產同樣產品的公司或者是兩家處于同一條供應鏈 上的公司,它是基于數學分析的策略的一個很好的范例。現在,讓我們來 虛擬一個例子。 In [8]: import matplotlib.pyplot as plt import numpy as np import pandas as pd import statsmodels from statsmodels.tsa.stattools import coint # 設置生成隨機數的種子 np.random.seed(107) import matplotlib.pyplot as plt
概念解釋:首先生成兩個虛擬的股票 假設股票X的收益率服從正態分布,每天隨機抽取一個值作為X的日收益率 。然后加總得到X的每天的價格。 In [9]: X_returns = np.random.normal(0, 1, 100) # 生成日收益率 # 把他們加總,然后把價格提高到一個合理的范圍 X = pd.Series(np.cumsum(X_returns), name='X') + 50 X.plot() Out[9]: 現在我們來生成Y。記住Y與X有很強的經濟關聯,因此Y的價格與X的 價格變化路徑與X的變化路徑應該非常相似。因此我們可以用這樣的方式 來生成Y:把X的價格提高一定值,然后再隨機加上一個干擾,并且這個干 擾服從正態分布。 In [3]: some_noise = np.random.normal(0, 1, 100) Y = X + 5 + some_noise Y.name = 'Y' pd.concat([X, Y], axis=1).plot() Out[3]: 定義:協整 現在我們已經生成了兩個協整序列。粗略得來講,協整是相關的一種 不同的形式。兩個協整序列間的差在均值上下來回波動。對于成功的配對 交易,還要求這個差值在時間向后推延的時候收斂到均值。換一種方式來 想,就是說協整序列不一定會收斂到同樣的值,但是它們都會收斂到某個 特定的值。 下面我們把他們之間的差畫出來來看看它具體是什么形狀。 In [4]: (Y-X).plot() # 畫出差值 plt.axhline((Y-X).mean(), color='red', linestyle='--') # 加上均 值 Out[4]: 檢驗協整關系 上述定義比較直觀,我們怎樣從統計的角度去檢驗兩組序列是否協整 呢?在'statsmodels.tsa.stattools'中有一個非常方便的檢定方法。我 們將會得到一個非常小的p-value,因為上述的X,Y序列已經最大限度的滿 足協整關系了。 In [16]: #計算這個協整檢驗的p-value,可以由此判斷這兩個時間序列在均值附近 是否穩定 score, pvalue, _ = coint(X,Y) print pvalue 2.75767345363e-16 相關 vs. 協整 相關和協整,雖然在理論上是相似,但也不盡相同。為了說明這一點 ,我們將給出相關但不協整的時間序列以及協整但不相關的序列。現在, 讓我們先來檢驗一下剛才生成的序列的相關性。 In [6]: X.corr(Y) Out[6]: 0.94970906463859306 跟預見的一樣,X,Y的相關性非常強。那么相關但不協整的序列是什 么樣的呢? 相關但不協整 一個簡單的例子就是兩個逐漸發散的序列。 In [8]: X_returns = np.random.normal(1, 1, 100) Y_returns = np.random.normal(2, 1, 100) X_diverging = pd.Series(np.cumsum(X_returns), name='X') Y_diverging = pd.Series(np.cumsum(Y_returns), name='Y') pd.concat([X_diverging, Y_diverging], axis=1).plot() Out[8]: In [10]: print '相關系數 ' + str(X_diverging.corr(Y_diverging)) score, pvalue, _ = coint(X_diverging,Y_diverging) print '協整檢定的p-value:' + str(pvalue) 相關系數 0.991426733118 協整檢定的p-value:0.52802926858 協整但不相關 一個簡單的例子就是一個服從正態分布的序列和一個方波 In [11]: Y2 = pd.Series(np.random.normal(0, 1, 1000), name='Y2') + 20 Y3 = Y2.copy() In [12]: # Y2 = Y2 + 10 Y3[0:100] = 30 Y3[100:200] = 10 Y3[200:300] = 30 Y3[300:400] = 10 Y3[400:500] = 30 Y3[500:600] = 10 Y3[600:700] = 30 Y3[700:800] = 10 Y3[800:900] = 30 Y3[900:1000] = 10 In [13]: Y2.plot() Y3.plot() plt.ylim([0, 40]) Out[13]:(0, 40) In [17]: # 相關系數接近于0 print '相關系數:'+ str(Y2.corr(Y3)) score, pvalue, _ = coint(Y2,Y3) print '協整檢定的p-value: ' + str(pvalue) 相關系數:-0.0296754336659 協整檢定的p-value: 0.0 可以確定,兩個序列的相關系數非常的低,但是P-value顯示它們滿足很 強的協整關系。 定義: 對沖頭寸 由于人們希望對沖市場行情不好的風險,因此通常在持有某種資產的 長頭寸的同時還會持有另一些資產的短頭寸。在股票價格上漲的時候,長 頭寸部分盈利;股票價格下跌的時候短頭寸部分盈利。因此,人們可以持 有部分股票的長頭寸,部分股票的短頭寸。這樣的話,如果整個市場的行 情都不好,持有的資產中仍然有一部分是盈利的,甚至有可能盈利的部分 會超過虧損的部分。在只有兩種股票的情形下,如果持有其中一個的長頭 寸和另一個的短頭寸,我們把這個叫對沖頭寸。 一個小技巧:在股票價格趨近時實現 由于兩只股票的價格可能相互接近也可能相互遠離,因此兩只股票的 價格差有大有小。配對交易的技巧就是保持在X,Y上保持一個對沖頭寸。 當兩只股票的價格都下跌時,我們既不盈利也不虧損,都上漲時也一樣。 我們從兩只股票價格回復到均值時的價格差中來盈利。為了實現這一點, 當X,Y的價格相差很大時,賣空Y,買入X。類似的,當價格相差很小時, 買入Y,賣空X。 尋找現實中存在協整關系的X,Y 最好的方法是先選一些可能存在協整關系的股票找起,然后進行統計檢定 。如果直接對所有股票進行統計檢定,可能會存在很大偏差。 下面提供了一種從一系列股票中尋找可能存在協整關系的股票組合的函數 。這個函數會返回協整關系檢定結果矩陣,一個p-value矩陣,并且每對 組合的p-value都小于0.05。 下面提供了一種從一系列股票中尋找可能存在協整關系的股票組合的 函數。這個函數會返回協整關系檢定結果矩陣,一個p-value矩陣,并且 每對組合的p-value都小于0.05。 In [11]: def find_cointegrated_pairs(securities_panel): n = len(securities_panel.minor_axis) score_matrix = np.zeros((n, n)) pvalue_matrix = np.ones((n, n)) keys = securities_panel.keys pairs = [] for i in range(n): for j in range(i+1, n): S1 = securities_panel.minor_xs (securities_panel.minor_axis[i]) S2 = securities_panel.minor_xs (securities_panel.minor_axis[j]) result = coint(S1, S2) score = result[0] pvalue = result[1] score_matrix[i, j] = score pvalue_matrix[i, j] = pvalue if pvalue <> pairs.append((securities_panel.minor_axis[i], securities_panel.minor_axis[j])) return score_matrix, pvalue_matrix, pairs
在汽車制造業中尋找存在協整關系的股票 下面我們來看幾家制造汽車的公司,看他們的股票是否有滿足協整關 系的。首先,確定一個我們要檢驗的股票列表。然后取出它們2014年的價 格數據。 以下是如何用get_price來得到將所有股票價格用一個pandas DataFrame顯示出來的例子。 In [3]: securities_panel.loc['price'].head(5) Out[3]: 用get_price得到特定股票價格的示例 In [5]: securities_dataframe=securities_panel['price'] securities_dataframe['000338.XSHE'].head(5) Out[5]: 2014-01-02 9.10 2014-01-03 8.94 2014-01-06 8.67 2014-01-07 8.60 2014-01-08 8.62 Name: 000338.XSHE, dtype: float64 現在我們來找哪些股票組合存在協整關系 In [13]: # 用一個熱能圖來顯示股票組合的協整檢定的p-value。為避免重復,只 畫出了對角線上方的熱能圖。對角線下方的都用1來代替 scores, pvalues, pairs = find_cointegrated_pairs (securities_panel) import seaborn seaborn.heatmap(pvalues, xticklabels=symbol_list, yticklabels=symbol_list, cmap='RdYlGn_r' , mask = (pvalues >= 0.95) ) print pairs [(u'000581.XSHE', u'000625.XSHE'), (u'000581.XSHE', u'000710.XSHE')] 看起來是('000581.XSHE', '000625.XSHE')和('000581.XSHE', '000710.XSHE')這兩對組合存在協整關系。現在我們以('000581.XSHE', '000625.XSHE')為例進一步研究它們的價格關系以確保檢定結果沒有錯誤 。 In [14]: S1 = securities_panel.loc['price']['000581.XSHE'] S2 = securities_panel.loc['price']['000625.XSHE'] In [16]: score, pvalue, _ = coint(S1, S2) pvalue Out[16]: 0.028668177055257312 把兩只股票的差價畫出來 In [18]: diff_series = S1 - S2 diff_series.plot() plt.axhline(diff_series.mean(), color='black') Out[18]: 差價本身在并沒有很重要的統計意義。用z-score來將它正態化會更 能說明問題。 In [20]: def zscore(series): return (series - series.mean()) / np.std(series) In [21]: zscore(diff_series).plot() plt.axhline(zscore(diff_series).mean(), color='black') plt.axhline(1.0, color='red', linestyle='--') plt.axhline(-1.0, color='green', linestyle='--') Out[21]: 簡易的策略: 在z-score低于-1.0的時候買入差價(即買入價格高的,賣出價格低 的) 在z-score低于高于1.0的時候賣空差價(即賣出價格高的,買入價格 低的) 在z-score接近0的時候退出所有頭寸 由于我們一開始已經定義了差價為S1-S2,買入差價的含義就是買入 一份S1,賣空一份S2,賣出差價就是買入一份S2,賣空一份S1。 以上的策略僅僅只是為了更好地闡釋配對交易的概念。在實際操作中 ,我們還需要計算S1,S2分別應該持有多少才能最大化盈利。 根據持續更新的統計數據來交易 定義:移動平均 移動平均是指過去的n對數據點在給定時間內的平均值。因此,在序 列中,對于最前面的n個數據點無法定義移動平均。 In [22]: # 獲取2只股票的價格差 difference = S1 - S2 difference.name = 'diff' # 獲取價格差10天的移動平均值 diff_mavg10 = pd.rolling_mean(difference, window=10) diff_mavg10.name = 'diff 10d mavg' # 獲取60天的移動平均值 diff_mavg60 = pd.rolling_mean(difference, window=60) diff_mavg60.name = 'diff 60d mavg' pd.concat([diff_mavg60, diff_mavg10], axis=1).plot() # pd.concat([diff_mavg60, diff_mavg10, difference], axis=1).plot() Out[22]: 我們可以用移動平均來計算給定時間內股票差價的z-score。這可以 告訴我們差價相對有多大,幫助我們判斷是否應該進入頭寸。現在,讓沃 恩來看看用移動平均計算的z-score。 In [23]: # 計算每60天的標準差 std_60 = pd.rolling_std(difference, window=60) std_60.name = 'std 60d' # 計算每一天的z-score zscore_60_10 = (diff_mavg10 - diff_mavg60)/std_60 zscore_60_10.name = 'z-score' zscore_60_10.plot() plt.axhline(0, color='black') plt.axhline(1.0, color='red', linestyle='--') plt.axhline(-1.0, color='green', linestyle='--') Out[23]: 單看z-socre并不能說明什么問題,我們把z-score和股票價格一起畫 出來看。 In [25]: two_stocks = securities_panel.loc['price'][['000581.XSHE', '000625.XSHE']] # 把股票價格都除以10,以方便觀察 pd.concat([two_stocks/10, zscore_60_10], axis=1).plot() Out[25]: |
|