使用 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
另外補充一點,最好使用 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 環境下的所有程式碼。