【火爐煉AI】機器學習026-股票數(shù)據(jù)聚類分析-近鄰傳播算法
(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2韧涨, tushare 1.2)
有一位朋友很擅長炒股展氓,聽說其資產已經達到了兩百多萬,我聽后對其敬佩得五體投地始锚,遂虛心向其請教炒股之秘訣瘪阁,他聽后撒遣,點了一根煙,深深地吸了一口管跺,然后慢悠悠地告訴我义黎,秘訣其實很簡單,你先準備一千萬豁跑,炒著炒著就能炒到兩百萬廉涕。。。我聽后狂噴鼻血狐蜕。宠纯。。
雖然沒有取到真經层释,但我仍不死心婆瓜,仍然覺得人工智能應該可以用于炒股,AI的能力都能夠輕松解決圍棋這一世界性難題贡羔,難道還不能打敗股票市場嗎廉白?
下面我們用機器學習的方法來研究一下股票數(shù)據(jù),由于股票數(shù)據(jù)之間沒有任何標記乖寒,故而這是一類比較典型的無監(jiān)督學習問題猴蹂。但在我們著手股票研究之前,需要了解一下什么是近鄰傳播算法宵统。
1. 近鄰傳播算法簡介
近鄰傳播聚類算法(Affinity Propagation, AP)是2007年在Science雜志上提出的一種新的聚類算法晕讲,它根據(jù)N個數(shù)據(jù)點之間的相似度進行聚類,這些相似度可以是對稱的马澈,即兩個數(shù)據(jù)點相互之間的相似度一樣(如歐式距離)瓢省,也可以是不對稱的,即兩個數(shù)據(jù)點相互之間的相似度不等痊班,這些相似度組成N*N的相似度矩陣S勤婚。
這種算法會同時考慮所有數(shù)據(jù)點都是潛在的代表點,通過結點之間的信息傳遞涤伐,最后得到高質量的聚類馒胆,這個信息的傳遞,是基于sum-product或者max-product的更新原則凝果,在任意一個時刻祝迂,這個信息幅度都代表著近鄰的程度,也就是一個數(shù)據(jù)點選擇另一個數(shù)據(jù)點作為代表點有多靠譜器净,這也是近鄰傳播名字的由來型雳。
關于算法原理和公式推導,有很多很好地文章山害,比如python 實現(xiàn) AP近鄰傳播聚類算法和Affinity propagation 近鄰傳播算法纠俭,讀者可以閱讀這些文章進行深入研究。
2. 準備股票數(shù)據(jù)
2.1 從網上獲取股票數(shù)據(jù)
股票數(shù)據(jù)雖然可以從網上獲取浪慌,但是要想輕易地得到結構化的數(shù)據(jù)冤荆,還是要花費一番功夫的,幸好权纤,我找到了一個很好地財經類python接口模塊--tushare钓简,這個模塊可以快速的從網站上爬取股票數(shù)據(jù)乌妒,并可以輕松保存和做進一步的數(shù)據(jù)分析,用起來非常方便涌庭。
下面我先自定義了三個工具函數(shù)芥被,用于輔助我從網上下載股票數(shù)據(jù)和對股票數(shù)據(jù)進行整理。如下代碼:
# 準備數(shù)據(jù)集坐榆,使用tushare來獲取股票數(shù)據(jù)
# 準備幾個函數(shù)拴魄,用來獲取數(shù)據(jù)
import tushare as ts
def get_K_dataframe(code,start,end):
'''get day-K data of code, from start date to end date
params:
code: stock code eg: 600123, 002743
start: start date, eg: 2016-10-01
end: end date, eg: 2016-10-31
return:
dataframe with columns [date, open, close, high, low]
'''
df=ts.get_k_data(code,start=start,end=end)
df.drop(['volume'],axis=1, inplace=True)
return df
這個函數(shù)獲取單只股票的估計數(shù)據(jù),其時間跨度為start到end,返回獲取到的股票數(shù)據(jù)DataFrame席镀。當然匹中,一次獲取一只股票的數(shù)據(jù)太慢了,下面這個函數(shù)我們可以一次獲取多只股票數(shù)據(jù)豪诲。
def get_batch_K_df(codes_list,start,end):
'''get batch stock K data'''
df=pd.DataFrame()
print('fetching data. pls wait...')
for code in codes_list:
# print('fetching K data of {}...'.format(code))
df=df.append(get_K_dataframe(code,start,end))
return df
此處我選擇上證50指數(shù)的成分股作為研究對象
2.2 對股票數(shù)據(jù)進行規(guī)整
由于tushare模塊已經將股票數(shù)據(jù)進行了基本的規(guī)整顶捷,此處我們只需要將數(shù)據(jù)處理成我們項目所需要的樣子即可。
此處對股票數(shù)據(jù)的規(guī)整包括有幾個方面:
1屎篱,計算需要聚類的數(shù)據(jù)服赎,此處我用收盤價減去開盤價作分析,即一天的漲跌幅度交播≈芈牵或許用一天的漲幅%形式可能更合適。
2秦士,由于上面的get_batch_k_df()函數(shù)獲取的批量股票數(shù)據(jù)都是將多個股票數(shù)據(jù)在縱向上合并而來缺厉,故而此處我們要將各種不同股票的漲跌幅度放在DataFrame的列上,以股票代碼為列名隧土。
3提针,在pd.merge()過程中,由于有的股票在某些交易日停牌曹傀,所以沒有數(shù)據(jù)辐脖,這幾個交易日就被刪掉(因為后面的聚類算法中不允許存在NaN),所以相當于要選擇所有股票都有交易數(shù)據(jù)的日期皆愉,這個選擇相當于取股票數(shù)據(jù)的交集揖曾,最終得到很少一部分數(shù)據(jù),數(shù)據(jù)量太少時亥啦,得到的聚類結果也沒有太多說服力。故而我的解決方法是练链,刪除一些交易日明顯很少的股票翔脱,不對其進行pd.merge(),最終得到603個交易日的有效數(shù)據(jù)媒鼓,選取了41只股票届吁,舍棄了9只停牌日太多的股票错妖。
這三部分的規(guī)整過程我都集成到一個函數(shù)中實現(xiàn),如下是這個函數(shù)的代碼:
# 數(shù)據(jù)規(guī)整函數(shù)疚沐,用于對獲取的df進行數(shù)據(jù)處理
def preprocess_data(stock_df,min_K_num=1000):
'''preprocess the stock data.
Notice: min_K_num: the minimum stock K number.
because some stocks was halt trading in this time period,
the some K data was missing.
if the K data number is less than min_K_num, the stock is discarded.'''
df=stock_df.copy()
df['diff']=df.close-df.open # 此處用收盤價與開盤價的差值做分析
df.drop(['open','close','high','low'],axis=1,inplace=True)
result_df=None
#下面一部分是將不同的股票diff數(shù)據(jù)整合為不同的列暂氯,列名為股票代碼
for name, group in df[['date','diff']].groupby(df.code):
if len(group.index)<min_K_num: continue
if result_df is None:
result_df=group.rename(columns={'diff':name})
else:
result_df=pd.merge(result_df,
group.rename(columns={'diff':name}),
on='date',how='inner') # 一定要inner,要不然會有很多日期由于股票停牌沒數(shù)據(jù)
result_df.drop(['date'],axis=1,inplace=True)
# 然后將股票數(shù)據(jù)DataFrame轉變?yōu)閚p.ndarray
stock_dataset=np.array(result_df).astype(np.float64)
# 數(shù)據(jù)歸一化亮蛔,此處使用相關性而不是協(xié)方差的原因是在結構恢復時更高效
stock_dataset/=np.std(stock_dataset,axis=0)
return stock_dataset,result_df.columns.tolist()
函數(shù)定義好了之后痴施,我們就可以獲取股票數(shù)據(jù),并對其進行規(guī)整分析究流,如下所示:
# 上面準備了各種函數(shù)辣吃,下面開始準備數(shù)據(jù)集
# 我們此處分析上證50指數(shù)的成分股,看看這些股票有哪些特性
sz50_df=ts.get_sz50s()
stock_list=sz50_df.code.tolist()
# print(stock_list) # 沒有問題
batch_K_data=get_batch_K_df(stock_list,start='2013-09-01',end='2018-09-01') # 查看最近五年的數(shù)據(jù)
print(batch_K_data.info())
-------------------------------------輸---------出--------------------------------
fetching data. pls wait...
<class 'pandas.core.frame.DataFrame'>
Int64Index: 56246 entries, 158 to 1356
Data columns (total 6 columns):
date 56246 non-null object
open 56246 non-null float64
close 56246 non-null float64
high 56246 non-null float64
low 56246 non-null float64
code 56246 non-null object
dtypes: float64(4), object(2)
memory usage: 3.0+ MB
None
--------------------------------------------完-------------------------------------
stock_dataset,selected_stocks=preprocess_data(batch_K_data,min_K_num=1100)
print(stock_dataset.shape) # (603, 41) 由此可以看出得到了603個交易日的數(shù)據(jù)芬探,其中有41只股票被選出神得。
# 其他的9只股票因為不滿足最小交易日的要求而被刪除。這603個交易日是所有41只股票都在交易偷仿,都沒有停牌的數(shù)據(jù)哩簿。
print(selected_stocks) # 這是實際使用的股票列表
-------------------------------------輸---------出--------------------------------
(603, 41)
['600000', '600016', '600019', '600028', '600029', '600030', '600036', '600048', '600050', '600104', '600111', '600276', '600340', '600519', '600547', '600585', '600690', '600703', '600887', '600999', '601006', '601088', '601166', '601169', '601186', '601288', '601318', '601328', '601336', '601390', '601398', '601601', '601628', '601668', '601688', '601766', '601800', '601818', '601857', '601988', '603993']
--------------------------------------------完-------------------------------------
到此為止,股票數(shù)據(jù)也從網上下載下來了酝静,我們也對其進行了數(shù)據(jù)處理节榜,可以滿足后面聚類算法的要求了。
########################小**********結###############################
1形入,tushare一個非常好用的獲取股票數(shù)據(jù)全跨,基金數(shù)據(jù),區(qū)塊連數(shù)據(jù)等各種財經數(shù)據(jù)的模塊亿遂,強烈推薦浓若。
2,此處我自定義了幾個函數(shù)蛇数,get_K_dataframe(), get_batch_K_df()和preprocess_data()都是具有一定通用性的挪钓,以后要獲取股票數(shù)據(jù)或者處理股票數(shù)據(jù),可以直接搬用或在此基礎上稍微修改即可耳舅。
3碌上,作為演示,此處我只獲取了上證50只股票的最近五年數(shù)據(jù)浦徊,并且刪除掉一些停牌太多的股票馏予,得到了41只股票的共603個有效交易日數(shù)據(jù)。
#################################################################
3. 用近鄰傳播算法聚類股票數(shù)據(jù)
首先我們構建了協(xié)方差圖模型盔性,從相關性中學習其圖結構
# 從相關性中學習其圖形結構
from sklearn.covariance import GraphLassoCV
edge_model=GraphLassoCV()
edge_model.fit(stock_dataset)
然后再構建近鄰傳播算法結構模型霞丧,并訓練LassoCV graph中的相關性數(shù)據(jù)
# 使用近鄰傳播算法構建模型,并訓練LassoCV graph
from sklearn.cluster import affinity_propagation
_,labels=affinity_propagation(edge_model.covariance_)
此處已經構建并訓練了該聚類算法模型冕香,但是怎么看結果了蛹尝?
如下代碼:
n_labels=max(labels)
# 對這41只股票進行了聚類后豫,labels里面是每只股票對應的類別標號
print('Stock Clusters: {}'.format(n_labels+1)) # 10,即得到10個類別
sz50_df2=sz50_df.set_index('code')
# print(sz50_df2)
for i in range(n_labels+1):
# print('Cluster: {}----> stocks: {}'.format(i,','.join(np.array(selected_stocks)[labels==i]))) # 這個只有股票代碼而不是股票名稱
# 下面打印出股票名稱突那,便于觀察
stocks=np.array(selected_stocks)[labels==i].tolist()
names=sz50_df2.loc[stocks,:].name.tolist()
print('Cluster: {}----> stocks: {}'.format(i,','.join(names)))
-------------------------------------輸---------出--------------------------------
Stock Clusters: 10
Cluster: 0----> stocks: 寶鋼股份,南方航空,華夏幸福,海螺水泥,中國神華
Cluster: 1----> stocks: 中信證券,保利地產,招商證券,華泰證券
Cluster: 2----> stocks: 北方稀土,洛陽鉬業(yè)
Cluster: 3----> stocks: 恒瑞醫(yī)藥,三安光電
Cluster: 4----> stocks: 山東黃金
Cluster: 5----> stocks: 貴州茅臺,青島海爾,伊利股份
Cluster: 6----> stocks: 中國聯(lián)通,大秦鐵路,中國鐵建,中國中鐵,中國建筑,中國中車,中國交建
Cluster: 7----> stocks: 中國平安,新華保險,中國太保,中國人壽
Cluster: 8----> stocks: 浦發(fā)銀行,民生銀行,招商銀行,上汽集團,興業(yè)銀行,北京銀行,農業(yè)銀行,交通銀行,工商銀行,光大銀行,中國銀行
Cluster: 9----> stocks: 中國石化,中國石油
--------------------------------------------完-------------------------------------
從結果中可以看出挫酿,這41只股票已經被劃分為10個簇群,從這個聚類結果中愕难,我們也可以看到早龟,比較類似的股票都被劃分到同一個簇群中,比如Cluster1中大部分都是證券公司务漩,而Cluster6中都是“鐵公基”類股票拄衰,而Cluster8中都是銀行類的股票。這和我們普遍認為的概念分類的股票相吻合饵骨。
雖然此處我們進行了合理分類翘悉,但是我還將這種分類結果繪制到圖中,便于直觀感受他們的簇群距離居触,所以我此處自定義了一個函數(shù)visual_stock_relationship()專門來可視化聚類算法的結果妖混。這個函數(shù)的代碼太長,此處我就不貼代碼了轮洋,可以參考我的github上代碼制市,得到的聚類結果為:
這個圖看起來一團糟,但是每一部分都代表不同的含義:
1弊予,這個圖形結合了本項目的股票數(shù)據(jù)祥楣,GraphLassoCV圖結構模型,近鄰傳播算法的分類結果汉柒,故而可以說是整個項目的結晶误褪。
2,圖中每一個節(jié)點代表一只股票碾褂,旁邊有股票名稱兽间,節(jié)點的顏色表示該股票所屬類別的種類,用節(jié)點顏色來區(qū)分股票所屬簇群正塌。
3嘀略,GraphLassoCV圖結構模型中的稀疏逆協(xié)方差信息用節(jié)點之間的線條來表示,線條越粗乓诽,表示股票之間的關聯(lián)性越強帜羊。
4,股票在圖形中的位置是由2D嵌套算法來決定的鸠天,距離越遠逮壁,表示其相關性越弱,簇間距離越遠。
這個圖得來不易窥淆,花了我整整一天時間來做這個項目,汗巍杈,里面的各種股票數(shù)據(jù)處理忧饭,太讓我頭疼了,所以再來具體研究一下這張圖筷畦。
########################小**********結###############################
1词裤,本項目僅僅使用股票的收盤價與開盤價的差值,就聚類得到了股票所屬類別的信息鳖宾,看來聚類的確可以用于股票內在結構的分類吼砂。
2,本項目先用GraphLassoCV得到股票原始數(shù)據(jù)之間的相關性圖鼎文,然后再用近鄰傳播算法對GraphLassoCV的相關性進行聚類渔肩,這種方式和以前我們直接用數(shù)據(jù)集來訓練聚類算法不一樣。
3拇惋,使用其他股票數(shù)據(jù)周偎,比如漲幅,成交量撑帖,或換手率蓉坎,也許可以挖掘出更多有用的股票結構信息,從而為我們的股票投資帶來幫助胡嘿。
4蛉艾,股票市場風險太大,沒事還是好好工作衷敌,好好研究AI勿侯,奉勸各位一句:珍愛生命,遠離股市逢享,切記罐监,切記!B髋馈弓柱!
#################################################################
注:本部分代碼已經全部上傳到(我的github)上,歡迎下載侧但。
參考資料:
1, Python機器學習經典實例矢空,Prateek Joshi著,陶俊杰禀横,陳小莉譯
2屁药,scikit-learn官方文檔