Python策略范例1-一步一步找alpha

回测框架简介

我们提供了Beta版本的在线Python编辑器给您并且将会提供更多的功能改善编辑体验如自动代码、股票代码补全来让您更加享受编写您的算法,使得您可以很轻松地在Ricequant平台上编写您的第一个策略。

我们平台会对您的算法策略做编译和运行时的验证,在您运行完整的回测之前可以使用我们的在线编辑器的编译功能做快速验证以节省您的宝贵时间。

我们目前支持股票的日线和分钟线的回测,以及股票分钟线的实时模拟交易。

我们平台还兼容各类Python的数学、统计和机器学习等扩展模块,如Pandas, Numpy, Scipy, statsmodels, Scikit Learn等等,您可以实现技术分析、多因子、数据挖掘、统计套利、事件驱动等各种流派和风格的证券投资策略。

我们平台使用的是中国A股市场从2005年到最新交易日的每分钟以及每日行情数据,包括股票数据、ETF/LOF/分级基金数据、指数数据、期货数据、财务数据、国债收益率曲线。

我们将不同市场、不同金融产品的数据和交易规则尽量统一进我们的Ricequant API,因此您不必去担心不同市场、不同金融产品之间的不同数据格式和不同的交易规则,可以更专注于算法策略和交易盈亏本身。

我们还处于Beta测试阶段,正在努力开发更多的功能如您可以在社区上和其他人分享想法共同进步,如您有对我们的各种建议,不要犹豫,请联系我们。

核心函数介绍

init函数(必须实现)

初始化方法 – 在回测和实时模拟交易只会在启动的时候触发一次。你的算法会使用这个方法来设置你需要的各种初始化配置。 context 对象将会在你的算法的所有其他的方法之间进行传递以方便你可以拿取到。

In [17]:
def init(context):
    # cash_limit的属性是根据用户需求自己定义的,你可以定义无限多种自己随后需要的属性,ricequant的系统默认只是会占用context.portfolio的关键字来调用策略的投资组合信息
    context.cash_limit = 5000

before_trading函数(选择实现)

可选择实现的函数。每天在策略开始交易前会被调用。不能在这个函数中发送订单。需要注意,该函数的触发时间取决于用户当前所订阅合约的交易时间。

举例来说,如果用户订阅的合约中存在有夜盘交易的期货合约,则该函数可能会在前一日的20:00触发,而不是早晨08:00.

In [18]:
def before_trading(context, bar_dict):
    # 拿取财务数据的逻辑,自己构建SQLAlchemy query
    fundamental_df = get_fundamentals(your_own_query)

    # 把查询到的财务数据保存到conext对象中
    context.fundamental_df = fundamental_df

    # 手动更新股票池
    update_universe(context.fundamental_df.columns.values)

handle_bar函数(必须实现)

bar数据的更新会自动触发该方法的调用。策略具体逻辑可在该方法内实现,包括交易信号的产生、订单的创建等。在实时模拟交易中,该函数在交易时间内会每分钟被触发一次。

In [19]:
def handle_bar(context, bar_dict):
    # put all your algorithm main logic here.
    # ...
    order_shares('000001.XSHE', 500)
    # ...

after_trading函数(选择实现)

可选择实现的函数。每天在收盘后被调用。不能在这个函数中发送订单。您可以在该函数中进行当日收盘后的一些计算。

在实时模拟交易中,该函数会在每天15:30触发。

证券的命名

股票、债券的命规则

股票代码是沪深两地证券交易所给上市股票分配的数字代码。这类代码涵盖所有在交易所挂牌交易的证券。熟悉这类代码有助于增加我们对交易品种的理解。

A股代码:沪市的为600×××,深市的为0×××;两市的后3位数字均是表示上市的先后顺序;

B股代码:沪市的为900×××,深市的为2×××;两市的后3位数字也是表示上市的先后顺序;

基金代码:沪市的为500×××,深市的为4×××;

配股代码:沪市的为700×××,深市的为8×××;后3位数为该股票代码的后3位数;

转配股代码:沪市的为710×××,深市的为3×××;后3位数为该股票代码的后3位数;

新股申购代码:沪市的为730×××,深市的即为该股票代码;

国债代码:沪市的为00××××,深市的为19××;

企业债券代码:沪市的为12××××,深市为10××;

回购债券:沪市的为2×××××,深市的为1×××。

期货的命名规则

期货合约的命名一般为:品种代码+交割年份(后两位)+交割月份(两位),如郑州硬麦:p0901

主力连续合约命名:品种代码+88,例如’IF88′

指数连续合约命名:品种代码+99

下单介绍

股票下单

平台提供了许多股票落单方式函数:

order_shares(指定股数交易)

落指定股数的买/卖单,最常见的落单方式之一。如有需要落单类型当做一个参量传入,如果忽略掉落单类型,那么默认是市价单(market order),也可以选择限价单(limit order)。

例如:购买Buy 2000 股的平安银行股票,并以市价单发送:

In [ ]:
order_shares('000001.XSHE', 2000)

order_lots(指定手数交易)

指定手数发送买/卖单。如有需要落单类型当做一个参量传入,如果忽略掉落单类型,那么默认是市价单(market order),也可以选择限价单(limit order)。

例如:买入20手的平安银行股票,并且发送市价单

In [ ]:
order_lots('000001.XSHE', 20)

order_value(指定价值交易)

使用想要花费的金钱买入/卖出股票,而不是买入/卖出想要的股数,正数代表买入,负数代表卖出。股票的股数总是会被调整成对应的100的倍数(在A中国A股市场1手是100股)。当您提交一个卖单时,该方法代表的意义是您希望通过卖出该股票套现的金额。如果金额超出了您所持有股票的价值,那么您将卖出所有股票。需要注意,如果资金不足,该API将不会创建发送订单。

例如:买入价值¥10000的平安银行股票,并以市价单发送。如果现在平安银行股票的价格是¥7.5,那么下面的代码会买入1300股的平安银行,因为少于100股的数目将会被自动删除掉

In [ ]:
order_value('000001.XSHE', 10000)

order_percent(一定比例下单)

发送一个等于目前投资组合价值(市场价值和目前现金的总和)一定百分比的买/卖单,正数代表买,负数代表卖。股票的股数总是会被调整成对应的一手的股票数的倍数(1手是100股)。百分比是一个小数,并且小于1(<100%),0.5表示的是50%.需要注意,如果资金不足,该API将不会创建发送订单。

例如:买入等于现有投资组合50%价值的平安银行股票。如果现在平安银行的股价是¥10/股并且现在的投资组合总价值是¥4000,用来买入的资金为¥2000,那么将会买入200股的平安银行股票。(不包含交易成本和滑点的损失)

In [ ]:
order_percent('000001.XSHG', 0.5)

order_target_value(目标价值下单)

买入/卖出并且自动调整该证券的仓位到一个目标价值。如果还没有任何该证券的仓位,那么会买入全部目标价值的证券。如果已经有了该证券的仓位,则会买入/卖出调整该证券的现在仓位和目标仓位的价值差值的数目的证券。需要注意,如果资金不足,该API将不会创建发送订单。

例如:如果现在的投资组合中持有价值¥3000的平安银行股票的仓位并且设置其目标价值为¥10000,以下代码范例会发送价值¥7000的平安银行的买单到市场。(向下调整到最接近每手股数即100的倍数的股数):

In [ ]:
order_target_value('000001.XSHE', 10000)

order_target_percent(目标比例下单)

买入/卖出证券以自动调整该证券的仓位到占有一个指定的投资组合的目标百分比。

如果投资组合中没有任何该证券的仓位,那么会买入等于现在投资组合总价值的目标百分比的数目的证券。
如果投资组合中已经拥有该证券的仓位,那么会买入/卖出目标百分比和现有百分比的差额数目的证券,最终调整该证券的仓位占据投资组合的比例至目标百分比。
其实我们需要计算一个$position\_to\_adjust$ (即应该调整的仓位)

$$position\_to\_adjust = target\_position – current\_position$$

投资组合价值等于所有已有仓位的价值和剩余现金的总和。买/卖单会被下舍入一手股数(A股是100的倍数)的倍数。目标百分比应该是一个小数,并且最大值应该<=1,比如0.5表示50%。

如果$position\_to\_adjust$ 计算之后是正的,那么会买入该证券,否则会卖出该证券。 需要注意,如果资金不足,该API将不会创建发送订单。

例如:如果投资组合中已经有了平安银行股票的仓位,并且占据目前投资组合的10%的价值,那么以下代码会买入平安银行股票最终使其占据投资组合价值的15%

In [ ]:
order_target_percent('000001.XSHE', 0.15)

期货下单

平台提供了许多期货落单的方式函数:

buy_open(买开)

买入开仓

例如:以价格为3500的限价单开仓买入2张上期所AG1607合约

In [ ]:
buy_open('AG1607', amount=2, style=LimitOrder(3500))

sell_close(平多仓)

平多仓

$sell\_close(id\_or\_ins, amount, style=OrderType)$

sell_open(卖开)

卖开

$sell\_open(id\_or\_ins, amount, style=OrderType)$

buy_close(平空仓)

平空仓

$buy\_close(id\_or\_ins, amount, style=OrderType)$

cancel_order(撤单)

撤单

$cancel\_order(order)$

get_open_orders(拿到未成交订单信息)

获取一个由order_id到order对象映射的dict,凡在此dict中的order都未被完全成交或取消

$get\_open\_orders()$

数据格式

为了使用户能对更多的市场进行算法回测研究,我们自定义了一套统一的金融数据格式,有pandas Panel/DataFrame/Series以及ndarray

以获取股票历史行情函数get_price返回的数据格式为例介绍:

传入一个order_book_id,多个fields,函数会返回一个pandas DataFrame

In [30]:
get_price('000001.XSHE', start_date='2013-01-04', end_date='2014-01-04', frequency='1d', fields=['close','open'], adjust_type='pre', skip_suspended=False, country='cn').head()
Out[30]:
close open
2013-01-04 5.5226 5.6366
2013-01-07 5.6297 5.5192
2013-01-08 5.5261 5.6297
2013-01-09 5.4777 5.5123
2013-01-10 5.4812 5.4777

传入一个order_book_id,一个field,函数会返回pandas Series

In [31]:
get_price('000001.XSHE', start_date='2013-01-04', end_date='2014-01-04', frequency='1d', fields='close', adjust_type='pre', skip_suspended=False, country='cn').head()
Out[31]:
2013-01-04    5.5226
2013-01-07    5.6297
2013-01-08    5.5261
2013-01-09    5.4777
2013-01-10    5.4812
Name: close, dtype: float64

传入多个order_book_id,一个field,函数会返回一个pandas DataFrame

In [32]:
get_price(['000001.XSHE','000002.XSHE'], start_date='2013-01-04', end_date='2014-01-04', frequency='1d', fields='close', adjust_type='pre', skip_suspended=False, country='cn').head()
Out[32]:
000001.XSHE 000002.XSHE
2013-01-04 5.5226 8.7657
2013-01-07 5.6297 8.7657
2013-01-08 5.5261 8.7657
2013-01-09 5.4777 8.7657
2013-01-10 5.4812 8.7657

传入多个order_book_id,函数会返回一个pandas Panel

In [38]:
get_price(['000001.XSHE','000002.XSHE'], start_date='2013-01-04', end_date='2014-01-04', frequency='1d', adjust_type='pre', skip_suspended=False, country='cn')
Out[38]:
<class 'rqcommons.pandas_patch.HybridDataPanel'>
Dimensions: 8 (items) x 240 (major_axis) x 2 (minor_axis)
Items axis: open to limit_down
Major_axis axis: 2013-01-04 00:00:00 to 2014-01-03 00:00:00
Minor_axis axis: 000001.XSHE to 000002.XSHE

history_bars函数

In [ ]:
history_bars(order_book_id, bar_count, frequency, fields)

获取指定合约的历史行情,同时支持日以及分钟历史数据。不能在init中调用。 注意,该API会自动跳过停牌数据。

可支持:日回测获取日历史数据、分钟回测获取日历史数据、分钟回测获取分钟历史数据

传入参数:

order_book_id

$str\ 合约代码,必填项$

bar_count

$int\ 获取的历史数据数量,必填项$

frequency

$str\ 获取数据什么样的频率进行。’1d’或’1m’分别表示每日和每分钟,必填项$

fields

$str\ 返回数据字段。必填项。见下方列表$

返回

ndarray ,方便直接与talib等计算库对接,效率较history返回的DataFrame更高

例如:获取最近5天的日线收盘价序列(策略当前日期为20160706)

In [ ]:
history_bars('000002.XSHE', 5, '1d', 'close')

上文已经提及了数据格式pandas Panel/DataFrame/Series,那么在获得了这些数据格式之后怎么进行下一步的操作呢,下面将要介绍到pandas:

Pandas模块

官网:http://pandas.pydata.org/pandas-docs/stable/index.html

Pandas是一个开源的,为Python编程语言提供高性能,易于使用的数据结构和数据分析工具。它提供快速,灵活和富有解释性的数据结构以方便直观的处理”相关“或“被标记的”数据。

Pandas适合处理多种类型的数据:

  • 具有不同数据类型列的表格数据,如SQL表或Excel电子表格
  • 有序或无序(不固定频率)的时间序列数据。
  • 带有行和列标签的任意矩阵数据
  • 任何其他形式的观测/统计数据集。

Pandas主要包含三种数据结构,分别是Series(一维),DataFrame(二维),Panel(三维)。其中Series和DataFrame可以用于处理绝大多数金融,统计,社会科学和许多工程领域的典型问题。对于R用户而言,DataFrame在支持所有R的data.frame的功能的基础上还能有更丰富的应用。Pandas库建立在NumPy库之上,旨在与科学计算环境和许多其他第三方库完美集成。

Pandas的优势:

  • 可以轻易的处理浮点及非浮点数据类型的缺失值(NaN)
  • 大小可变:DataFrame和Panel都可以删除或插入列
  • 数据自动对齐
  • 灵活强大的分组功能,可对数据集进行拆分组合操作
  • 方便的将其他Python和NumPy数据结构中不同类索引的数据转换为DataFrame对象
  • 基于智能标签的切片,花式索引,轻易从大数据集中取出子集
  • 直观的合并,连接数据集
  • 轻易的重新定义数据集形状和转置
  • 轴(axes)的分层标签(使每个元组有多个标签成为可能)

Pandas库是统计科学家在分析数据时的理想工具,非常适合应用于数据清洗,分析/建模,然后将分析结果组织成适合于绘图或表格显示的形式的全部过程。statsmodel库依赖Pandas库,使其成为Python统计计算系统的重要组成部分。

Pandas库已经广泛应用于金融数据。

*以上内容总结翻译自官网。

如果你已经安装了anaconda,Pandas库涵盖在anaconda提供的基本库内,因此我们无须安装即可使用。

下面是关于Pandas基本操作的简单展示:

In [42]:
from pandas import Series, DataFrame
import pandas as pd
import numpy as np

Series

Series是Pandas最重要的数据结构之一,是一种类似于一维数组的对象,它由一组数据(各种Numpy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生简单的Series。
下面是最简单的一种创建Series对象的方式:

In [ ]:
s = pd.Series(data,index=index)#data的数据类型可以是任何NumPy数据类型,而index只需与其等长。
In [45]:
# 下面我们随机生成一组ndarray格式数据,并创建Series
s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
# 若不输入索引则默认索引为从零开始的整数列
j = pd.Series(np.random.randn(5))

# 打印s,j
print('show s :','\n',s)
print('\n','show j :','\n',j)

# 打印s的索引
print('\n','s.index :',s.index)

# Series中索引可以看作dict中的key而元组中的值可以看作dict中的value
# 像对dict进行操作一样,我们取出key为b的元素s['b'],并打印出来
print('\n','s[\'b\'] :',s['b'])

# 打印dict的keys,可以发现索引就是key
print('\n','s.keys() :',s.keys())

# 通过to_dict函数将Series转为dict
mapping = s.to_dict()
print('\n','mapping :','\n',mapping)

# 正如Series可以通过dict操作进行管理,通过词典方式也可以构建Series
print('\n','Series(mapping) :','\n',Series(mapping))

#一条为了美观的分割线
print('--------------------------------------------------------------------------------------')
show s : 
 a   -1.263266
b    1.193284
c   -1.145649
d   -0.116367
e   -0.354037
dtype: float64

 show j : 
 0   -0.767192
1    0.791023
2    0.357123
3   -0.091236
4    1.230029
dtype: float64

 s.index : Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

 s['b'] : 1.19328368136

 s.keys() : Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

 mapping : 
 {'d': -0.11636668522224589, 'e': -0.3540371134798011, 'a': -1.2632657357021486, 'b': 1.1932836813579908, 'c': -1.1456485965931713}

 Series(mapping) : 
 a   -1.263266
b    1.193284
c   -1.145649
d   -0.116367
e   -0.354037
dtype: float64
--------------------------------------------------------------------------------------

从ricequant数据库中提取出的数据默认保存为pandas的三种数据结构,下面我们调用get_price函数取出收盘价数据。

In [46]:
# 默认取出数据的长度为一年
# 取出收盘价数据的方式比较灵活,但在正常情况下,ts_2的运行速度会更快一些
ts_1 = get_price('600208.XSHG')['ClosingPx'][-5:]
ts_2 = get_price('600208.XSHG',fields='close')[-5:]

# 打印取出的近五天收盘价
print('ts_1 :','\n',ts_1,'\n\n','ts_2 :','\n',ts_2)
# 打印二者数据类型
print('\n','type(ts_1) :',type(ts_1),'\n','type(ts_2) :',type(ts_2))


print('--------------------------------------------------------------------------------------')
ts_1 : 
 2013-12-27    3.0431
2013-12-30    3.0528
2013-12-31    3.0816
2014-01-02    3.0720
2014-01-03    3.0142
Name: close, dtype: float64 

 ts_2 : 
 2013-12-27    3.0431
2013-12-30    3.0528
2013-12-31    3.0816
2014-01-02    3.0720
2014-01-03    3.0142
Name: close, dtype: float64

 type(ts_1) : <class 'pandas.core.series.Series'> 
 type(ts_2) : <class 'pandas.core.series.Series'>
--------------------------------------------------------------------------------------

get_price函数将一维数据保存在pandas.Series中,index为日期。

了解更多关于get_price函数的信息:https://www.ricequant.com/api/python/chn#data-methods-get_price

pandas中提供大量函数能够方便的帮助我们管理数据,下面我们以ts_1中取出的数据为例为各位展示其基本操作。

In [47]:
# 取前3项,并打印出来
print('ts_1的前三项:\n',ts_1[:3])

# 打印第三个索引
print('\n第三个索引名:',ts_1.index[2])

# 按索引2013-12-30输出当日收盘价,并打印
print('\n2013-12-30的收盘价:',ts_1['2013-12-30'])

# 按行输出第三行的收盘价,并打印
print('\nts_1[2] :',ts_1[2])

print('--------------------------------------------------------------------------------------')
ts_1的前三项:
 2013-12-27    3.0431
2013-12-30    3.0528
2013-12-31    3.0816
Name: close, dtype: float64

第三个索引名: 2013-12-31 00:00:00

2013-12-30的收盘价: 3.0528

ts_1[2] : 3.0816
--------------------------------------------------------------------------------------

Pandas可以实现R中非常令人喜爱的which函数功能(即Matlab中的find函数),在以下标为索引查找数据时比which函数更简便更直观。

In [48]:
# 打印ts_1前三项中最大值的索引
print('\nts_1.index[ts_1[:3] == ts_1[:3].max()] :\n',ts_1[:3].index[ts_1 == ts_1[:3].max()])

# 在筛选符合条件的项时操作比R和Matlab更简便
# 打印ts_1前三项中大于3.05的值
print('\nts_1[:3][ts_1>3.05]:\n',ts_1[:3][ts_1>3.05])

# 打印ts_1中等于3.0431的值
print('\nts_1[ts_1 == 3.0431]:\n',ts_1[ts_1 == 3.0431])

print('--------------------------------------------------------------------------------------')
ts_1.index[ts_1[:3] == ts_1[:3].max()] :
 DatetimeIndex(['2013-12-31'], dtype='datetime64[ns]', freq=None)

ts_1[:3][ts_1>3.05]:
 2013-12-30    3.0528
2013-12-31    3.0816
Name: close, dtype: float64

ts_1[ts_1 == 3.0431]:
 2013-12-27    3.0431
Name: close, dtype: float64
--------------------------------------------------------------------------------------

Pandas还能轻松的调用numpy库中的基本函数,也能做四则运算:

In [49]:
# 一阶差分
print('np.diff(ts_1):',np.diff(ts_1))

# 以e为底的指数
print('\nnp.exp(ts_1[:3]) :\n',np.exp(ts_1[:3]))

# 四则运算
print('\n加法:\nts_1[:3]+ts_1[:3]=\n',ts_1[:3]+ts_1[:3])
print('\n减法:\nts_1[:3]-ts_1[:3]=\n',ts_1[:3]-ts_1[:3])
print('\n乘法:\nts_1[:3] * 2 =\n',ts_1[:3] * 2)
print('\n除法:\nts_1[:3] / 2 =\n',ts_1[:3] / 2)

print('--------------------------------------------------------------------------------------')
np.diff(ts_1): [ 0.0097  0.0288 -0.0096 -0.0578]

np.exp(ts_1[:3]) :
 2013-12-27    20.970150
2013-12-30    21.174550
2013-12-31    21.793244
Name: close, dtype: float64

加法:
ts_1[:3]+ts_1[:3]=
 2013-12-27    6.0862
2013-12-30    6.1056
2013-12-31    6.1632
Name: close, dtype: float64

减法:
ts_1[:3]-ts_1[:3]=
 2013-12-27    0.0
2013-12-30    0.0
2013-12-31    0.0
Name: close, dtype: float64

乘法:
ts_1[:3] * 2 =
 2013-12-27    6.0862
2013-12-30    6.1056
2013-12-31    6.1632
Name: close, dtype: float64

除法:
ts_1[:3] / 2 =
 2013-12-27    1.52155
2013-12-30    1.52640
2013-12-31    1.54080
Name: close, dtype: float64
--------------------------------------------------------------------------------------

DataFrame

DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。你可以把它看作是一个Excel表或者SQL表,或者value是一个Series的dict。

get_price函数同时支持取出一组股票的收盘价,结果将保存在DataFrame中。下面我们依然以get_price函数为例,简单介绍DataFrame的基本操作:

In [61]:
# 打印一组股票的收盘价,并打印其数据类型
df = get_price(['000024.XSHE', '000001.XSHE', '000002.XSHE'],start_date='20161010',end_date='20161020',fields='close')
print('df[:3] =\n',df[:3],'\n\ntype(df) :',type(df)) # 为了版面美观我们只打印前三项

# 000024.XSHE在这段时间没有收盘价,也许是因为已经退市或者未上市,我们删除这一列
# 在这里有三种不同的删除列的方法可供选择:
df_new = df.dropna(axis=1)#axis=0是按行删失,axis=1是按列删失
print('\ndf_new[:3] =\n',df_new[:3])

# 也可以队列中的pop函数 
pop = df.pop('000024.XSHE')
print('\ndf[:3] :\n',df[:3])

df['000024.XSHE'] = pop


print('--------------------------------------------------------------------------------------')
df[:3] =
             000024.XSHE  000001.XSHE  000002.XSHE
2016-10-10          NaN         9.12        25.97
2016-10-11          NaN         9.15        26.23
2016-10-12          NaN         9.13        26.60 

type(df) : <class 'pandas.core.frame.DataFrame'>

df_new[:3] =
             000001.XSHE  000002.XSHE
2016-10-10         9.12        25.97
2016-10-11         9.15        26.23
2016-10-12         9.13        26.60

df[:3] :
             000001.XSHE  000002.XSHE
2016-10-10         9.12        25.97
2016-10-11         9.15        26.23
2016-10-12         9.13        26.60
--------------------------------------------------------------------------------------
In [51]:
# 添加列
df['Ratio']=df['000001.XSHE']/df['000002.XSHE']
print('df[:3] :\n',df[:3])

# 打印行索引
print('\ndf[:3].index :\n',df[:3].index)
# 打印列索引
print('\ndf[:3].columns :\n',df[:3].columns)

# 转置df[:3]并打印
print('\ndf[:3].T:\n',df[:3].T)

print('--------------------------------------------------------------------------------------')
df[:3] :
             000001.XSHE  000002.XSHE     Ratio
2016-10-10         9.12        25.97  0.351174
2016-10-11         9.15        26.23  0.348837
2016-10-12         9.13        26.60  0.343233

df[:3].index :
 DatetimeIndex(['2016-10-10', '2016-10-11', '2016-10-12'], dtype='datetime64[ns]', freq=None)

df[:3].columns :
 Index(['000001.XSHE', '000002.XSHE', 'Ratio'], dtype='object')

df[:3].T:
              2016-10-10  2016-10-11  2016-10-12
000001.XSHE    9.120000    9.150000    9.130000
000002.XSHE   25.970000   26.230000   26.600000
Ratio          0.351174    0.348837    0.343233
--------------------------------------------------------------------------------------

在DataFrame中选择行列的方式与Series有些不同,在Series中data[‘index’]可以根据行索引取出元素,而在DataFrame中输入索引默认的是查找列索引data[‘column’],这显然不太方便。那么当我们需要按行查找数据的时候怎么办呢?

Pandas库中有两个函数可以实现按行下标搜索和按行索引搜索的功能:(当然如果不嫌麻烦的话也可以转置后按列取的)

In [52]:
# 按行索引搜索
print('\ndf.loc[\'2016-10-11\'] :\n',df.loc['2016-10-11'])
# 按行下标搜索
print('\ndf.iloc[2] :\n',df.iloc[2])

print('--------------------------------------------------------------------------------------')
df.loc['2016-10-11'] :
 000001.XSHE     9.150000
000002.XSHE    26.230000
Ratio           0.348837
Name: 2016-10-11 00:00:00, dtype: float64

df.iloc[2] :
 000001.XSHE     9.130000
000002.XSHE    26.600000
Ratio           0.343233
Name: 2016-10-12 00:00:00, dtype: float64
--------------------------------------------------------------------------------------

还有一个解决方案:df.ix 可以混合使用索引和下标进行访问,但是行列内部需保持一致

In [54]:
# 按行下标
print('df.ix[1]:\n',df.ix[1])
# 按行索引
print('\ndf.ix[\'2016-10-11\'] :\n',df.ix['2016-10-11'])

print('--------------------------------------------------------------------------------------')
df.ix[1]:
 000001.XSHE     9.150000
000002.XSHE    26.230000
Ratio           0.348837
Name: 2016-10-11 00:00:00, dtype: float64

df.ix['2016-10-11'] :
 000001.XSHE     9.150000
000002.XSHE    26.230000
Ratio           0.348837
Name: 2016-10-11 00:00:00, dtype: float64
--------------------------------------------------------------------------------------

如果只需要访问某位置的特定元素的话,df.iat 是速度最快的方式:

In [56]:
# 按下标
print('df.iat[1,0] =',df.iat[1,0])

print('--------------------------------------------------------------------------------------')
df.iat[1,0] = 9.15
--------------------------------------------------------------------------------------

在DataFrame中,因为每一列都保存为Series结构,因此继承了Series的各类优点,可以进行数据筛选和各类按列运算,不仅如此,在DataFrame数据结构下,对其中每个元素的各种运算也非常便捷:

In [57]:
# 数乘
print('df[:3] * 5 +2 =\n',df[:3] * 5 +2)

# 平方
print('\ndf[:3] ** 2 =\n',df[:3] ** 2)

# 取倒数
print('\n1/df[:3] =\n',1/df[:3])

print('--------------------------------------------------------------------------------------')
df[:3] * 5 +2 =
             000001.XSHE  000002.XSHE     Ratio
2016-10-10        47.60       131.85  3.755872
2016-10-11        47.75       133.15  3.744186
2016-10-12        47.65       135.00  3.716165

df[:3] ** 2 =
             000001.XSHE  000002.XSHE     Ratio
2016-10-10      83.1744     674.4409  0.123323
2016-10-11      83.7225     688.0129  0.121687
2016-10-12      83.3569     707.5600  0.117809

1/df[:3] =
             000001.XSHE  000002.XSHE     Ratio
2016-10-10     0.109649     0.038506  2.847588
2016-10-11     0.109290     0.038124  2.866667
2016-10-12     0.109529     0.037594  2.913472
--------------------------------------------------------------------------------------
In [58]:
# 按照步长为2取出数据
df2 = df.ix[::2]
print('df2:\n',df2)
print('\ndf + df2 =\n',df + df2)

print('--------------------------------------------------------------------------------------')
df2:
             000001.XSHE  000002.XSHE     Ratio
2016-10-10         9.12        25.97  0.351174
2016-10-12         9.13        26.60  0.343233
2016-10-14         9.09        26.84  0.338674
2016-10-18         9.09        26.00  0.349615
2016-10-20         9.07        25.94  0.349653

df + df2 =
             000001.XSHE  000002.XSHE     Ratio
2016-10-10        18.24        51.94  0.702349
2016-10-11          NaN          NaN       NaN
2016-10-12        18.26        53.20  0.686466
2016-10-13          NaN          NaN       NaN
2016-10-14        18.18        53.68  0.677347
2016-10-17          NaN          NaN       NaN
2016-10-18        18.18        52.00  0.699231
2016-10-19          NaN          NaN       NaN
2016-10-20        18.14        51.88  0.699306
--------------------------------------------------------------------------------------

可以看出,对于那些df2中不存在索引的项,dataframe默认为NaN,四则运算的结果依然为NaN。

DataFrame中有两个常用的函数,分别是join和concat。均负责连接两数据集。

In [60]:
# join函数内连接取交集:
s1 = df[:2]
s2 = df[4:6]
innerjoin = s1.align(s2,join='inner')
print('innerjoin =\n',innerjoin)

# join函数外连接取并集
outerjoin = s1.align(s2,join='outer')
print('\nouterjoin =\n',outerjoin)

# concat连接两dataframe
df_new = get_price(['000006.XSHE','000056.XSHE'],start_date='20161010',end_date='20161020',fields='close')
df_concat = pd.concat([df,df_new],axis=1)
print('\ndf_concat =\n',df_concat[:3])

print('--------------------------------------------------------------------------------------')
innerjoin =
 (Empty DataFrame
Columns: [000001.XSHE, 000002.XSHE, Ratio]
Index: [], Empty DataFrame
Columns: [000001.XSHE, 000002.XSHE, Ratio]
Index: [])

outerjoin =
 (            000001.XSHE  000002.XSHE     Ratio
2016-10-10         9.12        25.97  0.351174
2016-10-11         9.15        26.23  0.348837
2016-10-14          NaN          NaN       NaN
2016-10-17          NaN          NaN       NaN,             000001.XSHE  000002.XSHE     Ratio
2016-10-10          NaN          NaN       NaN
2016-10-11          NaN          NaN       NaN
2016-10-14         9.09        26.84  0.338674
2016-10-17         9.05        26.00  0.348077)

df_concat =
             000001.XSHE  000002.XSHE     Ratio  000006.XSHE  000056.XSHE
2016-10-10         9.12        25.97  0.351174         9.92        12.74
2016-10-11         9.15        26.23  0.348837        10.43        12.91
2016-10-12         9.13        26.60  0.343233        10.70        12.80
--------------------------------------------------------------------------------------

 

Python策略范例系列目录:

当前阅读> 1. Python策略范例1-一步一步找Alpha
2. Python策略范例2-一个简单的技术指标策略
3. Python策略范例3-了解米筐撮合机制
4. Python策略范例4-策略怎么样,米筐来分析
5. Python策略范例5-股息率策略
6. Python策略范例6-海龟交易的Python完全版
7. Python策略范例7-Dual Thrust 交易策略
8. Python策略范例8-止损/止盈的七种方法
9. Python策略范例9-我有一个策略想法,如何一步步转化成策略代码?
10. Python策略范例10-参数优化框架