【火爐煉AI】機器學習026-股票數(shù)據(jù)聚類分析-近鄰傳播算法

【火爐煉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上代碼制市,得到的聚類結果為:

上證50股票聚類結果圖

這個圖看起來一團糟,但是每一部分都代表不同的含義:

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官方文檔

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柏锄,隨后出現(xiàn)的幾起案子酿箭,更是在濱河造成了極大的恐慌复亏,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缭嫡,死亡現(xiàn)場離奇詭異缔御,居然都是意外死亡,警方通過查閱死者的電腦和手機妇蛀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門耕突,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人评架,你說我怎么就攤上這事眷茁。” “怎么了纵诞?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵上祈,是天一觀的道長。 經常有香客問我挣磨,道長雇逞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任茁裙,我火速辦了婚禮塘砸,結果婚禮上,老公的妹妹穿的比我還像新娘晤锥。我一直安慰自己掉蔬,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布矾瘾。 她就那樣靜靜地躺著女轿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壕翩。 梳的紋絲不亂的頭發(fā)上蛉迹,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音放妈,去河邊找鬼北救。 笑死,一個胖子當著我的面吹牛芜抒,可吹牛的內容都是我干的珍策。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼宅倒,長吁一口氣:“原來是場噩夢啊……” “哼攘宙!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蹭劈,失蹤者是張志新(化名)和其女友劉穎疗绣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铺韧,經...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡持痰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祟蚀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡割卖,死狀恐怖前酿,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情鹏溯,我是刑警寧澤罢维,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站丙挽,受9級特大地震影響肺孵,放射性物質發(fā)生泄漏。R本人自食惡果不足惜颜阐,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一平窘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凳怨,春花似錦瑰艘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至李剖,卻和暖如春芒率,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篙顺。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工偶芍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慰安。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓腋寨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親化焕。 傳聞我的和親對象是個殘疾皇子萄窜,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容