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

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

(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, tushare 1.2)

有一位朋友很擅長炒股,聽說其資產已經達到了兩百多萬,我聽后對其敬佩得五體投地,遂虛心向其請教炒股之秘訣,他聽后,點了一根煙,深深地吸了一口,然后慢悠悠地告訴我,秘訣其實很簡單,你先準備一千萬,炒著炒著就能炒到兩百萬。。。我聽后狂噴鼻血。。。

雖然沒有取到真經,但我仍不死心,仍然覺得人工智能應該可以用于炒股,AI的能力都能夠輕松解決圍棋這一世界性難題,難道還不能打敗股票市場嗎?

下面我們用機器學習的方法來研究一下股票數據,由于股票數據之間沒有任何標記,故而這是一類比較典型的無監督學習問題。但在我們著手股票研究之前,需要了解一下什么是近鄰傳播算法。


1. 近鄰傳播算法簡介

近鄰傳播聚類算法(Affinity Propagation, AP)是2007年在Science雜志上提出的一種新的聚類算法,它根據N個數據點之間的相似度進行聚類,這些相似度可以是對稱的,即兩個數據點相互之間的相似度一樣(如歐式距離),也可以是不對稱的,即兩個數據點相互之間的相似度不等,這些相似度組成N*N的相似度矩陣S。

這種算法會同時考慮所有數據點都是潛在的代表點,通過結點之間的信息傳遞,最后得到高質量的聚類,這個信息的傳遞,是基于sum-product或者max-product的更新原則,在任意一個時刻,這個信息幅度都代表著近鄰的程度,也就是一個數據點選擇另一個數據點作為代表點有多靠譜,這也是近鄰傳播名字的由來。

近鄰傳播算法的示意圖

關于算法原理和公式推導,有很多很好地文章,比如python 實現 AP近鄰傳播聚類算法Affinity propagation 近鄰傳播算法,讀者可以閱讀這些文章進行深入研究。


2. 準備股票數據

2.1 從網上獲取股票數據

股票數據雖然可以從網上獲取,但是要想輕易地得到結構化的數據,還是要花費一番功夫的,幸好,我找到了一個很好地財經類python接口模塊--tushare,這個模塊可以快速的從網站上爬取股票數據,并可以輕松保存和做進一步的數據分析,用起來非常方便。

下面我先自定義了三個工具函數,用于輔助我從網上下載股票數據和對股票數據進行整理。如下代碼:

# 準備數據集,使用tushare來獲取股票數據
# 準備幾個函數,用來獲取數據
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

這個函數獲取單只股票的估計數據,其時間跨度為start到end,返回獲取到的股票數據DataFrame。當然,一次獲取一只股票的數據太慢了,下面這個函數我們可以一次獲取多只股票數據。

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指數的成分股作為研究對象

2.2 對股票數據進行規整

由于tushare模塊已經將股票數據進行了基本的規整,此處我們只需要將數據處理成我們項目所需要的樣子即可。

此處對股票數據的規整包括有幾個方面:

1,計算需要聚類的數據,此處我用收盤價減去開盤價作分析,即一天的漲跌幅度。或許用一天的漲幅%形式可能更合適。

2,由于上面的get_batch_k_df()函數獲取的批量股票數據都是將多個股票數據在縱向上合并而來,故而此處我們要將各種不同股票的漲跌幅度放在DataFrame的列上,以股票代碼為列名。

3,在pd.merge()過程中,由于有的股票在某些交易日停牌,所以沒有數據,這幾個交易日就被刪掉(因為后面的聚類算法中不允許存在NaN),所以相當于要選擇所有股票都有交易數據的日期,這個選擇相當于取股票數據的交集,最終得到很少一部分數據,數據量太少時,得到的聚類結果也沒有太多說服力。故而我的解決方法是,刪除一些交易日明顯很少的股票,不對其進行pd.merge(),最終得到603個交易日的有效數據,選取了41只股票,舍棄了9只停牌日太多的股票。

這三部分的規整過程我都集成到一個函數中實現,如下是這個函數的代碼:

# 數據規整函數,用于對獲取的df進行數據處理
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數據整合為不同的列,列名為股票代碼
    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,要不然會有很多日期由于股票停牌沒數據
    
    result_df.drop(['date'],axis=1,inplace=True)
    # 然后將股票數據DataFrame轉變為np.ndarray
    stock_dataset=np.array(result_df).astype(np.float64)
    # 數據歸一化,此處使用相關性而不是協方差的原因是在結構恢復時更高效
    stock_dataset/=np.std(stock_dataset,axis=0)
    return stock_dataset,result_df.columns.tolist()

函數定義好了之后,我們就可以獲取股票數據,并對其進行規整分析,如下所示:

# 上面準備了各種函數,下面開始準備數據集
# 我們此處分析上證50指數的成分股,看看這些股票有哪些特性
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') # 查看最近五年的數據
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個交易日的數據,其中有41只股票被選出。
# 其他的9只股票因為不滿足最小交易日的要求而被刪除。這603個交易日是所有41只股票都在交易,都沒有停牌的數據。
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']

--------------------------------------------完-------------------------------------

到此為止,股票數據也從網上下載下來了,我們也對其進行了數據處理,可以滿足后面聚類算法的要求了。

########################小**********結###############################

1,tushare一個非常好用的獲取股票數據,基金數據,區塊連數據等各種財經數據的模塊,強烈推薦。

2,此處我自定義了幾個函數,get_K_dataframe(), get_batch_K_df()和preprocess_data()都是具有一定通用性的,以后要獲取股票數據或者處理股票數據,可以直接搬用或在此基礎上稍微修改即可。

3,作為演示,此處我只獲取了上證50只股票的最近五年數據,并且刪除掉一些停牌太多的股票,得到了41只股票的共603個有效交易日數據。

#################################################################


3. 用近鄰傳播算法聚類股票數據

首先我們構建了協方差圖模型,從相關性中學習其圖結構

# 從相關性中學習其圖形結構
from sklearn.covariance import GraphLassoCV
edge_model=GraphLassoCV()
edge_model.fit(stock_dataset)

然后再構建近鄰傳播算法結構模型,并訓練LassoCV graph中的相關性數據

# 使用近鄰傳播算法構建模型,并訓練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: 北方稀土,洛陽鉬業
Cluster: 3----> stocks: 恒瑞醫藥,三安光電
Cluster: 4----> stocks: 山東黃金
Cluster: 5----> stocks: 貴州茅臺,青島海爾,伊利股份
Cluster: 6----> stocks: 中國聯通,大秦鐵路,中國鐵建,中國中鐵,中國建筑,中國中車,中國交建
Cluster: 7----> stocks: 中國平安,新華保險,中國太保,中國人壽
Cluster: 8----> stocks: 浦發銀行,民生銀行,招商銀行,上汽集團,興業銀行,北京銀行,農業銀行,交通銀行,工商銀行,光大銀行,中國銀行
Cluster: 9----> stocks: 中國石化,中國石油

--------------------------------------------完-------------------------------------

從結果中可以看出,這41只股票已經被劃分為10個簇群,從這個聚類結果中,我們也可以看到,比較類似的股票都被劃分到同一個簇群中,比如Cluster1中大部分都是證券公司,而Cluster6中都是“鐵公基”類股票,而Cluster8中都是銀行類的股票。這和我們普遍認為的概念分類的股票相吻合。

雖然此處我們進行了合理分類,但是我還將這種分類結果繪制到圖中,便于直觀感受他們的簇群距離,所以我此處自定義了一個函數visual_stock_relationship()專門來可視化聚類算法的結果。這個函數的代碼太長,此處我就不貼代碼了,可以參考我的github上代碼,得到的聚類結果為:

上證50股票聚類結果圖

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

1,這個圖形結合了本項目的股票數據,GraphLassoCV圖結構模型,近鄰傳播算法的分類結果,故而可以說是整個項目的結晶。

2,圖中每一個節點代表一只股票,旁邊有股票名稱,節點的顏色表示該股票所屬類別的種類,用節點顏色來區分股票所屬簇群。

3,GraphLassoCV圖結構模型中的稀疏逆協方差信息用節點之間的線條來表示,線條越粗,表示股票之間的關聯性越強。

4,股票在圖形中的位置是由2D嵌套算法來決定的,距離越遠,表示其相關性越弱,簇間距離越遠。

這個圖得來不易,花了我整整一天時間來做這個項目,汗,里面的各種股票數據處理,太讓我頭疼了,所以再來具體研究一下這張圖。

股票聚類結果圖

########################小**********結###############################

1,本項目僅僅使用股票的收盤價與開盤價的差值,就聚類得到了股票所屬類別的信息,看來聚類的確可以用于股票內在結構的分類。

2,本項目先用GraphLassoCV得到股票原始數據之間的相關性圖,然后再用近鄰傳播算法對GraphLassoCV的相關性進行聚類,這種方式和以前我們直接用數據集來訓練聚類算法不一樣。

3,使用其他股票數據,比如漲幅,成交量,或換手率,也許可以挖掘出更多有用的股票結構信息,從而為我們的股票投資帶來幫助。

4,股票市場風險太大,沒事還是好好工作,好好研究AI,奉勸各位一句:珍愛生命,遠離股市,切記,切記!!!

#################################################################


注:本部分代碼已經全部上傳到(我的github)上,歡迎下載。

參考資料:

1, Python機器學習經典實例,Prateek Joshi著,陶俊杰,陳小莉譯

2,scikit-learn官方文檔

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容