1. 程式人生 > 其它 >使用 pandas處理股票資料並作分析

使用 pandas處理股票資料並作分析

文/kamidox(簡書作者) 原文:http://www.jianshu.com/p/1f1d4952669c

pandas 是資料分析的瑞士軍刀。我們今天使用 pandas 來玩一下股票資料,看看能從資料裡得到哪些有意思的資訊。

pandas 教程

如果你熟悉 Python 的話,官網上的 10 Minutes to pandas (http://pandas.pydata.org/pandas-docs/stable/10min.html )可以讓你在短時間內瞭解 pandas 能幹什麼事以及是怎麼幹的。針對每個主題,都可以橫向查到大量的資料和例子。

如果你 Python 不熟,但又想用 pandas 玩轉資料分析的話,Python for Data Analysis

是本不錯的書。書裡作者使用美國新生兒的名字得出了一些很有意思的結論。還分析了 movielen 的電影評分資料。熟悉 SQL 的同學應該對這些分析會深有感觸,相信這些人用 SQL 寫出過這些分析過程類似的程式碼。這本書的缺點是有點囉嗦,如果你熟悉 Python 又想快速學習的話,看第二章就夠了。但這本書很適合不熟悉 Python 的人,書的最後一章還附了 Python 的教程,即如果只玩 pandas 的話,掌握這些 Python 知識就夠了,真夠貼心。而且本書的作者就是 pandas 的作者。

另外補充一點,最好使用 ipython 環境來玩轉資料分析。特別是 ipython notebook ,熟悉快捷鍵後,用起來會很順手。本文玩轉的股票資料就是使用 ipython notebook。

股票資料下載

搜尋 ghancn 可以免費下載 2009 年之前的 5 分鐘資料和 1 分鐘資料。坦白講,資料質量不高,裡面有不少錯誤。但不影響我們玩這些資料。資料是以年為單位分不同的資料夾儲存的。

我們先看一下某個股票的資料長什麼樣:

(提示: 由於平臺原因,文章程式碼及資料格式會有錯亂,如想詳細學習請翻看原文: http://www.jianshu.com/p/1f1d4952669c )

import pandas as pdimport numpy as np

names = ['date',      
         'time',      
         'opening_price',      
         'ceiling_price',     
         'floor_price',     
         'closing_price',   
         'volume',    
         'amount']
         # 讀取資料時,我們以日期為索引,並解析成日期格式

raw = pd.read_csv('raw/2008/SH600690.csv', names=names, header=None, index_col='date', parse_dates=True)
raw.head()
 time  opening_price  ceiling_price  floor_price  closing_price   volume    amount
date2008-01-02  09:35          22.50          22.63        22.50          22.51   2042.50   46047232008-01-02  09:40          22.51          22.51        22.29          22.37   1545.17   34605032008-01-02  09:45          22.39          22.62        22.38          22.62   1744.76   39214432008-01-02  09:50          22.60          23.00        22.60          22.95   5339.00   122259392008-01-02  09:55          22.98          23.20        22.89          23.20   12577.73  28947824

轉化為日交易資料

我們使用 2007 年和 2008 年的資料來作為示例。因為我們更關心是一些長期的趨勢,分鐘級別的交易資料太細了,我們轉換為日資料。

# 股票漲跌幅檢查,不能超過 10% ,過濾掉一些不合法的資料def _valid_price(g):
    return (((g.max() - g.min()) / g.min()) < 0.223).all()# 按照日期分組days = raw.groupby(level=0).agg(
    {'opening_price': lambda g: _valid_price(g) and g[0] or 0,     'ceiling_price': lambda g: _valid_price(g) and np.max(g) or 0,     'floor_price': lambda g: _valid_price(g) and np.min(g) or 0,     'closing_price': lambda g: _valid_price(g) and g[-1] or 0,     'volume': 'sum',     'amount': 'sum'})
days.head()
            floor_price opening_price   ceiling_price   volume      amount      closing_price
date2008-01-02  22.29       22.50           24.50           200809.34   476179680   24.032008-01-03  23.81       24.03           25.20           166037.98   406906304   24.542008-01-04  23.68       24.53           24.76       

這裡只是為了玩這些資料,如果你真的需要股票日資料,雅虎財經網站上有質量非常高的日交易資料可供下載。

按照上述方法,可以把一個股票幾年的資料合併起來,生成一個包含所有年份的歷史日交易資料。具體可以參閱 stock.py (https://github.com/kamidox/stock-data/blob/master/stock.py)裡的 minutes_to_days_batch 函式。

股票波動率

什麼股票是好股票?要回答這個問題,先要把最簡單的問題說清楚。炒股就是低買高賣,實現獲利。那麼好股票的標準就是在你的持股週期內,波動最大的股票。這很好理解吧,波動最大,我們才有可能在相對低點買入,在相對高點賣出,獲利最大。

在一定的時間週期內,衡量股票波動的指標定義為 最高價/最低價。以我們表格中的資料,就是 ceiling_price/floor_price。這個比率最大的股票就是好股票。關於時間週期,這個和炒股策略有關。有些人喜歡做短線,可能就持股幾天,或一兩週。有些人習慣做長線,可能持股幾個月甚至幾年。也有些人本來打算做短線,做著做著變成長線,再做著做著,變成了股東。

為了簡單起見,我們拿波動週期為 30 個自然日來計算,即如果某個股票停牌,那麼他的價格就一直沒有變化,則波動為 0。

這裡,我們直接使用 600690 這個股票來作為示例。我們直接讀取已經合併過日交易的資料。

qdhr = pd.read_csv('test-data/SH600690.csv', index_col='date', parse_dates=True)
qdhr.head()
                floor_price     opening_price   ceiling_price   volume      amount      closing_price
date2007-01-04      9.28            9.30            10.14           259264.75   254734000   9.802007-01-05      9.53            9.70            10.15           171169.97   170154432   9.902007-01-08      9.93            9.93         

我們發現數據中間有空洞,即週末和停牌時間裡是沒有資料的。我們把這些資料填充完整,我們看看 pandas 如何處理 missing data 。

填充資料

我們先生成一段連續的日期資料作為索引:

# 填充資料:生成日期索引l = len(qdhr)
start = qdhr.iloc[0:1].index.tolist()[0]
end = qdhr.iloc[l - 1: l].index.tolist()[0]
idx = pd.date_range(start=start, end=end)
idx
DatetimeIndex(['2007-01-04', '2007-01-05', '2007-01-06', '2007-01-07',               '2007-01-08', '2007-01-09', '2007-01-10', '2007-01-11',               '2007-01-12', '2007-01-13',
               ...               '2008-12-22', '2008-12-23', '2008-12-24', '2008-12-25',               '2008-12-26', '2008-12-27', '2008-12-28', '2008-12-29',               '2008-12-30', '2008-12-31'],
               dtype='datetime64[ns]', length=728, freq='D')

接著使用 reindex 函式缺失的資料被全。填充股票資料時有個要求,我們把缺失的價格資料用前一個交易日的資料來填充,但交易量需要填充為 0。

data = qdhr.reindex(idx)
zvalues = data.loc[~(data.volume > 0)].loc[:, ['volume', 'amount']]
data.update(zvalues.fillna(0))
data.fillna(method='ffill', inplace=True)
data.head()
            floor_price opening_price   ceiling_price   volume      amount      closing_price2007-01-04  9.28        9.30            10.14           259264.75   254734000   9.82007-01-05  9.53        9.70            10.15           171169.97   170154432   9.92007-01-06  9.53        9.70            10.15           0.00        0           9.92007-01-07  9.53        9.70            10.15           0.00        0           9.92007-01-08  9.93        9.93            10.78           159340.58   164954896   10.6

我們可以看到,06, 07 兩天的資料被正確地填充了。

分組計算

我們需要計算 30 個自然日裡的股票平均波動週期。這樣,我們必須以 30 天為單位,對所有的歷史資料進行分組。然後逐個分組計算其波動率。

生成分組索引

# 定義產生分組索引的函式,比如我們要計算的週期是 20 天,則按照日期,20 個交易日一組def gen_item_group_index(total, group_len):
    """ generate an item group index array

    suppose total = 10, unitlen = 2, then we will return array [0 0 1 1 2 2 3 3 4 4]
    """

    group_count = total / group_len
    group_index = np.arange(total)    for i in range(group_count):
        group_index[i * group_len: (i + 1) * group_len] = i
    group_index[(i + 1) * group_len : total] = i + 1
    return group_index.tolist()
In [7]: gen_item_group_index(10, 3)
Out [7]: [0, 0, 0, 1, 1, 1, 2, 2, 2, 3]

根據分組索引來分組

period = 30group_index = gen_item_group_index(len(data), period)# 把分組索引資料新增到股票資料裡data['group_index'] = group_indexprint len(data)
data.head().append(data.tail())

我們看一下添加了分組索引後的資料最前面 5 個和最後 5 個數據,注意 group_index 的值。我們接下來就是根據這個值進行分組。

            floor_price opening_price   ceiling_price   volume      amount      closing_price   group_index2007-01-04  9.28        9.30            10.14           259264.75   254734000   9.80            02007-01-05  9.53        9.70     

分組計算最高價和最低價

# 針對下跌的波動,我們把最高價設定為負數。什麼是下跌的波動?就是先出現最高價,再出現最低價def _ceiling_price(g):
    return g.idxmin() < g.idxmax() and np.max(g) or (-np.max(g))# 根據索引分組計算group = data.groupby('group_index').agg({                                        'volume': 'sum',                                        'floor_price': 'min',                                        'ceiling_price': _ceiling_price})
group.head()
                volume      ceiling_price   floor_price
group_index0               1271711.00  22.33           16.211               1831018.01  24.75           18.982               2038944.01  -27.20          20.083               477219.16   23.49           21.404               203932.07   -22.48          20.10

給每個分組新增起始日期

有時我們看到某個週期內下跌了很多,或上漲了很多,我們想知道是什麼時候發生的,所以需要給每個分組新增起始日期。

# 新增每個分組的起始日期date_col = pd.DataFrame({"group_index": group_index, "date": idx})
group['date'] = date_col.groupby('group_index').agg('first')
group.head()

idx 是我們在上面程式碼裡生成的連續的日期索引資料。新增日期資料後的樣子:

                volume      ceiling_price   floor_price     date
group_index0               4634226.68  -12.38          9.02            2007-01-041          

新增波動率

# 新增我們的波動指標 股票波動係數 = 最高價/最低價group['ripples_radio'] = group.ceiling_price / group.floor_price
group.head()
                volume          ceiling_price   floor_price     date            ripples_radio
group_index0               4634226.68      -12.38          9.02            2007-01-04      -1.3725061               3499001.47      11.64           8.80      

排序

按照波動率排序,可以看到某段時間內波動最大的一些時間段。

# 降序排列。我們把分組的起始日期,交易量總和都列出來,也可以觀察一下交易量和股票波動比的關係ripples = group.sort_values('ripples_radio', ascending=False)
ripples.head()
            volume          ceiling_price   floor_price     date            ripples_radio
group_index101         4352881.31      14.85           9.18            2008-04-21      1.61764790          5703121.25      18.89           11.85        

從資料可以看出來,波動最大的在 30 個自然日內上漲了 61.76%。發生在 2008-04-21 開始的 30 天內。

當然,我們也可以計算前 10 大上漲波動的平均值。

ripples.head(10).ripples_radio.mean()
1.3657990069195818

也可以計算前 10 大下跌波動的平均值。

ripples.tail(10).ripples_radio.mean()
-1.4124407127785106

看來下跌的平均值比上漲的還大呀。

我們針對每個股票都使用上述方法計算其平均波動,這樣我們就可以從一系列股票裡找出那些波動最大的股票了。當然,上漲波動越大,下跌波動也越大,正所謂風險和機遇並存嘛。具體可參閱 stock.py 裡的 stock_ripples_batch 函式。

計算漲跌幅

我們注意到原始資料裡沒有漲跌幅的資料。漲跌幅定義為今日收盤價減去昨日收盤價。我們換個股票,取出原始資料。

data = pd.read_csv('test-data/SZ000565.csv', index_col='date', parse_dates=True)
data.head()
            floor_price     opening_price   ceiling_price   volume      amount          closing_price
date2007-01-04  4.16            4.22            4.27            17877.88    7477370.52      4.192007-01-05  4.15            4.16            4.27            10857.66    4588246.02      4.

利用 diff 函式快速計算漲跌幅。

rise = data.closing_price.diff()
data['rise'] = rise
data.head()
    floor_price opening_price   ceiling_price   volume  amount  closing_price   rise
date2007-01-04  4.16    4.22    4.27    17877.88    7477370.52  4.19    NaN2007-01-05  4.15    4.16    4.27    10857.66    4588246.02  4.24    0.052007-01-08  4.27   

注意到第一條記錄的漲跌幅為 NaN,因為第一條記錄的昨日是沒有資料的。感興趣的同學可以再計算一下漲跌百分比,其定義為當日的漲跌幅除以昨日的收盤價。

計算指定時間點之前的一段時間內波動最大的股票

有時我們關心某個時間點之前的一段時間變化最劇烈的股票。比如最近一週漲幅最大的,最近一週跌幅最大的,或者最近一個月交易量變化最大的等等。

我們看一下 000565 這個股票在 2008-12-31 之前 30 個自然日裡的波動率。

選定資料

這裡涉及到用日期對資料進行分片的技術,我們需要選擇指定日期及之前一段時間內的資料。

end_date = '2008-12-31'period = 30end_date = pd.Timestamp(end_date)
start_date = end_date - pd.Timedelta(days=period)

data = pd.read_csv('test-data/SZ000565.csv', index_col='date', parse_dates=True)
data = data.loc[start_date:end_date]
data
            floor_price     opening_price   ceiling_price   volume          amount          closing_price
date2008-12-01  7.40            7.58            7.90            41747.12        3.214610e+07    7.882008-12-02  7.55            7.56            8.38            74552.15        6.029661e+07    8.322008-12-03  8.40            8.40            8.93            85361.64        7.420082e+07    8.822008-12-04  8.42            8.88            9.08            110410.46       9.740610e+07    8.502008-12-05  8.33            8.40            9.35            126479.91    

選出資料後,計算波動率就簡單了。我們按照老辦法,上漲的波動率為正數,下跌的波動率為負數。

# 計算波動值_ripple_radio = lambda data: data.ceiling_price.max() / data.floor_price.min()
ripple_radio = data.floor_price.idxmin() < data.ceiling_price.idxmax() and _ripple_radio(data) or -_ripple_radio(data)
ripple_radio
-1.4394812680115274

最後,遍歷所有的股票,計算其指定日期之前的一段時間的波動值,選出波動最大的股票,即是我們關注的股票。比如,經歷股票大跌,我們判斷會反彈,我們想搶反彈,搶哪個股票呢?答案是搶大跌中下跌最多的,因為下跌最多的股票往往反彈也最多。這部分程式碼可參閱 stock.py 裡的 recent_ripples 函式。

為什麼要用 pandas 玩轉股票資料

答案應該已經比較明顯了,雖然很多資料股票軟體裡都有。但一些高階的資料篩選方式其實這些股票軟體都不支援的。最後,需要補充一句,大家都是成年人,文章裡的任何策略是個人的思路,不構成投資建議啊,後果自負啊。

最最後,感興趣的可以看一下 stock.ipynb ( https://github.com/kamidox/stock-data/blob/master/stock.ipynb ),這個是本文在 ipython notebook 環境下的所有程式碼。