一文带你快速入门股票量化交易

量化投资-、python、pandas
量化交易
python
策略分享
标签: #<Tag:0x00007fb3dcc18eb8> #<Tag:0x00007fb3dcc18d78> #<Tag:0x00007fb3dcc18c10> #<Tag:0x00007fb3dcc18ad0>

(iQuant) #1
克隆策略

导语

由于技术带来的创新,金融机构正向科技公司的方向发展。交易的速度、频率和大数据量,多年来使金融机构对技术的关注一直在增加。 技术已经成为金融的主要推动者。

在金融界最热门的编程语言中,R和Python名列其中,还有其他语言如C++、C#和Java。在本教程中,你将会学到如何使用Python进行量化交易。 本教程包括以下内容:

  • 基础知识:了解股票和交易策略,时间序列数据,以及如何设置工作平台。

  • 常用量化交易知识:时间序列数据,滚动窗口、波动率。学会使用Python中的软件包Pandas计算以上指标。

  • 开发动量策略:完成一个简单策略的编程。

  • 策略回测:在BigQuant平台对交易策略进行回测。

  • 优化及评估:对策略进行优化,使其执行地更好,并最终评估策略的性能和稳健性。

Alt text

开始学习Python在金融中的使用

在你学习交易策略之前,需要先掌握基本知识。 除本教程外,更多基础学习可以查看【宽客学院】Pandas快速入门。

股票交易

股票交易者可以通过购买或出售股票,并且希望有利润产生。 为了获得一个良好的收益率,交易者在市场上进行长期或短期的操作: 当按照一个固定的计划在市场上进行操作时,该计划被称为交易策略。

开发一个交易策略需要经过几个阶段:制定策略和形式,利用计算机进行初步的测试,对策略进行回测,优化策略,最后评估策略性能和鲁棒性。

交易策略通常是需要回测的:交易策略使用历史数据进行重建,回测会在过去进行并且使用你制定的规则和策略。 这样,我们就可以了解策略的有效性。 在应用于实际市场之前,你可以把回测过程当成一个起点,优化并改进策略。 当然,这一切都依赖于潜在的理论和理念: 即过去制定的任何策略都有可能在未来发挥作用, 而过去表现不佳的任何策略也可能在未来都表现不佳。

时间序列数据

在投资中,时间序列跟踪选定数据点(如股票价格)在特定时间段内的运动, 并定期记录数据点。如果你还对此有所疑惑,可以看下面的这个例子:

Alt text

日期被放置在x轴上,价格在y轴上显示。

当你处理股票数据时,不仅仅是包含时间点和价格的两列数据, 大多数情况下,你将观察五个列,包含时间点和该期间的开盘价、最高价、最低价和收盘价。 这意味着,如果时间点为日级别,当天数据将使您了解当天的开盘价和收盘价,以及最高和最低价格走势。

设置工作区

让你的工作区准备就绪是一件容易的工作:您基本上只需要确保系统上运行Python和集成开发环境(IDE)。

这里推荐使用:BigQuant, 平台提供了完整的量化策略开发框架,帮助你在浏览器中做量化交易。 更重要的是,你还可以访问一个社区,可以与同行讨论解决方案或问题。

Python在金融上的基础:Pandas

使用Python进行量化交易时,你会使用软件包:Pandas,NumPy、SciPy、Matplotlib。 本节将解释如何使用Pandas导入数据、探索和操作数据,并对所导入的数据执行常见的量化交易。

将金融数据导入Python

在本平台中,历史数据已被加载。

In [2]:
import pandas as pd # 首先导入库
df = D.history_data(instruments = ['600519.SHA'],
                     start_date = '2017-01-01',
                     end_date = '2017-07-28',
                     fields = ['open','high','low','close']
                     ) # 以贵州茅台为例,导入某一时间段历史数据
df = df[['instrument','date','open','high','low','close']] # 运行结果根据需要进行列调整

使用时间序列数据

所得到的对象df是DataFrame,它是可以包含不同类型的列的二维标记数据结构。 当你手上有一个常规的DataFrame时,你做的第一件事就是运行head()和tail(),查看DataFrame的第一行和最后一行。 当您使用时间序列数据时,这并不会改变该DataFrame。

同时,我们可以使用describe()功能获取摘要统计信息。

In [2825]:
df.tail()# 查看后几条数据,,默认查询5条
Out[2825]:
instrument date open high low close
134 600519.SHA 2017-07-24 3376.806152 3451.256836 3376.806152 3397.569824
135 600519.SHA 2017-07-25 3428.857666 3433.124268 3385.481445 3398.707520
136 600519.SHA 2017-07-26 3399.347656 3424.804443 3317.643799 3353.695801
137 600519.SHA 2017-07-27 3353.695801 3383.703613 3349.927002 3369.624268
138 600519.SHA 2017-07-28 3349.216064 3442.155029 3325.821289 3441.088379
In [2826]:
df.head(3) # 查看前3条数据
Out[2826]:
instrument date open high low close
0 600519.SHA 2017-01-03 2341.622803 2360.676270 2331.325439 2343.583984
1 600519.SHA 2017-01-04 2344.004395 2466.941650 2343.864258 2465.120361
2 600519.SHA 2017-01-05 2451.740967 2461.898193 2419.798340 2428.904785
In [2827]:
df.describe()# 使用describe()函数对数据快速统计汇总
Out[2827]:
open high low close
count 139.000000 139.000000 139.000000 139.000000
mean 2847.113770 2879.254639 2823.472900 2854.916748
std 335.776001 339.251801 329.971283 336.216522
min 2341.622803 2360.676270 2331.325439 2343.583984
25% 2506.694824 2538.427490 2498.499146 2513.734863
50% 2806.332764 2847.872070 2766.964844 2805.702148
75% 3192.528076 3222.988525 3147.986206 3197.256836
max 3428.857666 3451.256836 3385.481445 3441.088379

这些数据清楚地包含每天开盘价、收盘价,最高价和最低价的四列数据。

提示:如果你想要存储数据并在之后读取,推荐使用平台的DataSource方式。

In [3]:
ds = DataSource.write_df(df) # 将数据存储在平台上
DataSource(id=ds.id).read_df().head() # 读取数据
Out[3]:
instrument date open high low close
0 600519.SHA 2017-01-03 2341.622803 2360.676270 2331.325439 2343.583984
1 600519.SHA 2017-01-04 2344.004395 2466.941650 2343.864258 2465.120361
2 600519.SHA 2017-01-05 2451.740967 2461.898193 2419.798340 2428.904785
3 600519.SHA 2017-01-06 2428.204102 2520.249512 2424.421387 2457.064697
4 600519.SHA 2017-01-09 2436.330078 2471.915283 2427.503662 2441.303467

获取该DataFrame数据的一种方法是通过索引和列。 后者称为子集,类型是Series,它是一个能够保存任何类型数据的一维标记数组。

在下面的练习中使用index和columns来查看数据的索引和列。 接下来,通过使用方括号[]获取子集

为查看子集类型,使用type()函数检查使用的类型:

In [2829]:
df.index #查看DataFrame的索引
Out[2829]:
RangeIndex(start=0, stop=139, step=1)
In [2830]:
df.columns # 检查列
Out[2830]:
Index(['instrument', 'date', 'open', 'high', 'low', 'close'], dtype='object')
In [2831]:
ts = df['close'][-10:] # 提取 `close`最近十次观察数据
In [2832]:
type(ts)# 查看ts的属性
Out[2832]:
pandas.core.series.Series

方括号可以很好地对数据进行分组,但不是用Pandas来获取数据的最好方法。

推荐使用loc()和iloc():使用前者进行基于标签的索引,后者用于位置索引。

你可以传递行标签,如标签2007和2006-11-01到loc()功能,或者传递整数,如22与43到iloc()功能。

完成下面的练习,了解两者loc()和iloc()工作方式:

In [2833]:
print(df.set_index('date').loc['2017-01-01':'2017-02-28'].head()) #查看2017年1月-2月的前几行
            instrument         open         high          low        close
date                                                                      
2017-01-03  600519.SHA  2341.622803  2360.676270  2331.325439  2343.583984
2017-01-04  600519.SHA  2344.004395  2466.941650  2343.864258  2465.120361
2017-01-05  600519.SHA  2451.740967  2461.898193  2419.798340  2428.904785
2017-01-06  600519.SHA  2428.204102  2520.249512  2424.421387  2457.064697
2017-01-09  600519.SHA  2436.330078  2471.915283  2427.503662  2441.303467
In [2834]:
print(df.set_index('date').loc['2017'].head())# 查看2017年的前几行
            instrument         open         high          low        close
date                                                                      
2017-01-03  600519.SHA  2341.622803  2360.676270  2331.325439  2343.583984
2017-01-04  600519.SHA  2344.004395  2466.941650  2343.864258  2465.120361
2017-01-05  600519.SHA  2451.740967  2461.898193  2419.798340  2428.904785
2017-01-06  600519.SHA  2428.204102  2520.249512  2424.421387  2457.064697
2017-01-09  600519.SHA  2436.330078  2471.915283  2427.503662  2441.303467
In [2835]:
print(df.iloc[18:36]) #查看2017年2月份
    instrument       date         open         high          low        close
18  600519.SHA 2017-02-03  2423.720947  2445.996826  2416.716064  2429.675293
19  600519.SHA 2017-02-06  2441.163330  2443.965332  2416.435791  2429.675293
20  600519.SHA 2017-02-07  2429.605225  2433.528076  2403.686768  2406.769043
21  600519.SHA 2017-02-08  2408.029785  2420.218506  2400.394531  2411.182129
22  600519.SHA 2017-02-09  2416.716064  2443.264893  2413.984131  2434.999023
23  600519.SHA 2017-02-10  2442.073975  2450.479980  2416.716064  2417.276367
24  600519.SHA 2017-02-13  2409.710938  2465.050293  2395.140625  2449.989746
25  600519.SHA 2017-02-14  2448.518555  2469.953857  2445.436523  2453.211914
26  600519.SHA 2017-02-15  2462.248291  2468.833008  2432.126953  2441.443604
27  600519.SHA 2017-02-16  2441.233398  2442.073975  2420.288574  2438.361328
28  600519.SHA 2017-02-17  2444.035400  2482.843018  2434.228516  2455.663574
29  600519.SHA 2017-02-20  2454.613037  2538.042236  2451.811035  2530.827148
30  600519.SHA 2017-02-21  2534.959961  2538.812744  2502.036621  2512.964355
31  600519.SHA 2017-02-22  2526.063721  2567.463135  2521.790527  2535.030029
32  600519.SHA 2017-02-23  2528.585449  2560.177979  2520.109375  2533.698975
33  600519.SHA 2017-02-24  2535.800537  2542.034912  2522.140869  2540.143555
34  600519.SHA 2017-02-27  2539.232910  2541.754883  2499.374756  2502.176758
35  600519.SHA 2017-02-28  2500.775635  2516.186768  2476.818604  2484.384033
In [2836]:
print(df.iloc[[18,25],[2,4]])# 查看2017-2-3与2017-2-14的open与close值
           open          low
18  2423.720947  2416.716064
25  2448.518555  2445.436523

除了索引之外,您还可能需要探索其他一些技巧来更好地了解您的数据。 我们尝试从数据集中抽取大约5行,然后对数据进行重新组织,以便df的数据处于月级别,而不是每天。 您可以利用sample()和resample()功能:

In [2837]:
sample = df.sample(5)# sample命令:从指定的序列中,随机的截取指定长度的片断,不作原地修改。
print(sample) # 显示数据
     instrument       date         open         high          low        close
116  600519.SHA 2017-06-28  3391.037842  3391.037842  3313.352783  3329.464111
9    600519.SHA 2017-01-16  2410.621582  2415.314941  2373.285156  2391.988525
100  600519.SHA 2017-06-06  3117.003174  3145.233398  3111.609375  3143.972412
3    600519.SHA 2017-01-06  2428.204102  2520.249512  2424.421387  2457.064697
47   600519.SHA 2017-03-16  2637.792969  2649.981689  2611.454346  2625.254150
In [2838]:
monthly_df= df.set_index('date').resample('M').mean()# resample命令:从指定的序列中,按照指定的方式对序列作聚合,不作原地修改
print(monthly_df)
                   open         high          low        close
date                                                          
2017-01-31  2434.267334  2462.793213  2412.991699  2439.217529
2017-02-28  2465.949219  2487.260010  2449.402100  2467.081787
2017-03-31  2620.920166  2649.558350  2604.942627  2631.092529
2017-04-30  2800.701416  2842.828613  2770.615234  2811.851074
2017-05-31  2978.690186  3017.574707  2958.575195  2994.707031
2017-06-30  3262.932861  3292.716797  3237.683105  3270.074707
2017-07-31  3274.643311  3310.675537  3237.717529  3277.791992

resample()功能经常被使用于时间系列的频率转换。

最后,我们进行数据可视化并对数据执行一些常见量化交易。 例如,计算每天开盘价和收盘价之间的差额:

In [2839]:
df['diff'] = df.open - df.close #计算一阶差分
del df['diff'] #删除新的‘diff’列

可视化时间序列数据

除了head(),tail(),index等命令,您可能还需要直观感受时间序列数据。

感谢Pandas与Matplotlib的整合:只需使用plot()函数并传递相关参数即可。 在BigQuant平台上,你也可以选择T.plot函数绘制交互式图表。

让我们运行下面的代码:

In [2840]:
T.plot(df.set_index('date')[['close']],title= '收盘价',chart_type = 'line') #显示收盘价

如果您想了解更多关于绘图知识,请查看数据图表可视化

量化相关的一些简单知识

在本节的其余部分,您将了解收益率,滚动窗口,波动率和普通最小二乘回归(OLS)的更多信息。

收益率

简单的每日百分比变化不考虑股息和其他因素,代表股票在一天交易中的价值变化百分比。 计算每天的百分比变化很容易,使用Pandas中pct_change()函数即可。

In [7]:
import numpy as np # 将 `numpy` 导入为 `np`
daily_close = df.set_index('date')[['close']] #获取每日收盘价
daily_pct_change = daily_close.pct_change() # 每日收益
daily_pct_change.fillna(0, inplace=True)# 将NA的值替换为0
print(daily_pct_change.tail()) #输出日收益
               close
date                
2017-07-24  0.009721
2017-07-25  0.000335
2017-07-26 -0.013244
2017-07-27  0.004750
2017-07-28  0.021208
In [8]:
daily_log_returns = np.log(daily_close.pct_change()+1) # 日对数收益率
print(daily_log_returns.tail()) # 输出日对数收益率
               close
date                
2017-07-24  0.009674
2017-07-25  0.000335
2017-07-26 -0.013332
2017-07-27  0.004738
2017-07-28  0.020987

计算每日百分比变化可以更好地了解收益率随时间的增长。

但每月或每季度的收益率如何计算呢?在这种情况下,您可以使用resample()。

In [9]:
monthly = daily_close.resample('BM').apply(lambda x:x[-1]) # 将`df`重新采样到经营月,获取最后一个时间
print(monthly.pct_change()) # 计算每月百分比的变化
               close
date                
2017-01-31       NaN
2017-02-28  0.029731
2017-03-31  0.089381
2017-04-28  0.070194
2017-05-31  0.071249
2017-06-30  0.065268
2017-07-31  0.041083
In [10]:
quarter = daily_close.resample('4M').mean() # 将‘df’重新采样到季度,获取每个月的平均值
print(quarter.pct_change()) # 计算季度百分比的变化
               close
date                
2017-01-31       NaN
2017-05-31  0.117966
2017-09-30  0.200511

使用pct_change()非常方便,但也会隐去每日百分比是如何计算的。 如果需要详细计算过程,你可以使用Pandas的shift()功能:将daily_close值除以daily_close.shift(1) -1。

提示:将以下代码的结果与之前获得的结果进行比较,以清楚地看出两种方法之间的差异。

In [11]:
daily_pct_change = daily_close/daily_close.shift(1)-1 # 计算每日百分比变化
print(daily_pct_change.tail()) # 显示数据
               close
date                
2017-07-24  0.009721
2017-07-25  0.000335
2017-07-26 -0.013244
2017-07-27  0.004750
2017-07-28  0.021208

提示:在Pandas shift()功能的帮助下计算日对数收益率。

In [12]:
daily_log_returns = np.log(daily_close/daily_close.shift(1))

作为参考,每日百分比变化的计算基于以下公式: $$ {r}_{t} = \frac{{p}_{t}}{{p}_{t-1}}-1$$ 其中:$p$ 是价格,$t$ 是时间(在这种情况下为一天),$r$ 是收益。 另外,你可以绘制以下的分布:

In [13]:
import matplotlib.pyplot as plt
daily_pct_change.hist(bins = 50) # 绘制 `daily_pct_change`的直方图
plt.show() # 显示图表
print(daily_pct_change.describe()) # 输出汇总统计
            close
count  138.000000
mean     0.002878
std      0.013579
min     -0.035588
25%     -0.006381
50%      0.001254
75%      0.009894
max      0.051859

日收益累积率有助于确定定期投资的价值,您可以使用每日百分比变化值来计算日累积收益率:

In [14]:
cum_daily_return = (1+daily_pct_change).cumprod() # 计算累积的每日收益
print(cum_daily_return.tail()) # 输出累计的每日收益
T.plot(cum_daily_return,chart_type='line') # 绘制累积的每日收益图
               close
date                
2017-07-24  1.449732
2017-07-25  1.450218
2017-07-26  1.431011
2017-07-27  1.437808
2017-07-28  1.468301

使用resample()功能计算月累积收益率:

In [15]:
cum_monthly_return = cum_daily_return.resample('M').mean() # 将日累积收益重新组合,取月累积收益平均值
print(cum_monthly_return) # 输出月累积收益
               close
date                
2017-01-31  1.043207
2017-02-28  1.052696
2017-03-31  1.122679
2017-04-30  1.199808
2017-05-31  1.277832
2017-06-30  1.395330
2017-07-31  1.398623

如何计算收益率是一项有价值的技巧,但是当你不将其与其他股票进行比较时,这些数字并没有太大的意义。

这就是为什么你会经常看到两个或更多个股票进行比较的例子。 在本节的其余部分,你将从平台获取更多财务数据,以便计算每日百分比变化并比较结果。

获取更多数据是首要任务。查看下面的代码,将四只股票的数据加载到一个大的DataFrame中:

In [16]:
# 获取多只股票的历史数据
instruments = ['000001.SZA','000002.SZA','000004.SZA','000005.SZA',] #确认股票池代码
all_data = D.history_data(instruments, 
               start_date='2017-01-01', 
               end_date='2017-06-28', 
               fields=['open', 'high','low','close'], 
               period=None, 
               groupped_by_instrument= False) #获取四只股票历史数据
all_data = all_data[['instrument','date','open','high','low','close']].sort_values(['instrument','date'],ascending = [True,True]) #按照需要对数据组织列名并进行排序
print(all_data.head()) #显示数据进行观察
    instrument       date        open        high         low       close
0   000001.SZA 2017-01-03  954.347656  961.680786  952.252502  959.585571
4   000001.SZA 2017-01-04  958.538025  961.680786  957.490417  959.585571
8   000001.SZA 2017-01-05  960.633179  961.680786  958.538025  960.633179
12  000001.SZA 2017-01-06  960.633179  960.633179  954.347656  956.442871
16  000001.SZA 2017-01-09  956.442871  960.633179  954.347656  958.538025

查看数据如下: Alt text

进行下一步工作:

In [17]:
daily_close_px = all_data.pivot('date','instrument','close') #转换为所需的dataframe排列
daily_pct_change = daily_close_px.pct_change() # 计算 `daily_close_px`的日变化百分比
daily_pct_change.hist(bins=50,
                      sharex=True,
                      sharey=True,
                      figsize=(12,8)) # 绘制分布图
plt.show() # 显示图表

另一个有用的图是散点图矩阵(scatter matrix):

In [18]:
pd.scatter_matrix(daily_pct_change,diagonal = 'kde', alpha = 0.1,figsize = (12,8))
plt.show()

恭喜你!你已经成功地完成了第一项——量化交易,现在我们来学习第二项:滚动窗口。

滚动窗口

当您需要在特定时间段上计算统计信息时,需要使用滚动窗口。 有很多的Pandas函数来计算滚动窗口,例如rolling_mean(),rolling_std()等。 但是最好用的功能组合是rolling()搭配使用mean()或std()。

但滚动窗口对你而言有什么意义? 确切的意义取决于获得的统计数据。例如,滚动平均值可平滑短期波动,并突出长期数据趋势。

In [19]:
close_px = daily_close_px[['000001.SZA']]#截取一只股票的数据
moving_avg = close_px.rolling(window=10).mean()# 计算滚动平均值
print(moving_avg[-5:])# 输出结果
instrument  000001.SZA
date                  
2017-06-22  955.080994
2017-06-23  956.128577
2017-06-26  958.118988
2017-06-27  960.633185
2017-06-28  964.299719
In [20]:
moving_avg = pd.rolling_mean(close_px,10) #另一种语法

在T.plot的帮助下,了解移动平均值及其实际意义:

In [21]:
asset1 = pd.DataFrame(data= None,
                      columns = ['close','5','10'],
                      index=daily_close_px.index)
asset1['close'] = daily_close_px['000001.SZA']
asset1['5日MA'] = pd.rolling_mean(asset1['close'],5)# 短期滚动窗口滚动均值
asset1['20日MA'] = pd.rolling_mean(asset1['close'],20)# 长期滚动窗口滚动均值
T.plot(asset1[['close','5日MA','20日MA']],chart_type='line')

波动率计算

股票的波动性是衡量股票在特定时间段内的收益率方差变化的度量。 将股票的波动与另一种股票进行比较,以获得风险较低的投资判断,所以检查股票在整体市场中的波动情况是很常见的。

一般来说,波动性越高,该股票的投资风险越大,从而导致投资于另一方。 波动率计算如下:

In [2856]:
min_periods = 10 # 定义要考虑的时间段的最小值
vol = daily_pct_change.rolling(min_periods).std() * np.sqrt(min_periods) # 计算波动率
T.plot(vol,chart_type='line')

可以从代码中清楚地看到这一点:

窗口值的改变将会改变整体结果,如果使窗口更宽并使之min_periods变大,结果将变得不具有代表性。 如果窗口更窄,结果会更接近于标准偏差。

根据数据采样频率判断出正确的窗口大小是一门技巧。

普通最小二乘回归(OLS)

通过传统的回归分析(如普通最小二乘回归(OLS))对数据进行更多的统计分析。在此过程中,需要利用statsmodels库。

In [2857]:
import statsmodels.api as sm
from statsmodels import regression

# 获得贵州茅台、中国平安的数据
start_date = '2014-01-01'
end_date = '2015-01-01' #设定时间段
asset1 = D.history_data('600519.SHA',start_date,end_date,fields=['close']).set_index('date')['close']
asset2 =D.history_data('601318.SHA',start_date,end_date,fields=['close']).set_index('date')['close'] #获取历史数据
model = regression.linear_model.OLS(asset1, sm.add_constant(asset2)).fit() #做回归计算,其中asset2作为自变量,asset1作为因变量
print(model.summary()) # 显示模型参数
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                  close   R-squared:                       0.442
Model:                            OLS   Adj. R-squared:                  0.440
Method:                 Least Squares   F-statistic:                     192.5
Date:                Fri, 11 Aug 2017   Prob (F-statistic):           1.27e-32
Time:                        19:19:25   Log-Likelihood:                -1426.1
No. Observations:                 245   AIC:                             2856.
Df Residuals:                     243   BIC:                             2863.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const        474.6328     31.689     14.978      0.000     412.212     537.054
close          9.4189      0.679     13.873      0.000       8.082      10.756
==============================================================================
Omnibus:                       45.301   Durbin-Watson:                   0.048
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               64.744
Skew:                          -1.157   Prob(JB):                     8.73e-15
Kurtosis:                       3.992   Cond. No.                         283.
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

绘制普通最小二乘回归:

In [2858]:
plt.plot(asset2,asset1,'r.') # 绘制实际数据
ax = plt.axis() # 将一个值添加到绘图
x = np.linspace(ax[0],ax[1]+0.01) # 初始化 `x`
plt.plot(x,model.params[0]+model.params[1]*x,'b',lw=2) # 绘制回归线
plt.grid(True)
plt.axis('tight')
plt.xlabel('asset2')
plt.ylabel('asset1') # 自定义图表
plt.show #显示图表
Out[2858]:
<function matplotlib.pyplot.show>

使用滚动关联的返回值作为交叉检查结果的一种方式,结果如下:

In [2859]:
T.plot(asset1.rolling(window=20).corr(asset2),chart_type='line')# 绘制滚动相关性图

用Python构建交易策略

Ps:本文介绍一种比较原始的用Python构建交易策略的方法,目的是便于大家认识Python和了解股票策略。如果想要快捷开发策略,可以使用BigQuant策略回测引擎。本文的策略如果使用BigQuant回测机制仅需 30行以内代码,见:金叉死叉策略

首先介绍一下常用的交易策略:趋势策略。

典型交易策略

趋势策略的假设基础为:价格的移动将继续朝着目前的方向发展。 换句话说,股票有动力延续向上或向下的趋势。

这个策略的一些例子是金叉死叉、多均线策略和海龟交易策略:

现在着手于开发第一个交易策略!

一个简单的交易策略

从量化交易的“Hello World!”:金叉死叉策略开始: 创建两个简单移动平均线(SMA),具有不同的回溯期,分别假设为40天和100天。 如果短期移动平均线超过长期移动平均线,选择长期持有; 如果长期移动平均线超过短期移动平均线,则退出。

当选择长期持有,策略认为股票价格会上涨,将来会以更高的价格卖出(=买入信号); 当选择短期持有,策略判断卖出股票,期望以更低的价格买回来,实现利润(=卖出信号)。

策略分解如下:

  • 定义两个不同的回溯期:短期窗口和长期窗口。设置两个变量并为每个变量分配一个整数, 确保分配给短期窗口的整数小于分配给长期窗口变量的整数。

  • 创建一个空的DataFrame:signals,确保复制数据的索引,以便计算数据的每日买入或卖出信号。

  • 在signals DataFrame中创建一个列,该列将被命名signal并将此列中所有行的初始值设置为0.0。

  • 创建一组短期和长期的简单移动平均线。

  • 创建信号:当短期滚动平均线跨越长期滚动平均线时,创建一个信号,但只能在大于最短滚动平均窗口的周期内创建信号。 当条件为真时,初始化值0.0的signal列将赋值1.0,创建一个“信号”。 如果条件为假,则0.0保留原始值,不生成信号。

  • 收集信号之差,以产生实际的交易订单。

用Python构建该交易策略的代码:

In [23]:
# Building a trading strategy with Python
import pandas as pd
import numpy as np
# 以立讯精密为例
df = D.history_data(instruments = ['002475.SZA'],
                     start_date = '2010-01-01',
                     end_date = '2017-08-10',
                     fields = ['open','high','low','close']
                     ) # 获取历史数据
df = df[['date','open','high','low','close']].set_index('date') # 调整列并设置索引为时间值
short_window = 40 # 设置短期窗口
long_window = 100 # 设置长期窗口
signals = pd.DataFrame(index = df.index) # 构建dataframe
signals['signal'] = 0.0 # 初始化signal列
signals['short_mavg'] = df['close'].rolling(window = short_window,
                                             min_periods = 1,
                                             center = False).mean() # 计算短期滚动平均数据
signals['long_mavg'] = df['close'].rolling(window=long_window,
                                             min_periods = 1,
                                             center = False).mean() #计算长期滚动平均数据
signals['signal'][short_window:] = np.where(signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:],1.0,0.0) #判断平均值大小
signals['positions'] = signals['signal'].diff() # 计算信号
print(signals.head(12))
            signal  short_mavg  long_mavg  positions
date                                                
2010-09-15     0.0   39.990002  39.990002        NaN
2010-09-16     0.0   40.240002  40.240002        0.0
2010-09-17     0.0   39.723334  39.723334        0.0
2010-09-20     0.0   39.200001  39.200001        0.0
2010-09-21     0.0   38.364001  38.364001        0.0
2010-09-27     0.0   37.998334  37.998334        0.0
2010-09-28     0.0   37.540000  37.540000        0.0
2010-09-29     0.0   37.382500  37.382500        0.0
2010-09-30     0.0   37.415556  37.415556        0.0
2010-10-08     0.0   37.487000  37.487000        0.0
2010-10-11     0.0   37.346364  37.346364        0.0
2010-10-12     0.0   37.146667  37.146667        0.0

绘制交易策略的结果:

In [2861]:
import matplotlib.pyplot as plt

fig = plt.figure() # 初始化
ax1 = fig.add_subplot(111, ylabel = "Price in ¥") #设置Y标签
df['close'].plot(ax = ax1,color = 'r', lw = 2.,figsize=(12,8)) # 绘制收盘价曲线
signals[['short_mavg','long_mavg']].plot(ax=ax1,lw=2.) #绘制长周期与短周期收盘价移动平均线
ax1.plot(signals.loc[signals.positions == 1.0].index,
        signals.short_mavg[signals.positions ==1.0],
        '^', markersize =10, color = 'm') # 绘制买入信号点
ax1.plot(signals.loc[signals.positions == -1.0].index,
         signals.short_mavg[signals.positions == -1.0],
         'v', markersize = 10, color = 'k') # 绘制卖出信号点
plt.show() # 显示图片

一个简单回测的实现

下面将介绍如何创建可以生成订单并管理损益的投资组合:

  • 创建变量initial_capital:设置初始资本

  • 创建DataFrame 'positions':从DataFrame 'signals' 复制索引,即时间范围。

  • 在DataFrame中创建一个新列df。 在信号为1的时候,短滚动平均线跨越长滚动平均线,购买100股。 在信号为0的时候,数值为0。

  • 创建DataFrame 'portfolio':存储未平仓头寸的市值。

  • 创建DataFrame 'pos_diff':存储持有股票数量

  • 创建一个portfolio的新列holdings:存储购买的头寸-股票的价值乘以“close”价格。

  • portfolio中cash列:initial_capital扣除你的持有资本(你支付购买股票的价格)来计算得到现金值

  • portfolio中total列:现金和拥有的持有量的总和。

  • portfolio中returns列:存储收益值。

In [2867]:
initial_capital = float(30000.0) # 初始化资本
positions = pd.DataFrame(index=signals.index).fillna(0.0) # 构建 'positions' DataFrame
positions['df']=100*signals['signal'] # 购买股票
portfolio = positions.multiply(df['close'],axis = 0) # 计算股票总值
del portfolio['df'] #删除多余列
pos_diff = positions.diff()# 存储持有股票的数量
portfolio['holdings'] = (positions.multiply(df['close'],axis=0)).sum(axis=1) # 存储“持有市值”
portfolio['cash'] = initial_capital - (pos_diff.multiply(df['close'],axis=0)).sum(axis=1).cumsum() # 存储现金值
portfolio['total'] = portfolio['cash']+portfolio['holdings'] # 总价值
portfolio['returns'] = portfolio['total'].pct_change() # 计算收益率
print(portfolio.tail()) # 显示计算结果
                holdings         cash         total   returns
date                                                         
2017-08-04  30473.422241  8620.246887  39093.669128  0.007072
2017-08-07  30564.935303  8620.246887  39185.182190  0.002341
2017-08-08  29817.587280  8620.246887  38437.834167 -0.019072
2017-08-09  30381.909180  8620.246887  39002.156067  0.014681
2017-08-10  30930.981445  8620.246887  39551.228333  0.014078

绘制投资结果:

In [2868]:
fig = plt.figure() # 初始化
ax1 = fig.add_subplot(111,ylabel = 'Portfolio value in ¥') #设置y轴标签
portfolio['total'].plot(ax=ax1,lw=2.,figsize = (12,8)) #绘制总资产曲线
ax1.plot(portfolio.loc[signals.positions == 1.0].index,
        portfolio.total[signals.positions == 1.0],
        '^', markersize = 10, color = 'm') # 绘制买入点
ax1.plot(portfolio.loc[signals.positions == -1.0].index,
        portfolio.total[signals.positions == -1.0],
        'v', markersize = 10, color = 'k')# 绘制卖出点
plt.show()#显示图片

改善交易策略

一个简单的交易算法通过回测发现策略并不完善,还需进一步改进。 你可以使用一个或多个算法来连续地改进模型,例如KMeans,KNN,分类,回归树和遗传算法。 除了算法之外,您可以通过使用多因子组合来改善策略。

评估金叉死叉策略

使用Pandas来计算一些指标,以进一步判断交易策略。 首先,使用夏普比率了解投资组合的收益率是否匹配你对投资的预期,或者衡量风险。

理想的情况当然是收益率相当可观,但风险是尽可能的小。这就是为什么投资组合的夏普比率越大越好。 通常,投资者可以接受大于1的比率,2是非常好的,3是优秀的。 算法如下:

In [2864]:
returns = portfolio['returns'] #获取收益
sharpe_ratio = np.sqrt(246)*(returns.mean()/returns.std()) # 年化夏普比率,交易日设定为每年246天
print(sharpe_ratio)# 显示夏普比率
0.350627049295

接下来,还可以计算最大回撤,用于测量投资组合最大降幅。 该指标表示基于某种策略选择投资组合的风险。

In [2865]:
window = 252 # 定义时间窗口为246个交易日
rolling_max = df['close'].rolling(window, min_periods=1).max() # 计算时间窗口收盘价最大值
daily_drawdown = df['close']/rolling_max - 1.0 #计算时间窗口每日最大回撤
max_daily_drawdown = daily_drawdown.rolling(window, min_periods=1).min() #获取最大回撤最小值
daily_drawdown.plot()
max_daily_drawdown.plot()
plt.show() # 显示图片

下一步是复合年增长率(CAGR),为您提供在该时期内的恒定收益率。计算公式如下:

$$ (EV/BV)^{1/n}-1 $$

请注意:数据以日为周期,因此”1“被调整为365天(1年)。

In [2866]:
days = (df.index[-1] - df.index[0]).days # 得到天数
cagr = ((((df['close'][-1]) / df['close'][0])) ** (365.0/days)) - 1 # 计算年化收益率
print(cagr)  # 显示年化收益率
0.344720617431

接下来的计划?

你已经浏览完Python在金融上应用的教程。

还有更多内容等待你的发现,敬请访问BigQuant官方网站!


(m15567441124) #2

你好,我想回测沪深300ETF的数据,使用510300.OFA带入,运行显示unknonw market suffix: {‘OFA’},请问应该如何解决?