如何用Python和機器學習炒股賺錢?
相信很多人都想過讓人工智慧來幫你賺錢,但到底該如何做呢?瑞士日內瓦的一位金融數據顧問 Ga?tan Rickter 近日發表文章介紹了他利用 Python 和機器學習來幫助炒股的經驗,其最終成果的收益率跑贏了長期處於牛市的標準普爾 500 指數。雖然這篇文章並沒有將他的方法完全徹底公開,但已公開的內容或許能給我們帶來如何用人工智慧炒股的啟迪。
我終於跑贏了標準普爾 500 指數 10 個百分點!聽起來可能不是很多,但是當我們處理的是大量流動性很高的資本時,對沖基金的利潤就相當可觀。更激進的做法還能得到更高的回報。
這一切都始於我閱讀了 Gur Huberman 的一篇題為《Contagious Speculation and a Cure for Cancer: A Non-Event that Made Stock Prices Soar》的論文。該研究描述了一件發生在 1998 年的涉及到一家上市公司 EntreMed(當時股票代碼是 ENMD)的事件:
「星期天《紐約時報》上發表的一篇關於癌症治療新葯開發潛力的文章導致 EntreMed 的股價從周五收盤時的 12.063 飆升至 85,在周一收盤時接近 52。在接下來的三周,它的收盤價都在 30 以上。這股投資熱情也讓其它生物科技股得到了溢價。但是,這個癌症研究方面的可能突破在至少五個月前就已經被 Nature 期刊和各種流行的報紙報道過了,其中甚至包括《泰晤士報》!因此,僅僅是熱情的公眾關注就能引發股價的持續上漲,即便實際上並沒有出現真正的新信息。」
在研究者給出的許多有見地的觀察中,其中有一個總結很突出:
「(股價)運動可能會集中於有一些共同之處的股票上,但這些共同之處不一定要是經濟基礎。」
我就想,能不能基於通常所用的指標之外的其它指標來劃分股票。我開始在資料庫裡面挖掘,幾周之後我發現了一個,其包含了一個分數,描述了股票和元素周期表中的元素之間的「已知和隱藏關係」的強度。
我有計算基因組學的背景,這讓我想起了基因和它們的細胞信號網路之間的關係是如何地不為人所知。但是,當我們分析數據時,我們又會開始看到我們之前可能無法預測的新關係和相關性。
選擇出的涉及細胞可塑性、生長和分化的信號通路的基因的表達模式
和基因一樣,股票也會受到一個巨型網路的影響,其中各個因素之間都有或強或弱的隱藏關係。其中一些影響和關係是可以預測的。
我的一個目標是創建長的和短的股票聚類,我稱之為「籃子聚類(basket clusters)」,我可以將其用於對沖或單純地從中獲利。這需要使用一個無監督機器學習方法來創建股票的聚類,從而使這些聚類之間有或強或弱的關係。這些聚類將會翻倍作為我的公司可以交易的股票的「籃子(basket)」。
首先我下載了一個數據集:Public Company Hidden Relationship Discovery,這個數據集基於元素周期表中的元素和上市公司之間的關係。
然後我使用了 Python 和一些常用的機器學習工具——scikit-learn、numpy、pandas、matplotlib 和 seaborn,我開始了解我正在處理的數據集的分布形狀。為此我參考了一個題為《Principal Component Analysis with KMeans visuals》的 Kaggle Kernel:Principal Component Analysis with KMeans visuals
import numpy as npnimport pandas as pdnfrom sklearn.decomposition nimport PCAnfrom sklearn.cluster nimport KMeansnimport matplotlib.pyplot as pltnimport seaborn as sbnnnp.seterr(divide=ignore, invalid=ignore)n# Quick way to test just a few column featuresnn# stocks = pd.read_csv(supercolumns-elements-nasdaq-nyse-otcbb-general-UPDATE-2017-03-01.csv, usecols=range(1,16))nnstocks = pd.read_csv(supercolumns-elements-nasdaq-nyse-otcbb-general-UPDATE-2017-03-01.csv)nnprint(stocks.head())nnstr_list = []nfor colname, colvalue in stocks.iteritems(): nif type(colvalue[1]) == str:n str_list.append(colname)n# Get to the numeric columns by inversionnnnum_list = stocks.columns.difference(str_list)nnstocks_num = stocks[num_list]nnprint(stocks_num.head())n
輸出:簡單看看前面 5 行:
zack@twosigma-Dell-Precision-M3800:/home/zack/hedge_pool/baskets/hcluster$ ./hidden_relationships.pyn Symbol_update-2017-04-01 Hydrogen Helium Lithium Beryllium Boron n0 A 0.0 0.00000 0.0 0.0 0.0 n1 AA 0.0 0.00000 0.0 0.0 0.0 n2 AAAP 0.0 0.00461 0.0 0.0 0.0 n3 AAC 0.0 0.00081 0.0 0.0 0.0 n4 AACAY 0.0 0.00000 0.0 0.0 0.0 nn Carbon Nitrogen Oxygen Fluorine ... Fermium Mendelevium n0 0.006632 0.0 0.007576 0.0 ... 0.000000 0.079188 n1 0.000000 0.0 0.000000 0.0 ... 0.000000 0.000000 n2 0.000000 0.0 0.000000 0.0 ... 0.135962 0.098090 n3 0.000000 0.0 0.018409 0.0 ... 0.000000 0.000000 n4 0.000000 0.0 0.000000 0.0 ... 0.000000 0.000000 nn Nobelium Lawrencium Rutherfordium Dubnium Seaborgium Bohrium Hassium n0 0.197030 0.1990 0.1990 0.0 0.0 0.0 0.0 n1 0.000000 0.0000 0.0000 0.0 0.0 0.0 0.0 n2 0.244059 0.2465 0.2465 0.0 0.0 0.0 0.0 n3 0.000000 0.0000 0.0000 0.0 0.0 0.0 0.0 n4 0.000000 0.0000 0.0000 0.0 0.0 0.0 0.0 nn Meitnerium n0 0.0 n1 0.0 n2 0.0 n3 0.0 n4 0.0 nn[5 rows x 110 columns]n Actinium Aluminum Americium Antimony Argon Arsenic Astatine n0 0.000000 0.0 0.0 0.002379 0.047402 0.018913 0.0 n1 0.000000 0.0 0.0 0.000000 0.000000 0.000000 0.0 n2 0.004242 0.0 0.0 0.001299 0.000000 0.000000 0.0 n3 0.000986 0.0 0.0 0.003378 0.000000 0.000000 0.0 n4 0.000000 0.0 0.0 0.000000 0.000000 0.000000 0.0 nn Barium Berkelium Beryllium ... Tin Titanium Tungsten Uranium n0 0.0 0.000000 0.0 ... 0.0 0.002676 0.0 0.000000 n1 0.0 0.000000 0.0 ... 0.0 0.000000 0.0 0.000000 n2 0.0 0.141018 0.0 ... 0.0 0.000000 0.0 0.004226 n3 0.0 0.000000 0.0 ... 0.0 0.000000 0.0 0.004086 n4 0.0 0.000000 0.0 ... 0.0 0.000000 0.0 0.000000 nn Vanadium Xenon Ytterbium Yttrium Zinc Zirconium n0 0.000000 0.0 0.0 0.000000 0.000000 0.0 n1 0.000000 0.0 0.0 0.000000 0.000000 0.0 n2 0.002448 0.0 0.0 0.018806 0.008758 0.0 n3 0.001019 0.0 0.0 0.000000 0.007933 0.0 n4 0.000000 0.0 0.0 0.000000 0.000000 0.0 nn[5 rows x 109 columns]nzack@twosigma-Dell-Precision-M3800:/home/zack/hedge_pool/baskets/hcluster$n
概念特徵的皮爾遜相關性(Pearson Correlation)。在這裡案例中,是指來自元素周期表的礦物和元素:
stocks_num = stocks_num.fillna(value=0, axis=1)nnX = stocks_num.valuesnfrom sklearn.preprocessing import StandardScalernX_std = StandardScaler().fit_transform(X)nnf, ax = plt.subplots(figsize=(12, 10))nplt.title(Pearson Correlation of Concept Features (Elements & Minerals))n# Draw the heatmap using seabornnnsb.heatmap(stocks_num.astype(float).corr(),linewidths=0.25,vmax=1.0, square=True, cmap="YlGnBu", linecolor=black, annot=True)nsb.plt.show()n
輸出:(這個可視化例子是在前 16 個樣本上運行得到的)。看到元素周期表中的元素和上市公司關聯起來真的很有意思。在某種程度時,我想使用這些數據基於公司與相關元素或材料的相關性來預測其可能做出的突破。
測量「已解釋方差(Explained Variance)」和主成分分析(PCA)
已解釋方差=總方差-殘差方差(explained variance = total variance - residual variance)。應該值得關注的 PCA 投射組件的數量可以通過已解釋方差度量(Explained Variance Measure)來引導。Sebastian Raschka 的關於 PCA 的文章對此進行了很好的描述,參閱:Principal Component Analysis
# Calculating Eigenvectors and eigenvalues of Cov matirxnnmean_vec = np.mean(X_std, axis=0)ncov_mat = np.cov(X_std.T)neig_vals, eig_vecs = np.linalg.eig(cov_mat)n# Create a list of (eigenvalue, eigenvector) tuplesnneig_pairs = [ (np.abs(eig_vals[i]),eig_vecs[:,i]) for i in range(len(eig_vals))]n# Sort from high to lownneig_pairs.sort(key = lambda x: x[0], reverse= True)n# Calculation of Explained Variance from the eigenvaluestot = sum(eig_vals)nvar_exp = [(i/tot)*100 for i in sorted(eig_vals, reverse=True)] ncum_var_exp = np.cumsum(var_exp) n# Cumulative explained variance# Variances plotnnmax_cols = len(stocks.columns) - 1plt.figure(figsize=(10, 5))nplt.bar(range(max_cols), var_exp, alpha=0.3333, align=center, label=individual explained variance, color = g)nplt.step(range(max_cols), cum_var_exp, where=mid,label=cumulative explained variance)nplt.ylabel(Explained variance ratio)nplt.xlabel(Principal components)nplt.legend(loc=best)nplt.show()n
輸出:
從這個圖表中我們可以看到大量方差都來自於預測主成分的前 85%。這是個很高的數字,所以讓我們從低端的開始,先只建模少數幾個主成分。更多有關分析主成分合理數量的信息可參閱:Principal Component Analysis explained visually
使用 scikit-learn 的 PCA 模塊,讓我們設 n_components = 9。代碼的第二行調用了 fit_transform 方法,其可以使用標準化的電影數據 X_std 來擬合 PCA 模型並在該數據集上應用降維(dimensionality reduction)。
pca = PCA(n_components=9)nx_9d = pca.fit_transform(X_std)nnplt.figure(figsize = (9,7))nplt.scatter(x_9d[:,0],x_9d[:,1], c=goldenrod,alpha=0.5)nplt.ylim(-10,30)nplt.show()n
輸出:
這裡我們甚至沒有真正觀察到聚類的些微輪廓,所以我們很可能應該繼續調節 n_component 的值直到我們得到我們想要的結果。這就是數據科學與藝術(data science and art)中的「藝術」部分。
現在,我們來試試 K-均值,看看我們能不能在下一章節可視化任何明顯的聚類。
K-均值聚類(K-Means Clustering)
我們將使用 PCA 投射數據來實現一個簡單的 K-均值。
使用 scikit-learn 的 KMeans() 調用和 fit_predict 方法,我們可以計算聚類中心並為第一和第三個 PCA 投射預測聚類索引(以便了解我們是否可以觀察到任何合適的聚類)。然後我們可以定義我們自己的配色方案並繪製散點圖,代碼如下所示:
# Set a 3 KMeans clusteringnnkmeans = KMeans(n_clusters=3)n# Compute cluster centers and predict cluster indicesnnX_clustered = kmeans.fit_predict(x_9d)# Define our own color mapnnLABEL_COLOR_MAP = {0 : r,1 : g,2 : b}nlabel_color = [LABEL_COLOR_MAP[l] for l in X_clustered]n# Plot the scatter digramnnplt.figure(figsize = (7,7))nplt.scatter(x_9d[:,0],x_9d[:,2], c= label_color, alpha=0.5)nplt.show()n
輸出:
這個 K-均值散點圖看起來更有希望,好像我們簡單的聚類模型假設就是正確的一樣。我們可以通過這種顏色可視化方案觀察到 3 個可區分開的聚類。
當然,聚類和可視化數據集的方法還有很多,參考:https://goo.gl/kGy3ra
使用 seaborn 方便的 pairplot 函數,我可以以成對的方式在數據框中自動繪製所有的特徵。我們可以一個對一個地 pairplot 前面 3 個投射並可視化:
# Create a temp dataframe from our PCA projection data "x_9d"nndf = pd.DataFrame(x_9d)ndf = df[[0,1,2]]ndf[X_cluster] = X_clusteredn# Call Seaborns pairplot to visualize our KMeans clustering on the PCA projected datannsb.pairplot(df, hue=X_cluster, palette=Dark2, diag_kind=kde, size=1.85)nsb.plt.show()n
輸出:
構建籃子聚類(Basket Clusters)
你應該自己決定如何微調你的聚類。這方面沒有什麼萬靈藥,具體的方法取決於你操作的環境。在這個案例中是由隱藏關係所定義的股票和金融市場。
一旦你的聚類使你滿意了,你就可以設置分數閾值來控制特定的股票是否有資格進入一個聚類,然後你可以為一個給定的聚類提取股票,將它們作為籃子進行交易或使用這些籃子作為信號。你可以使用這種方法做的事情很大程度就看你自己的創造力以及你在使用深度學習變體來進行優化的水平,從而基於聚類或數據點的概念優化每個聚類的回報,比如 short interest 或 short float(公開市場中的可用股份)。
你可以注意到了這些聚類被用作籃子交易的方式一些有趣特徵。有時候標準普爾和一般市場會存在差異。這可以提供本質上基於「信息套利(information arbitrage)」的套利機會。一些聚類則和谷歌搜索趨勢相關。
看到聚類和材料及它們的供應鏈相關確實很有意思,正如這篇文章說的一樣:Zooming in on 10 materials and their supply chains - Fairphone
我僅僅使用該數據集操作了 Cobalt(鈷)、Copper(銅)、Gallium(鎵)和 Graphene(石墨烯)這幾個列標籤,只是為了看我是否可能發現從事這一領域或受到這一領域的風險的上市公司之間是否有任何隱藏的聯繫。這些籃子和標準普爾的回報進行了比較。
通過使用歷史價格數據(可直接在 Quantopian、Numerai、Quandl 或 Yahoo Finance 使用),然後你可以匯總價格數據來生成預計收益,其可使用 HighCharts 進行可視化:
我從該聚類中獲得的回報超過了標準普爾相當一部分,這意味著你每年的收益可以比標準普爾還多 10%(標準普爾近一年來的漲幅為 16%)。我還見過更加激進的方法可以凈掙超過 70%。現在我必須承認我還做了一些其它的事情,但因為我工作的本質,我必須將那些事情保持黑箱。但從我目前觀察到的情況來看,至少圍繞這種方法探索和包裝新的量化模型可以證明是非常值得的,而其唯一的缺點是它是一種不同類型的信號,你可以將其輸入其它系統的流程中。
生成賣空籃子聚類(short basket clusters)可能比生成買空籃子聚類(long basket clusters)更有利可圖。這種方法值得再寫一篇文章,最好是在下一個黑天鵝事件之前。
如果你使用機器學習,就可能在具有已知和隱藏關係的上市公司的寄生、共生和共情關係之上搶佔先機,這是很有趣而且可以盈利的。最後,一個人的盈利能力似乎完全關乎他在生成這些類別的數據時想出特徵標籤(即概念(concept))的強大組合的能力。
我在這類模型上的下一次迭代應該會包含一個用於自動生成特徵組合或獨特列表的單獨演算法。也許會基於近乎實時的事件,這可能會影響那些具有隻有配備了無監督學習演算法的人類才能預測的隱藏關係的股票組。
選自Medium 機器之心編譯
推薦閱讀:
※Python爬蟲聯想詞視頻和代碼
※[譯] Python 3.7 新特性
※Python數據分析及可視化實例之可視化圖表應用簡介
※Django學習筆記一:搭建簡易博客
※使用 Python 連接 Todoist 與 Pomotodo