使用 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 环境下的所有代码。

原文发布于微信公众号 - 大数据挖掘DT数据分析(datadw)

原文发表时间:2016-03-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯大讲堂的专栏

面向设计的半封装web组件开发

作者:张鑫旭,资深钓鱼爱好者,然后平时喜欢研究与学习前端技术。 前言 本文内容可谓是对大脑认知的一场洗礼。我们平常提到组件,就会想到重用,各个项目都能使用。而...

18510
来自专栏HansBug's Lab

面向对象先导课感想

下来我将分点讲述下收获和感想以及相关意见和建议。 收获和感想 作为一个虽然没有专门学过java但是早已经熟悉OOP程序设计方式,并使用 C# 有过大概几千行开发...

2834
来自专栏CDA数据分析师

让机器猜猜你喜欢的歌手-R关联分析

作者 CDA 数据分析师 关联规则挖掘是数据挖掘中成果颇丰而且比较活跃的研究分支。采用关联模型比较典型的案例是“尿布与啤酒”的故事。在美国,一些年轻的父亲...

20510
来自专栏无原型不设计

文字如何实现完美UI?文本排版设计告诉你

一部手机,电量充足,网络通畅,就足以让我们打发一天的时光,尽情沉浸在手机时代的缤纷世界里。这个信息资源无穷尽的手机网络世界,是设计师和开发者们在不停的探索中一...

2627
来自专栏Keegan小钢

App架构设计经验谈:技术选型

当你做架构设计时,必然会面临技术选型的抉择,不同的技术方案,架构也可能完全不同。有哪些技术选型需要做决策呢?比如,App是纯原生开发,还是Web App,抑或H...

461
来自专栏CSDN技术头条

编码过程中需尽量避免的 7 条捷径

1. 复制代码 “我认为最有价值的规则是避免重复。有且仅有一次是极限编程里的说法。- Martin Fowler 这很容易成为头号规则。如果你想要你的代码坚如磐...

1836
来自专栏令仔很忙

面向对象

在面向对象编程出现之前,几乎所有的程序都是以面向过程为中心的,程序的运行从某个地方开始运行达到一定的目的就结束了。而且程序的代码修改和重复使用率比较低。面向...

702
来自专栏闰土大叔

回顾自己三次失败的面试经历

前言 时间的齿轮已经来到了2017年的11月份,距离2018年仅仅还剩下不到两个月的时间。站在这个时间点上,我对自己之前三次失败的面试经历做了一次深度回顾。 ...

3269
来自专栏HTML5学堂

移动端兼容系列 HTML与CSS兼容

HTML5学堂:本文,我们将继续为大家总结介绍移动端的常见兼容问题,今天要提的是关于移动端HTML与CSS当中,遇到的一些常见兼容问题,主要包括取消电话号码的识...

2736
来自专栏牛客网

招银网络科技软开面经

广州地区现场面在华工大学城中心酒店,一共三面 一面(技术面,问的不深,但是涉及的面挺广) 1.项目和实习内容 2.java new对象的过程 3.jvm虚拟机的...

3677

扫描关注云+社区