五糧液與老白乾的配對交易策略研究
本文的研究對象是酒類行業的11隻股票,整體思路是在11隻股票中尋找協整性強的2隻股票,然後構造配對交易策略並進行回測,檢驗該測策略的效果。
Pairs Trading,即配對交易策略,其基本原理就是找出兩隻走勢相關的股票。這兩隻股票的價格差距從長期來看在一個固定的水平內波動,如果價差暫時性的超過或低於這個水平,就買多價格偏低的股票,賣空價格偏高的股票。等到價差恢復正常水平時,進行平倉操作,賺取這一過程中價差變化所產生的利潤。(摘自:【量化課堂】基於協整的搬磚策略)
【量化課堂】基於協整的搬磚策略 - JoinQuant量化課堂 - JoinQuant選取的股票
本文的選取的11隻股票如下所示:
協整性檢驗
首先,我們構造一個讀取股票價格並判斷協整關係的函數。該函數返回的兩個值分別為協整性檢驗的 p 值矩陣以及所有傳入的參數中協整性較強的股票對。 p 值越低,協整關係就越強;p 值低於 0.05 時,協整關係非常強。
利用如下代碼進行協整關係的檢驗,並用熱力圖來直觀呈現結果。
說明:顏色越紅的表示p值越小,則協整關係就越強。
import jqdataimport numpy as npimport pandas as pdimport statsmodels.api as smimport seaborn as snsimport matplotlib.pyplot as pltdef find_cointegrated_pairs(dataframe): n = dataframe.shape[1] pvalue_matrix = np.ones((n, n)) keys = dataframe.keys() pairs = [] for i in range(n): for j in range(n): stock1 = dataframe[keys[i]] stock2 = dataframe[keys[j]] result = sm.tsa.stattools.coint(stock1, stock2) pvalue = result[1] pvalue_matrix[i, j] = pvalue if pvalue < 0.05: pairs.append((keys[i], keys[j], pvalue)) return pvalue_matrix, pairsstock_list = ["600519.XSHG", "600809.XSHG", "002304.XSHE", "603369.XSHG", "600702.XSHG", "600779.XSHG","000858.XSHE", "000995.XSHE", "000799.XSHE", "600559.XSHG", "600197.XSHG"]prices_df = get_price(stock_list,start_date="2016-01-01",end_date="2017-01-01",fields="close")[close]pvalues, pairs = find_cointegrated_pairs(prices_df)sns.heatmap(1-pvalues, xticklabels=stock_list, yticklabels=stock_list, cmap=RdYlGn_r, mask = (pvalues == 1),annot=True)print (pairs)
結果如下圖所示:
從圖中可以看出,11隻酒類股票中有10對(涵蓋(a,b)與(b,a))具有顯著的協整性關係,10對股票分別是:600809—600779;002304—000799;600702—000858;600779—000858;000858—600559;002304—603369;600809—600779;600519—600197;600779—600559;000858—600559。
注1:按(x,y)順序排列
注2:coint(a,b)不等於coint(b,a)。
這裡選取「000858—600559」即「五糧液—老白乾酒」這一對股票進行研究。
價格走勢
利用如下代碼呈現五糧液和老白乾酒這兩隻股票的價格走勢。
stock_df1 = prices_df["000858.XSHE"]stock_df2 = prices_df["600559.XSHG"]plt.plot(stock_df1); plt.plot(stock_df2)plt.xlabel("Time"); plt.ylabel("Price")plt.legend(["000858.XSHE", "600559.XSHG"],loc=best)
OLS回歸
然後,對兩隻股票的價格進行OLS線性回歸。
x=stock_df1y=stock_df2 X=sm.add_constant(x)result=(sm.OLS(y,X)).fit()print(result.summary())
由上圖可知,係數是0.5586,下面呈現的是數據和擬合線。
fig, ax = plt.subplots(figsize=(8,6))ax.plot(x, y, o, label="price")ax.plot(x, result.fittedvalues, b, label="OLS")ax.legend(loc=upper left)
所以,假設老白乾酒的股價為y,五糧液的股價為x,擬合結果:
y=5.2179+0.5586*x
因此,y-0.5586*x 是平穩序列。
價差序列
於是,可按照上述比例,做出五糧液和老白乾酒的股價價差的平穩序列。
plot(stock_df2-0.5586*stock_df1);plt.axhline((stock_df2-0.5586*stock_df1).mean(), color="red", linestyle="--")plt.xlabel("Time"); plt.ylabel("Stationary Series")plt.legend(["Stationary Series", "Mean"])
圖中的紅線就是兩者股價價差的均值,可見雖然價差上下波動,但整體上都會回歸到均值附近。
何時買與賣
z-score 是對時間序列偏離其均值程度的衡量,表示時間序列偏離了其均值多少倍的標準差。一個序列在時間 t 的 z-score,是它在時間 t 的值,減去序列的均值,再除以序列的標準差後得到的值。代碼如下:
def zscore(series): return (series - series.mean()) / np.std(series)plot(zscore(stock_df2-0.5586*stock_df1))plt.axhline(zscore(stock_df2-0.5586*stock_df1).mean(), color="green")plt.axhline(1.0, color="red", linestyle="--")plt.axhline(-1.0, color="yellow", linestyle="--")plt.legend(["z-score", "mean", "+1", "-1"],loc=upper right)
當兩個序列的z-score突破1或者-1時,說明兩支股票的價差脫離了統計概念中的合理區間,如果它們的協整關係能夠保持,那麼它們的價差應該收斂。
因此,當 z-score 突破紅線時,說明五糧液的股價相對於老白乾酒被高估,所以買入1份老白乾酒並賣空0.5586份五糧液的股票,當 z-score 回歸於0時(圖中綠線)時清倉可獲利。如果 z-score 突破下方黃線時,說明五糧液的股價相對於老白乾酒被低估,所以買入1份五糧液的股票並賣空1.8份老白乾酒的股票。
在A股如何實現
由於A股市場是不允許直接賣空操作的,而融資融券又具有槓桿。因此退而求其次,至進行做多操作。
首先,初始狀態資金配置為50%的五糧液與50%的老白乾酒。設置z-score的檢測周期為120天(約半年的交易日)。
整體思路:每一天檢測下 z-score,如果 z-score >1,則調整為全倉老白乾酒;如果 z-score < -1,則全倉調整為五糧液;如果由 z-score 從兩端調整至 0 點,則調整為初始配置比例。
import numpyimport pandas as pddef initialize(context): set_params() set_variables() set_backtest()def set_params(): g.security1="600559.XSHG" g.security2="000858.XSHE" g.benchmark="000858.XSHE" g.regression_ratio=0.5586 g.p=0.5 g.q=0.5 g.test_days=120def set_variables(): g.state="empty"def set_backtest(): set_benchmark(g.benchmark) log.set_level("order","error") set_option("use_real_price", True) set_slippage(FixedSlippage(0.))def handle_data(context, data): new_state=get_signal() change_positions(new_state,context)def z_test(): prices1=np.array(attribute_history(g.security1, g.test_days, 1d, [close]).close) prices2=np.array(attribute_history(g.security2, g.test_days, 1d, [close]).close) stable_series=prices2-g.regression_ratio*prices1 series_mean=mean(stable_series) sigma=np.std(stable_series) diff=stable_series[-1]-series_mean return(diff/sigma)def get_signal(): z_score=z_test if z_score > 1: return(allbuy2) if z_score < -1: reutrn(allbuy1) if -1 <= z_score <= 1: if z_score >= 0: return(side1) else: return(side2)def change_positions(new_state,context): total_value=context.portfolio.portfolio_value if new_state == allbuy1: order_target(g.security2,0) order_value(g.security1,total_value) g.state=allbuy1 if new_state == allbuy2: order_target(g.security1,0) order_value(g.security2,total_value) g.state=allbuy2 if (g.state == allbuy1 and new_state == side2) or (g.state == allbuy2 and new_state == side1): order_target_value(g.security1, g.p*total_value) order_target_value(g.security2, g.q*total_value) order_target_value(g.security1, g.p*total_value) order_target_value(g.security2, g.q*total_value) g.state = even
在 JoinQuant 上運行回測後,結果如下:
以五糧液(000858.XSHE)為基準:
以老白乾酒(600559.XSHG)為基準:
可見,該策略比單獨購買老白乾酒的股票的收益率要來得高,但是不如略低於只購買五糧液的股票的收益率,因此本策略還有很大的改進空間。
結語
由於本人初次學習量化策略,本文作為我學習的一個記錄,有諸多不足之處望各位海涵。本文首先發在個人公眾號:ValueFinance,記錄自己學習內容的個人日誌類公眾號,感興趣者歡迎訂閱。
http://weixin.qq.com/r/oy_wqF-ENQJKrRvL93qx (二維碼自動識別)
參考資料
- 【量化課堂】基於協整的搬磚策略
- 【量化課堂】協整的直觀認識
- 我好像破解了聚寬擂台排第一的策略?!
推薦閱讀:
※【聚寬投資】求賢季
※【理論】量化並行效率:阿姆達爾定律
※2017 年你眼中最好的券商量化策略研報是哪篇?有哪些亮點?
※【信號】神策alpha No.2