You are on page 1of 93

目 录

双均线策略(期货)
Dual Thrust(期货)
R-Breaker(期货)
菲阿里四价(期货)
小市值(股票)
布林线均值回归(股票)
alpha对冲(股票+期货)
多因子选股(股票)
网格交易(期货)
指数增强(股票)
跨品种套利(期货)
跨期套利(期货)
日内回转交易(股票)
做市商交易(期货)
海龟交易法(期货)
行业轮动(股票)
机器学习(股票)

本文档使用 掘金量化 构建 -1-


双均线策略(期货)

双均线策略(期货)
双均线策略
1. 原理
均线的“前世今生”
均线理论为什么有效?
均线理论的缺陷
均线理论的改进
2. 策略逻辑
3. 策略代码
4. 回测结果与稳健性分析

双均线策略

1. 原理

均线的“前世今生”

均线,一个进行形态分析时总也绕不过去的指标。

均线最早由美国投资专家Joseph E.Granville(格兰威尔)于20世纪中期提出,现在仍然广泛为人们使用,成为判断买卖信
号的一大重要指标。从统计角度来说,均线就是历史价格的平均值,可以代表过去N日股价的平均走势。

1962年7月,Joseph E.Granville在他的书中提出了著名的Granville八大买卖法则。只利用股价和均线即可进行择时,
方法简单有效,一经提出,迅速受到市场追捧。尤其是其中的金叉和死叉信号,更是沿用至今。

Granville 八大法则其中有四条是用于判断买进时机,另外四条是用于判断卖出时机。买进和卖出法则一一对应,分布在高
点的左右两侧(除买4和卖4以外)。法则内容如下所示:

买1:均线整体上行,股价由下至上上穿均线,此为黄金交叉,形成第一个买点。
买2:股价出现下跌迹象,但尚未跌破均线,此时均线变成支撑线,形成第二个买点。
买3:股价仍处于均线上方,但呈现急剧下跌趋势。当跌破均线时,出现第三个买点。
买4:(右侧)股价和均线都处于下降通道,且股价处于均线下方,严重远离均线,出现第四个买点。

卖1:均线由上升状态变为缓慢下降的状态,股价也开始下降。当股价跌破均线时,此为死亡交叉,形成第一个卖点。
卖2:股价仍处于均线之下,但股价开始呈现上涨趋势,当股价无限接近均线但尚未突破时,此时均线变成阻力线,形成第二个
卖点。
卖3:股价终于突破均线,处于均线上方。但持续时间不长,股价开始下跌,直至再一次跌破均线,此为第三个卖点。
卖4:(左侧)股价和均线都在上涨,股价上涨的速度远快于均线上涨的速度。当股价严重偏离均线时,出现第四个卖点。
(部分资料来源于百度百科)

本文档使用 掘金量化 构建 -2-


双均线策略(期货)

(图片来源于网络)

均线理论为什么有效?

Shiller(1981)在研究中发现,资产的长期价格呈现均值回复的特征,即从长期来看,资产的价格会回归均值。这也是均线
理论被广泛应用的前提。

均线理论的缺陷

均线归根到底是一种平均值,平均值在应用过程中存在最大的问题就是其滞后性。当出现买入卖出信号时,最佳时机早已过
去。举例来说,如果A股票最新价格出现了较大的涨幅,股价和均线都上涨,但均线的速度慢于股价上涨速度。此时,从形态上
来看,金叉出现,为买入信号。次日,股价回调,股价下降的速度快于均线下降的速度,形成死叉,为卖点。这样一买一卖不
仅没有盈利,反而出现亏损。

均线理论的改进

针对均线的缺点,市场上提出了各种各样的改进方法。

1.对均线的计算方法进行改正。

加权移动平均线是在移动平均线的基础上按照时间进行加权。越靠近当前日期的价格对未来价格的影响越大,赋予更大的权
重;越远离当前日期价格,赋予越小的权重。

2.调整均线周期

利用不同周期均线得到的结果也不同。许多有经验的投资者发现,在不同的市场中,有些均线的效果显著优于其他周期均线。
有些长线投资者还会将股价替换成短周期均线进行趋势判断。

2. 策略逻辑
第一步:获取数据,计算长短期均线
第二步:设置交易信号

当短期均线由上向下穿越长期均线时做空
当短期均线由下向上穿越长期均线时做多

本文档使用 掘金量化 构建 -3-


双均线策略(期货)

回测数据: SHFE.rb2101的60s频度bar数据
回测时间: 2020-04-01 到 2020-05-31
回测初始资金:3万

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import
3. from gm.api import *
4. import talib
5.
6.
7. '''
8. 本策略以SHFE.rb2101为交易标的,根据其一分钟(即60s频度)bar数据建立双均线模型,
9. 短周期为20,长周期为60,当短期均线由上向下穿越长期均线时做空,
10. 当短期均线由下向上穿越长期均线时做多,每次开仓前先平掉所持仓位,再开仓。
11. 注:为了适用于仿真和实盘,在策略中增加了一个“先判断是否平仓成功再开仓”的判断逻辑,以避免出现未平仓成功,
可用资金不足的情况。
12. 回测数据为:SHFE.rb2101的60s频度bar数据
13. 回测时间为:2020-04-01 09:00:00到2020-05-31 15:00:00
14. '''
15.
16.
17. def init(context):
18. context.short = 20 # 短周期均线
19. context.long = 60 # 长周期均线
20. context.symbol = 'SHFE.rb2101' # 订阅交易标的
21. context.period = context.long + 1 # 订阅数据滑窗长度
22. context.open_long = False # 开多单标记
23. context.open_short = False # 开空单标记
24. subscribe(context.symbol, '60s', count=context.period) # 订阅行情
25.
26.
27. def on_bar(context, bars):
28. # 获取通过subscribe订阅的数据
29. prices = context.data(context.symbol, '60s', context.period, fields='close')
30.
31. # 利用talib库计算长短周期均线
32. short_avg = talib.SMA(prices.values.reshape(context.period), context.short)
33. long_avg = talib.SMA(prices.values.reshape(context.period), context.long)
34.
35. # 查询持仓
36. position_long = context.account().position(symbol=context.symbol, side=1)
37. position_short = context.account().position(symbol=context.symbol, side=2)
38.
39. # 短均线下穿长均线,做空(即当前时间点短均线处于长均线下方,前一时间点短均线处于长均线上方)
40. if long_avg[-2] < short_avg[-2] and long_avg[-1] >= short_avg[-1]:
41.
42. # 无多仓情况下,直接开空
43. if not position_long:
44. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
position_effect=PositionEffect_Open,
45. order_type=OrderType_Market)
46. print(context.symbol, '以市价单调空仓到仓位')

本文档使用 掘金量化 构建 -4-


双均线策略(期货)

47.
48. # 有多仓情况下,先平多,再开空(开空命令放在on_order_status里面)
49. else:
50. context.open_short = True
51.
52. # 以市价平多仓
53. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
position_effect=PositionEffect_Close,
54. order_type=OrderType_Market)
55. print(context.symbol, '以市价单平多仓')
56.
57. # 短均线上穿长均线,做多(即当前时间点短均线处于长均线上方,前一时间点短均线处于长均线下方)
58. if short_avg[-2] < long_avg[-2] and short_avg[-1] >= long_avg[-1]:
59.
60. # 无空仓情况下,直接开多
61. if not position_short:
62. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
position_effect=PositionEffect_Open,
63. order_type=OrderType_Market)
64. print(context.symbol, '以市价单调多仓到仓位')
65.
66. # 有空仓的情况下,先平空,再开多(开多命令放在on_order_status里面)
67. else:
68. context.open_long = True
69.
70. # 以市价平空仓
71. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
72. position_effect=PositionEffect_Close,
order_type=OrderType_Market)
73. print(context.symbol, '以市价单平空仓')
74.
75.
76. def on_order_status(context, order):
77.
78. # 查看下单后的委托状态
79. status = order['status']
80.
81. # 成交命令的方向
82. side = order['side']
83.
84. # 交易类型
85. effect = order['position_effect']
86.
87. # 当平仓委托全成后,再开仓
88. if status == 3:
89.
90. # 以市价开空仓,需等到平仓成功无仓位后再开仓
91. # 如果无多仓且side=2(说明平多仓成功),开空仓
92. if effect == 2 and side == 2 and context.open_short:
93. context.open_short = False
94. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
position_effect=PositionEffect_Open,
95. order_type=OrderType_Market)
96. print(context.symbol, '以市价单调空仓到仓位')
97.
98. # 以市价开多仓,需等到平仓成功无仓位后再开仓

本文档使用 掘金量化 构建 -5-


双均线策略(期货)

99. # 如果无空仓且side=1(说明平空仓成功),开多仓
100. if effect == 2 and side == 1 and context.open_long:
101. context.open_long = False
102. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
position_effect=PositionEffect_Open,
103. order_type=OrderType_Market)
104. print(context.symbol, '以市价单调多仓到仓位')
105.
106.
107. if __name__ == '__main__':
108. '''
109. strategy_id策略ID,由系统生成
110. filename文件名,请与本文件名保持一致
111. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
112. token绑定计算机的ID,可在系统设置-密钥管理中生成
113. backtest_start_time回测开始时间
114. backtest_end_time回测结束时间
115. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
116. backtest_initial_cash回测初始资金
117. backtest_commission_ratio回测佣金比例
118. backtest_slippage_ratio回测滑点比例
119. '''
120. run(strategy_id='strategy_id',
121. filename='main.py',
122. mode=MODE_BACKTEST,
123. token='token_id',
124. backtest_start_time='2020-04-01 09:00:00',
125. backtest_end_time='2020-05-31 15:00:00',
126. backtest_adjust=ADJUST_NONE,
127. backtest_initial_cash=10000000,
128. backtest_commission_ratio=0.0001,
129. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金3万,手续费率为0.01%,滑点比率为0.01%,得到的回测结果如下图:

本文档使用 掘金量化 构建 -6-


双均线策略(期货)

策略整体收益率5.75%,年化收益率为39.15%,同期沪深300收益率为5.22%,策略跑赢沪深300.最大回撤为10.32%。

为了探究该策略在不同回测期以及不同品种的适用情况,对策略进行调整。调整范围主要包括:标的、回测期、均线周期,调
整结果如下表所示:

标的 回测期 均线周期 年化收益率 最大回撤

SHFE.rb2101 2020.04.01-2020.05.31 20/60 39.15% 10.32%

SHFE.rb2101 2020.06.01-2020.08.30 20/60 -29.19% 17.82%

SHFE.rb2101 2020.08.31-2020.10.31 20/60 -72.29% 17.12%

SHFE.rb2101 2020.04.01-2020.05.31 10/60 -79.71% 15.80%

SHFE.rb2101 2020.04.01-2020.05.31 30/60 -39.16% 10.59%

SHFE.rb2101 2020.04.01-2020.05.31 20/90 11.97% 5.35%

SHFE.rb2101 2020.04.01-2020.05.31 30/90 -1.87% 6.60%

SHFE.ag2101 2020.04.01-2020.05.31 20/60 -136.19% 37.67%

根据上表可以看出,对于不同的标的、回测期、均线周期,双均线策略的收益情况差异较大。即使相同标的、相同均线周期,
不同回测期收益情况也会出现较大差异。在应用时要注意风险管理,避免出现短期过拟合现象。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 -7-


Dual Thrust(期货)

Dual Thrust(期货)
Dual Thrust
1. 原理
2. 策略逻辑
3. 策略代码
4. 回测结果与稳健性分析

Dual Thrust

1. 原理

由Michael Chalek在20世纪80年代开发的Dual Thrust策略是一个趋势跟踪策略。

其核心思想是定义一个区间,区间的上界和下界分别为支撑线和阻力线。当价格超过上界时,如果持有空仓,先平再开多;如
果没有仓位,直接开多。当价格跌破下界时,如果持有多仓,则先平仓,再开空仓;如果没有仓位,直接开空仓。

上下界的设定是交易策略的核心部分。在计算上下界时共用到:最高价、最低价、收盘价、开盘价四个参数。

公式如下:

Range = Max(HH-LC,HC-LL)
上限:Open + K1 Range
下限:Open + k2 Range

K1 和 K2一般根据自己经验以及回测结果进行优化。

2. 策略逻辑

第一步:设置参数N、k1、k2
第二步:计算HH、LC、HC、LL
第三步:计算range
第四步:设定做多和做空信号

回测标的:SHFE.rb2010
回测期:2020-02-07 15:00:00 到 2020-04-15 15:00:00
回测初始资金:3万

注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import
3. from gm.api import *
4.
5.
6. """
7. Dual Thrust是一个趋势跟踪系统
8. 计算前N天的最高价-收盘价和收盘价-最低价。然后取这2N个价差的最大值,乘以k值。把结果称为触发值。

本文档使用 掘金量化 构建 -8-


Dual Thrust(期货)

9. 在今天的开盘,记录开盘价,然后在价格超过上轨(开盘+触发值)时马上买入,或者价格低于下轨(开盘-触发值)
时马上卖空。
10. 没有明确止损。这个系统是反转系统,也就是说,如果在价格超过(开盘+触发值)时手头有空单,则平空开多。
11. 同理,如果在价格低于(开盘-触发值)时手上有多单,则平多开空。
12. 选用了SHFE的rb2010 在2020-02-07 15:00:00 到 2020-04-15 15:00:00' 进行回测。
13. 注意:
14. 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。
15. 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交
16. """
17.
18.
19. # 策略中必须有init方法
20. def init(context):
21.
22. # 设置要进行回测的合约(可以在掘金终端的仿真交易中查询标的代码)
23. context.symbol = 'SHFE.rb2010' # 订阅&交易标的, 此处订阅的是上期所的螺纹钢 2010
24.
25. # 设置参数
26. context.N = 5
27. context.k1 = 0.2
28. context.k2 = 0.2
29.
30. # 获取当前时间
31. time = context.now.strftime('%H:%M:%S')
32.
33. # 如果策略执行时间点是交易时间段,则直接执行algo定义buy_line和sell_line,以防直接进入on_bar()
导致context.buy_line和context.sell_line未定义
34. if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
35. algo(context)
36.
37. # 如果是交易时间段,等到开盘时间确保进入algo()
38. schedule(schedule_func = algo, date_rule = '1d', time_rule = '09:00:00')
39. schedule(schedule_func = algo, date_rule = '1d', time_rule = '21:00:00')
40.
41. # 只需要最新价,所以只需要订阅一个, 如果用tick,次数太多,用一分钟线代替
42. subscribe(symbols=context.symbol, frequency='60s', count = 1)
43.
44.
45. def algo(context):
46. # 取历史数据
47. data = history_n(symbol=context.symbol, frequency='1d', end_time=context.now,
48. fields='symbol,open,high,low,close', count=context.N + 1, df=True)
49. # 取开盘价
50. # 回测模式下,开盘价可以直接用history_n取到
51. if context.mode == 2:
52. # 获取当天的开盘价
53. current_open = data['open'].loc[context.N]
54. # 去掉当天的实时数据
55. data.drop(context.N, inplace = True)
56. # 如果是实时模式,开盘价需要用current取到
57. else:
58. # 获取当天的开盘价
59. current_open = current(context.symbol)[0]['open']
60.
61. # 计算Dual Thrust 的上下轨

本文档使用 掘金量化 构建 -9-


Dual Thrust(期货)

62. HH = data['high'].max()
63. HC = data['close'].max()
64. LC = data['close'].min()
65. LL = data['low'].min()
66. range = max(HH - LC, HC - LL)
67. context.buy_line = current_open + range * context.k1 # 上轨
68. context.sell_line = current_open - range * context.k2 # 下轨
69.
70.
71. def on_bar(context, bars):
72. # 取出订阅的这一分钟的bar
73. bar = bars[0]
74. # 取出买卖线
75. buy_line = context.buy_line
76. sell_line = context.sell_line
77.
78. # 获取现有持仓
79. position_long = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
80. position_short = context.account().position(symbol=context.symbol,
side=PositionSide_Short)
81.
82. # 交易逻辑部分
83. # 如果超过range的上界
84. if bar.close > buy_line:
85. if position_long: # 已经持有多仓,直接返回
86. return
87. elif position_short: # 已经持有空仓,平仓再做多。
88. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
89. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
90. print('市价单平空仓', context.symbol)
91. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
92. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
93. print('市价单开多仓', context.symbol)
94. else: # 没有持仓时,市价开多。
95. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
96. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
97. print('市价单开多仓', context.symbol)
98. # 如果低于range的下界
99. elif bar.close < sell_line:
100. if position_long: # 已经持有多仓, 平多再开空。
101. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
102. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
103. print('市价单平多仓', context.symbol)
104. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
105. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
106. print('市价单开空仓', context.symbol)
107. elif position_short: # 已经持有空仓,直接返回。
108. return

本文档使用 掘金量化 构建 - 10 -
Dual Thrust(期货)

109. else: # 没有持仓,直接开空


110. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
111. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
112. print('市价单开空仓', context.symbol)
113.
114.
115. if __name__ == '__main__':
116. '''
117. strategy_id策略ID,由系统生成
118. filename文件名,请与本文件名保持一致
119. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
120. token绑定计算机的ID,可在系统设置-密钥管理中生成
121. backtest_start_time回测开始时间
122. backtest_end_time回测结束时间
123. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
124. backtest_initial_cash回测初始资金
125. backtest_commission_ratio回测佣金比例
126. backtest_slippage_ratio回测滑点比例
127. '''
128. run(strategy_id='strategy_id',
129. filename='main.py',
130. mode=MODE_BACKTEST,
131. token='token_id',
132. backtest_start_time='2020-02-07 15:00:00',
133. backtest_end_time='2020-04-15 15:00:00',
134. backtest_initial_cash= 30000,
135. backtest_commission_ratio=0.0001,
136. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金3万,手续费率为0.01%,滑点比率为0.01%,N=5,K1=0.2,K2=0.2,回测期为2020-02-07 到 2020-04-
15时,回测结果如下图所示。

本文档使用 掘金量化 构建 - 11 -
Dual Thrust(期货)

回测期策略累计收益率为9.88%,年化收益率为52.29%。同期沪深300指数收益率为-2.63%,策略跑赢沪深300指数。最大回
撤为5.20%,夏普比率为2.63。

为了检验策略的稳健性,设置不同的k1、k2参数,并且设置不同的回测期,检验回测结果。回测结果如下表所示。

K1 K2 回测期 年化收益率 最大回撤

0.2 0.2 2020.02.07-2020.04.15 52.29% 5.20%

0.3 0.3 2020.02.07-2020.04.15 54.93% 8.80%

0.4 0.4 2020.02.07-2020.04.15 43.40% 4.58%

0.5 0.5 2020.02.07-2020.04.15 -22.15% 7.08%

0.5 0.5 2020.04.07-2020.06.15 6.43% 4.60%

0.5 0.5 2020.06.07-2020.09.15 -24.21% 9.80%

可以发现,在2020年2月-2020年4月,不论k1、k2如何设置,收益率均维持为正,回撤比较稳定。在其他月份,回测结果较
差。说明回测参数设置对于回测结果的影响较小,回测期对于回测结果的影响较大。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 12 -
R-Breaker(期货)

R-Breaker(期货)
R Breaker
1. 原理
1.1 原理
1.2 触发条件
1.3 背后逻辑解析
2、策略逻辑
3、策略代码
4、回测结果与稳健性分析

R Breaker

1. 原理

1.1 原理

R Breaker是一种日内回转交易策略,属于短线交易。日内回转交易是指当天买入或卖出标的后于当日再卖出或买入标的。日
内回转交易通过标的短期波动盈利,低买高卖,时间短、投机性强,适合短线投资者。

R Breaker主要分为分为反转和趋势两部分。空仓时进行趋势跟随,持仓时等待反转信号反向开仓。

由于我国A股采用的是“T+1”交易制度,为了方便起见,以期货为例演示R Breaker策略。

反转和趋势突破的价位点根据前一交易日的收盘价、最高价和最低价数据计算得出,分别为:突破买入价、观察卖出价、反转
卖出价、反转买入价、观察买入价和突破卖出价。计算方法如下:

指标计算方法

中心价位P = (H + C + L)/3

突破买入价 = H + 2P -2L

观察卖出价 = P + H - L

反转卖出价 = 2P - L

反转买入价 = 2P - H

观察买入价 = P - (H - L)

突破卖出价 = L - 2(H - P)

1.2 触发条件

空仓时:突破策略
空仓时,当盘中价格>突破买入价,则认为上涨的趋势还会继续,开仓做多;
空仓时,当盘中价格<突破卖出价,则认为下跌的趋势还会继续,开仓做空。

持仓时:反转策略
持多单时:当日内最高价>观察卖出价后,盘中价格回落,跌破反转卖出价构成的支撑线时,采取反转策略,即做空;
持空单时:当日内最低价<观察买入价后,盘中价格反弹,超过反转买入价构成的阻力线时,采取反转策略,即做多。

如下图所示:

本文档使用 掘金量化 构建 - 13 -
R-Breaker(期货)

1.3 背后逻辑解析

首先看一下这6个价格与前一日价格之间的关系。

反转卖出价和反转买入价
根据公式推导,发现这两个价格和前一日最高最低价没有确定的大小关系。

观察卖出价和观察买入价。
用观察卖出价 - 前一交易日最高价发现,(H+P-L)-H = P - L >0,说明观察卖出价>前一交易日最高价;同理可证,
观察买入价<前一交易日最低价。

突破买入价和突破卖出价
突破买入价>观察卖出价>前一交易日最高价,可以说明突破买入价>>前一交易日最高价。做差后可以发现,突破买入价 -
前一交易日最高价 = 2[(C-L)+(H-L)]/3。

用K线形态表示:

前一交易日K线越长,下影线越长,突破买入价越高。
前一交易日K线越长,上影线越长,突破卖入价越高。

这样一来就可以解释R Breaker背后的逻辑了。

当今日的价格突破前一交易日的最高点,形态上来看会是上涨趋势,具备一定的开多仓条件,但还不够。若前一交易日的下影
线越长,说明多空方博弈激烈,多方力量强大。因此可以设置更高的突破买入价,一旦突破说明多方力量稳稳地占据了上风,
那么就有理由相信未来会继续上涨。同理可解释突破卖出价背后的逻辑。

持有多仓时,若标的价格持续走高,则在当天收盘之前平仓获利离场。若价格不升反降,跌破观察卖出价时,此时价格仍处于
前一交易日最高价之上,继续观望。若继续下跌,直到跌破反转卖出价时,平仓止损。

持有空仓时,若标的价格持续走低,则在当天收盘之前平仓获利离场。若价格不降反升,升至观察买入价时,此时价格仍处于
前一交易日最低价之下,继续观望。若继续上涨,直到升至反转买入价时,平仓止损。

2、策略逻辑

本文档使用 掘金量化 构建 - 14 -
R-Breaker(期货)

第一步:根据收盘价、最高价和最低价数据计算六个价位。
第二步:如果是空仓条件下,如果价格超过突破买入价,则开多仓;如果价格跌破突破卖出价,则开空仓。
第三步:在持仓条件下:

持多单时,当最高价超过观察卖出价,盘中价格进一步跌破反转卖出价,反手做多;
持空单时,当最低价低于观察买入价,盘中价格进一步超过反转买入价,反手做空。

第四步:接近收盘时,全部平仓。

回测标的:SHFE.rb2010
回测期:2019-10-1 15:00:00 到 2020-04-16 15:00:00
回测初始资金:100万
注意:若修改回测期,需要修改对应的回测标的。

3、策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import
3. import pandas as pd
4. from gm.api import *
5. from datetime import datetime, timedelta
6.
7.
8. """
9. R-Breaker是一种短线日内交易策略
10. 根据前一个交易日的收盘价、最高价和最低价数据通过一定方式计算出六个价位,从大到小依次为:
11. 突破买入价、观察卖出价、反转卖出价、反转买入、观察买入价、突破卖出价。以此来形成当前交易
12. 日盘中交易的触发条件。
13. 追踪盘中价格走势,实时判断触发条件。具体条件如下:
14. 突破
15. 在空仓条件下,如果盘中价格超过突破买入价,则采取趋势策略,即在该点位开仓做多。
16. 在空仓条件下,如果盘中价格跌破突破卖出价,则采取趋势策略,即在该点位开仓做空。
17. 反转
18. 持多单,当日内最高价超过观察卖出价后,盘中价格出现回落,且进一步跌破反转卖出价构成的支撑线时,采取反转策
略,即在该点位反手做空。
19. 持空单,当日内最低价低于观察买入价后,盘中价格出现反弹,且进一步超过反转买入价构成的阻力线时,采取反转策
略,即在该点位反手做多。
20. 设定止损条件。当亏损达到设定值后,平仓。
21.
22. 注意:
23. 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。
24. 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。
25. 3:本策略使用在15点收盘时全平的方式来处理不持有隔夜单的情况,实际使用中15点是无法平仓的。
26. """
27.
28.
29. def init(context):
30. # 设置交易品种
31. context.symbol = 'SHFE.ag'
32. # 设置止损点数
33. context.stopLossPrice = 50
34.
35. # 获取前一交易日的主力合约
36. startDate = get_previous_trading_date(exchange='SHFE', date=context.now.date())

本文档使用 掘金量化 构建 - 15 -
R-Breaker(期货)

37. continuous_contract = get_continuous_contracts(context.symbol, startDate,


startDate)
38. context.mainContract = continuous_contract[0]['symbol']
39.
40. # 获取当前时间
41. time = context.now.strftime('%H:%M:%S')
42.
43. # 如果当前时间是非交易时间段,则直接执行algo,以防直接进入on_bar()导致context.bBreak等未定义
44. if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
45. algo(context)
46.
47. # 如果是交易时间段,等到开盘时间确保进入algo()
48. schedule(schedule_func = algo, date_rule = '1d', time_rule = '09:00:00')
49. schedule(schedule_func = algo, date_rule = '1d', time_rule = '21:00:00')
50.
51. # 订阅行情
52. subscribe(continuous_contract[0]['symbol'], frequency='60s', count=1)
53.
54.
55. def algo(context):
56. # 检查主力和约,发生变化则更换订阅
57. # 由于主力合约在盘后才公布,为了防止未来函数,选择上一交易日的主力合约。
58. startDate = get_previous_trading_date(exchange='SHFE', date=context.now.date())
59. contractInfo = get_continuous_contracts(context.symbol, startDate, startDate)
60. if context.mainContract != contractInfo[0]['symbol']:
61. context.mainContract = contractInfo[0]['symbol']
62. subscribe(context.mainContract, frequency='60s', count=1,
unsubscribe_previous=True)
63.
64. # 获取历史数据
65. data = history_n(symbol=context.mainContract, frequency='1d',
66. end_time=context.now, fields='high,low,open,symbol,close',
count=2, df=True)
67. high = data['high'].iloc[0] # 前一日的最高价
68. low = data['low'].iloc[0] # 前一日的最低价
69. close = data['close'].iloc[0] # 前一日的收盘价
70. pivot = (high + low + close) / 3 # 枢轴点
71. context.bBreak = high + 2 * (pivot - low) # 突破买入价
72. context.sSetup = pivot + (high - low) # 观察卖出价
73. context.sEnter = 2 * pivot - low # 反转卖出价
74. context.bEnter = 2 * pivot - high # 反转买入价
75. context.bSetup = pivot - (high - low) # 观察买入价
76. context.sBreak = low - 2 * (high - pivot) # 突破卖出价
77. context.data = data
78.
79.
80. def on_bar(context, bars):
81.
82. # 获取止损价
83. STOP_LOSS_PRICE = context.stopLossPrice
84.
85. # 设置参数
86. bBreak = context.bBreak
87. sSetup = context.sSetup
88. sEnter = context.sEnter

本文档使用 掘金量化 构建 - 16 -
R-Breaker(期货)

89. bEnter = context.bEnter


90. bSetup = context.bSetup
91. sBreak = context.sBreak
92. data = context.data
93.
94. # 获取现有持仓
95. position_long = context.account().position(symbol=context.mainContract,
side=PositionSide_Long)
96. position_short = context.account().position(symbol=context.mainContract,
side=PositionSide_Short)
97.
98. # 突破策略:
99. if not position_long and not position_short: # 空仓条件下
100. if bars[0].close > bBreak:
101. # 在空仓的情况下,如果盘中价格超过突破买入价,则采取趋势策略,即在该点位开仓做多
102. order_volume(symbol=context.mainContract, volume=10, side=OrderSide_Buy,
103. order_type=OrderType_Market,
position_effect=PositionEffect_Open) # 做多
104. print("空仓,盘中价格超过突破买入价: 开仓做多")
105. context.open_position_price = bars[0].close
106. elif bars[0].close < sBreak:
107. # 在空仓的情况下,如果盘中价格跌破突破卖出价,则采取趋势策略,即在该点位开仓做空
108. order_volume(symbol=context.mainContract, volume=10, side=OrderSide_Sell,
109. order_type=OrderType_Market,
position_effect=PositionEffect_Open) # 做空
110. print("空仓,盘中价格跌破突破卖出价: 开仓做空")
111. context.open_position_price = bars[0].close
112.
113. # 设置止损条件
114. else: # 有持仓时
115. # 开仓价与当前行情价之差大于止损点则止损
116. if (position_long and context.open_position_price - bars[0].close >=
STOP_LOSS_PRICE) or \
117. (position_short and bars[0].close - context.open_position_price >=
STOP_LOSS_PRICE):
118. print('达到止损点,全部平仓')
119. order_close_all() # 平仓
120.
121. # 反转策略:
122. if position_long: # 多仓条件下
123. if data.high.iloc[1] > sSetup and bars[0].close < sEnter:
124. # 多头持仓,当日内最高价超过观察卖出价后,
125. # 盘中价格出现回落,且进一步跌破反转卖出价构成的支撑线时,
126. # 采取反转策略,即在该点位反手做空
127. order_close_all() # 平仓
128. order_volume(symbol=context.mainContract, volume=10,
side=OrderSide_Sell,
129. order_type=OrderType_Market,
position_effect=PositionEffect_Open) # 做空
130. print("多头持仓,当日内最高价超过观察卖出价后跌破反转卖出价: 反手做空")
131. context.open_position_price = bars[0].close
132. elif position_short: # 空头持仓
133. if data.low.iloc[1] < bSetup and bars[0].close > bEnter:
134. # 空头持仓,当日内最低价低于观察买入价后,

本文档使用 掘金量化 构建 - 17 -
R-Breaker(期货)

135. # 盘中价格出现反弹,且进一步超过反转买入价构成的阻力线时,
136. # 采取反转策略,即在该点位反手做多
137. order_close_all() # 平仓
138. order_volume(symbol=context.mainContract, volume=10,
side=OrderSide_Buy,
139. order_type=OrderType_Market,
position_effect=PositionEffect_Open) # 做多
140. print("空头持仓,当日最低价低于观察买入价后超过反转买入价: 反手做多")
141. context.open_position_price = bars[0].close
142.
143. if context.now.hour == 14 and context.now.minute == 59:
144. order_close_all()
145. print('全部平仓')
146.
147.
148. if __name__ == '__main__':
149. run(strategy_id='strategy_id',
150. filename='main.py',
151. mode=MODE_BACKTEST,
152. token='token_id',
153. backtest_start_time='2019-10-1 15:00:00',
154. backtest_end_time='2020-04-16 15:00:00',
155. backtest_initial_cash=1000000,
156. backtest_commission_ratio=0.0001,
157. backtest_slippage_ratio=0.0001)

4、回测结果与稳健性分析

设定初始资金100万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示:

回测期间累计收益为17.69%,年化收益率为32.44%,基准收益率为-0.92%,整体跑赢指数。最大回撤为6.11%,胜率为
45.00%。

本文档使用 掘金量化 构建 - 18 -
R-Breaker(期货)

改变回测期间,观察回测结果如下表所示。

标的 回测期 年化收益率 最大回撤

SHFE.ag2010 2019.10.01-2020.04.16 32.44% 6.11%

SHFE.rb2010 2019.10.01-2020.04.16 0.08% 1.07%

SHFE.sn2010 2019.10.01-2020.04.16 19.59% 2.39%

SHFE.cu2010 2019.10.01-2020.04.16 31.91% 4.80%

SHFE.ni2010 2019.10.01-2020.04.16 -1.98% 6.81%

由上表可看出,除了ni2010合约以外,其他几个合约均能保持正收益率,尤其是ag2010合约和cu2010合约,年化收益率达到
30%以上,最大回撤却只有10%以内,远远跑赢大盘指数。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 19 -
菲阿里四价(期货)

菲阿里四价(期货)
菲阿里四价
1. 原理
2. 策略逻辑
3. 策略代码
4. 回测结果与稳健性分析

菲阿里四价

1. 原理

菲阿里四价同R Breaker一样,也是一种日内策略交易,适合短线投资者。

菲阿里四价指的是:昨日高点、昨日低点、昨天收盘、今天开盘四个价格。

菲阿里四价上下轨的计算非常简单。昨日高点为上轨,昨日低点为下轨。当价格突破上轨时,买入开仓;当价格突破下轨时,
卖出开仓。

2. 策略逻辑

第一步:获取昨日最高价、最低价、收盘价、开盘价四个数据。
第二步:计算上轨和下轨。当价格上穿上轨时,买入开仓;当价格下穿下轨时,卖出开仓。
第三步:当日平仓。

回测标的:SHFE.rb2010
回测期:2020-02-07 至 2020-04-15
回测初始资金:200万
注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import
3. from gm.api import *
4.
5.
6. """
7. 上轨=昨日最高点;
8. 下轨=昨日最低点;
9. 止损=今日开盘价;
10. 如果没有持仓,且现价大于了昨天最高价做多,小于昨天最低价做空。
11. 如果有多头持仓,当价格跌破了开盘价止损。
12. 如果有空头持仓,当价格上涨超过开盘价止损。
13. 选取 进行了回测。
14. 注意:
15. 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。
16. 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。
17. """

本文档使用 掘金量化 构建 - 20 -
菲阿里四价(期货)

18.
19.
20. def init(context):
21. # 设置标的
22. context.symbol = 'SHFE.rb2010'
23. # 订阅一分钟线
24. subscribe(symbols = context.symbol,frequency = '60s',count = 1)
25. # 记录开仓次数,保证一天只开仓一次
26. context.count = 0
27. # 记录当前时间
28. time = context.now.strftime('%H:%M:%S')
29.
30. # 如果当前时间点是交易时间段,则直接执行algo获取历史数据,以防直接进入on_bar()导致
context.history_data未定义
31. if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
32. algo(context)
33.
34. # 如果是非交易时间段,等到上午9点或晚上21点再执行algo()
35. schedule(schedule_func = algo, date_rule = '1d', time_rule = '09:00:00')
36. schedule(schedule_func = algo, date_rule = '1d', time_rule = '21:00:00')
37.
38.
39. def algo(context):
40. # 获取历史的n条信息
41. context.history_data = history_n(symbol=context.symbol, frequency = '1d', end_time
= context.now,
42. fields='symbol,open,high,low',count=2, df=True)
43.
44.
45. def on_bar(context,bars):
46. # 取出订阅的一分钟bar
47. bar = bars[0]
48. # 提取数据
49. data = context.history_data
50. # 现有持仓情况
51. position_long = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
52. position_short = context.account().position(symbol=context.symbol,
side=PositionSide_Short)
53.
54. # 如果是回测模式
55. if context.mode == 2:
56. # 开盘价直接在data最后一个数据里取到,前一交易日的最高和最低价为history_data里面的倒数第二条
中取到
57. open = data.loc[1, 'open']
58. high = data.loc[0, 'high']
59. low = data.loc[0, 'low']
60.
61. # 如果是实时模式
62. else:
63. # 开盘价通过current取到
64. open = current(context.symbol)[0]['open']
65. # 实时模式不会返回当天的数据,所以history_data里面的最后一条数据是前一交易日的数据
66. high = data.loc[-1, 'high']
67. low = data.loc[-1, 'low']

本文档使用 掘金量化 构建 - 21 -
菲阿里四价(期货)

68.
69.
70. # 交易逻辑部分
71. if position_long: # 多头持仓小于开盘价止损。
72. if bar.close < open:
73. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
74. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
75. print('以市价单平多仓')
76. elif position_short: # 空头持仓大于开盘价止损。
77. if bar.close > open:
78. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
79. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
80. print('以市价单平空仓')
81.
82. else: # 没有持仓。
83. if bar.close > high and not context.count: # 当前的最新价大于了前一天的最高价
84. # 开多
85. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
86. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
87. print('以市价单开多仓')
88. context.count = 1
89. elif bar.close < low and not context.count: # 当前最新价小于了前一天的最低价
90. # 开空
91. order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
92. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
93. print('以市价单开空仓')
94. context.count = 1
95.
96. # 每天收盘前一分钟平仓
97. if context.now.hour == 14 and context.now.minute == 59:
98. order_close_all()
99. print('全部平仓')
100. context.count = 0
101.
102.
103. if __name__ == '__main__':
104. '''
105. strategy_id策略ID,由系统生成
106. filename文件名,请与本文件名保持一致
107. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
108. token绑定计算机的ID,可在系统设置-密钥管理中生成
109. backtest_start_time回测开始时间
110. backtest_end_time回测结束时间
111. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
112. backtest_initial_cash回测初始资金
113. backtest_commission_ratio回测佣金比例
114. backtest_slippage_ratio回测滑点比例
115. '''
116. run(strategy_id='strategy_id',
117. filename='main.py',

本文档使用 掘金量化 构建 - 22 -
菲阿里四价(期货)

118. mode=MODE_BACKTEST,
119. token='token_id',
120. backtest_start_time='2020-01-01 15:00:00',
121. backtest_end_time='2020-09-01 16:00:00',
122. backtest_adjust=ADJUST_PREV,
123. backtest_initial_cash=100000,
124. backtest_commission_ratio=0.0001,
125. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金10万,手续费率为0.01%,滑点比率为0.01%。回测结果如图所示:

回测期累计收益率为-4.05%,年化收益率为-6.03%。沪深300指数收益率为16.61%,整体跑输指数。最大回撤为4.34%,胜
率为25.66%。

为了验证策略的稳健性,更改标的,回测结果如下:

标的 回测期 年化收益率 最大回撤

SHFE.rb2010 2020.02.07-2020.04.15 -6.03% 4.34%

SHFE.ag2010 2020.02.07-2020.04.15 38.03% 7.97%

SHFE.ni2010 2020.02.07-2020.04.15 -22.46% 22.85%

SHFE.zn2010 2020.02.07-2020.04.15 -15.93% 12.01%

SHFE.rb2010 2020.04.07-2020.06.15 -3.40% 1.37%

SHFE.rb2010 2020.06.07-2020.08.15 -13.96% 2.32%

SHFE.rb2010 2020.08.07-2020.10.15 -11.38% 2.18%

由上表可以看出,随着品种的变化,策略的收益变化差异较大。ag2010的收益率能达到38.03%,但ni2010的收益仅
为-22.46%。对于rb2010,随着回测期的变化,年化收益率均为负值,收益较不稳定。

本文档使用 掘金量化 构建 - 23 -
菲阿里四价(期货)

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 24 -
小市值(股票)

小市值(股票)
小市值策略(股票)
1. 原理
1.1 因子投资
1.2 规模因子
2. 策略逻辑
3. 策略代码
4. 回测结果和稳健性分析

小市值策略(股票)
(以下部分内容参考《因子投资:方法与实践》一书)

1. 原理

1.1 因子投资

提到量化策略,总是绕不开因子投资。自CAPM模型提出以来,因子投资不断发展壮大,成为市场广泛关注的领域。市面上的策
略很大一部分都是基于各种各样的因子创造的,因子在股票和债券市场上的应用也取得了不小的成就。

到底什么是因子投资?

20世纪60年代,资本资产定价模型(Capital Asset Pricing Model)提出,揭示了资产的预期收益率(预期超额收益


率)与风险之间的关系,第一次给出了资本资产定价的直观表达式。

其中Rm表示市场预期收益率,β表示市场的风险暴露。

该公式指出资产的预期超额收益率由资产对市场风险的暴露大小决定,也就是说,资产的超额预期收益率可以完全由市场因子
解释。

随着市场异象不断出现,人们发现资产的收益率并非只由市场因子决定,还受到其他因子的影响。1976年,Ross提出了无套利
定价理论,构建了多因子定价模型,表达式为:

其中λ是因子预期收益率,β是对应的因子暴露,α表示误差项。

该公式指出资产的预期收益率是由一系列因子及其因子暴露加上误差项决定的。而因子投资的目的就是寻找到能够解释资产收
益率的因子。

既然资产预期收益率是由众多因子决定的,什么是因子?怎么寻找因子?

根据《因子投资方法与实践》一书中的定义,“一个因子描述了众多资产共同暴露的某种系统性风险,该风险是资产收益率背后
的驱动力,因子收益率正式这种系统性风险的风险溢价或风险补偿,它是这些资产的共性收益。”翻译过来就是,想要作为因
子,必须能够解释多个资产的收益情况,并且能够带来正收益。

再来看式2,可以发现,资产预期收益率是由两部分组成的,除了λ以外,还有一个α项,称之为误差项,表示资产预期收益率中
λ无法解释的部分。α的出现可能有以下两个原因,一种是因为模型设定错误,右侧遗漏了重要的因子;一种是由于选用样本数

本文档使用 掘金量化 构建 - 25 -
小市值(股票)

据可能存在一定偏差,导致在该样本数据下出现了α项。

为了确定α的出现是哪种原因,需要利用统计检验进行判断。

如果α显著为零,说明α的出现只是偶然,并不能说明定价模型存在错误;
如果α显著为零,说明α的出现只是偶然,并不能说明定价模型存在错误;
如果α显著不为零,说明资产预期收益里尚存在定价模型未能完全解释的部分。这种情况,就成为异象。
因此,因子大体上可分为两种,一种是定价因子,也就是λ部分;一种是异象因子,也就是α部分。

1.2 规模因子

1981年Banz基于纽交所长达40年的数据发现,小市值股票月均收益率比其他股票高0.4%。其背后的原因可能是投资者普遍不
愿意持有小公司股票,使得这些小公司价格普遍偏低,甚至低于成本价,因此会有较高的预期收益率。由此产生了小市值策
略,即投资于市值较小的股票。市值因子也被纳入进大名鼎鼎的Fama三因子模型和五因子模型之中。

A股市场上规模因子是否有效?研究发现,2016年以前,A股市场上规模因子的显著性甚至超过了欧美等发达国家市场。但到了
2017-2018年期间,大市值股票的表现明显优于小市值股票,使得规模因子在A股市场上的有效性存疑。

2. 策略逻辑

第一步:确定调仓频率,以每月第一天调仓为例
第二步:确定股票池股票数量,这里假设有30支
第三步:调仓日当天获取前一个月的历史数据,并按照市值由小到大排序
第四步:买入前30支股票

回测期:2005-01-01 到 2020-10-01
股票池:所有A股股票
回测初始资金:100万

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. from gm.api import *
4. from datetime import timedelta
5.
6.
7. """
8. 小市值策略
9. 本策略每个月触发一次,计算当前沪深市场上市值最小的前30只股票,并且等权重方式进行买入。
10. 对于不在前30的有持仓的股票直接平仓。
11. 回测时间为:2005-01-01 08:00:00 到 2020-10-01 16:00:00
12. """
13.
14.
15. def init(context):
16. # 每月第一个交易日的09:40 定时执行algo任务(仿真和实盘时不支持该频率)
17. schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
18. # 使用多少的资金来进行开仓。
19. context.ratio = 0.8
20. # 定义股票池数量
21. context.num = 30
22. # 通过get_instruments获取所有的上市股票代码

本文档使用 掘金量化 构建 - 26 -
小市值(股票)

23. context.all_stock = get_instruments(exchanges='SHSE, SZSE', sec_types=[1],


skip_suspended=False,
24. skip_st=False, fields='symbol, listed_date,
delisted_date',
25. df=True)
26.
27.
28. def algo(context):
29. # 获取筛选时间:date1表示当前日期之前的100天,date2表示当前时间
30. date1 = (context.now - timedelta(days=100)).strftime("%Y-%m-%d %H:%M:%S")
31. date2 = context.now.strftime("%Y-%m-%d %H:%M:%S")
32.
33. # 上市不足100日的新股和退市股和B股
34. code = context.all_stock[(context.all_stock['listed_date'] < date1) &
(context.all_stock['delisted_date'] > date2) &
35. (context.all_stock['symbol'].str[5] != '9') &
(context.all_stock['symbol'].str[5] != '2')]
36. # 剔除停牌和st股
37. df_code = get_history_instruments(symbols=code['symbol'].to_list(),
start_date=date2, end_date=date2, df=True)
38. df_code = df_code[(df_code['is_suspended'] == 0) & (df_code['sec_level'] == 1)]
39.
40. # 获取所有股票市值
41. fundamental = get_fundamentals_n('trading_derivative_indicator',
df_code['symbol'].to_list(),
42. context.now, fields='TOTMKTCAP',
order_by='TOTMKTCAP', count=1, df=True)
43.
44. # 对市值进行排序(升序),并且获取前30个。 最后将这个series 转化成为一个list即为标的池
45. trade_symbols = fundamental.reset_index(
46. drop=True).loc[:context.num - 1, 'symbol'].to_list()
47. print('本次股票池有股票数目: ', len(trade_symbols))
48.
49. # 计算每个个股应该在持仓中的权重
50. percent = 1.0 / len(trade_symbols) * context.ratio
51.
52. # 获取当前所有仓位
53. positions = context.account().positions()
54.
55. # 平不在标的池的仓位
56. for position in positions:
57. symbol = position['symbol']
58. if symbol not in trade_symbols:
59. order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
60. position_side=PositionSide_Long)
61. print('市价单平不在标的池的', symbol)
62.
63. # 将标中已有持仓的和还没有持仓的都调整到计算出来的比例。
64. for symbol in trade_symbols:
65. order_target_percent(symbol=symbol, percent=percent,
order_type=OrderType_Market,
66. position_side=PositionSide_Long)
67. print(symbol, '以市价单调整至权重', percent)
68.
69.

本文档使用 掘金量化 构建 - 27 -
小市值(股票)

70. if __name__ == '__main__':


71. '''
72. strategy_id策略ID,由系统生成
73. filename文件名,请与本文件名保持一致
74. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
75. token绑定计算机的ID,可在系统设置-密钥管理中生成
76. backtest_start_time回测开始时间
77. backtest_end_time回测结束时间
78. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
79. backtest_initial_cash回测初始资金
80. backtest_commission_ratio回测佣金比例
81. backtest_slippage_ratio回测滑点比例
82. '''
83. run(strategy_id='13a64e72-e900-11eb-b05f-309c2322ba62',
84. filename='main.py',
85. mode=MODE_BACKTEST,
86. token='2b62e7651c9897d0cdd4a6cd818a7ba8488af710',
87. backtest_start_time='2005-01-01 08:00:00',
88. backtest_end_time='2020-10-01 16:00:00',
89. backtest_adjust=ADJUST_PREV,
90. backtest_initial_cash=1000000,
91. backtest_commission_ratio=0.0001,
92. backtest_slippage_ratio=0.0001)

4. 回测结果和稳健性分析

设定初始资金100万,手续费率为0.01%,滑点比率为0.01%。回测结果如图所示:

回测期累计收益率为447.16%,年化收益率为28.38%,沪深300指数收益率为366.77%,策略整体跑赢指数。
为了检验策略的稳健性,改变回测期和标的股票数量,得到结果如下:

n 回测期 回测期长度 年化收益率 最大回撤

本文档使用 掘金量化 构建 - 28 -
小市值(股票)

30 2005.01.01-2020.10.01 15年零9个月 28.38% 59.51%

30 2005.01.01-2015.01.01 10年零9个月 0.99% 0.30%

30 2015.01.01-2020.10.01 5年零9个月 0.85% 0.31%

20 2005.01.01-2020.10.01 15年零9个月 20.19% 60.53%

10 2005.01.01-2020.10.01 15年零9个月 23.43% 60.75%

从长期来看,小市值策略能够带来一定的收益,但同时也伴随着较大的回撤水平。
从短期来看,策略收益跑输大盘。收益率低,回撤小,整体效益较差。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 29 -
布林线均值回归(股票)

布林线均值回归(股票)
布林线均值回归策略
1. 原理
2. 策略思路
3. 策略代码
4. 回测结果与稳健性分析

布林线均值回归策略

1. 原理

提起布林线均值回归策略,就不得不提布林带这个概念。布林带是利用统计学中的均值和标准差联合计算得出的,分为均线,
上轨线和下轨线。布林线均值回归策略认为,标的价格在上轨线和下轨线围成的范围内浮动,即使短期内突破上下轨,但长期
内仍然会回归到布林带之中。因此,一旦突破上下轨,即形成买卖信号。

当股价向上突破上界时,为卖出信号,当股价向下突破下界时,为买入信号。

BOLL线的计算公式:

中轨线 = N日移动平均线
上轨线 = 中轨线 + k 标准差
下轨线 = 中轨线 - k 标准差

2. 策略思路

第一步:根据数据计算BOLL线的上下界
第二步:获得持仓信号
第三步:回测分析

回测标的:SHSE.600004
回测期:2009-09-17 13:00:00 到 2020-03-21 15:00:00
回测初始资金:1000元

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import
3. from gm.api import *
4.
5.
6. """
7. 本策略采用布林线进行均值回归交易。当价格触及布林线上轨的时候进行卖出,当触及下轨的时候,进行买入。
8. 使用600004在 2009-09-17 13:00:00 到 2020-03-21 15:00:00 进行了回测。
9. 注意:
10. 1:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。
11. """
12.
13. # 策略中必须有init方法
14. def init(context):

本文档使用 掘金量化 构建 - 30 -
布林线均值回归(股票)

15. # 设置布林线的三个参数
16. context.maPeriod = 26 # 计算BOLL布林线中轨的参数
17. context.stdPeriod = 26 # 计算BOLL 标准差的参数
18. context.stdRange = 1 # 计算BOLL 上下轨和中轨距离的参数
19.
20. # 设置要进行回测的合约
21. context.symbol = 'SHSE.600004' # 订阅&交易标的, 此处订阅的是600004
22. context.period = max(context.maPeriod, context.stdPeriod, context.stdRange) + 1 #
订阅数据滑窗长度
23.
24. # 订阅行情
25. subscribe(symbols= context.symbol, frequency='1d', count=context.period)
26.
27.
28. def on_bar(context, bars):
29. # 获取数据滑窗,只要在init里面有订阅,在这里就可以取的到,返回值是pandas.DataFrame
30. data = context.data(symbol=context.symbol, frequency='1d', count=context.period,
fields='close')
31.
32. # 计算boll的上下界
33. bollUpper = data['close'].rolling(context.maPeriod).mean() \
34. + context.stdRange * data['close'].rolling(context.stdPeriod).std()
35. bollBottom = data['close'].rolling(context.maPeriod).mean() \
36. - context.stdRange * data['close'].rolling(context.stdPeriod).std()
37. # 获取现有持仓
38. pos = context.account().position(symbol=context.symbol, side=PositionSide_Long)
39.
40. # 交易逻辑与下单
41. # 当有持仓,且股价穿过BOLL上界的时候卖出股票。
42. if data.close.values[-1] > bollUpper.values[-1] and data.close.values[-2] <
bollUpper.values[-2]:
43. if pos: # 有持仓就市价卖出股票。
44. order_volume(symbol=context.symbol, volume=100, side=OrderSide_Sell,
45. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
46. print('以市价单卖出一手')
47.
48. # 当没有持仓,且股价穿过BOLL下界的时候买出股票。
49. elif data.close.values[-1] < bollBottom.values[-1] and data.close.values[-2] >
bollBottom.values[-2]:
50. if not pos: # 没有持仓就买入一百股。
51. order_volume(symbol=context.symbol, volume=100, side=OrderSide_Buy,
52. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
53. print('以市价单买入一手')
54.
55.
56. if __name__ == '__main__':
57. '''
58. strategy_id策略ID,由系统生成
59. filename文件名,请与本文件名保持一致
60. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
61. token绑定计算机的ID,可在系统设置-密钥管理中生成
62. backtest_start_time回测开始时间
63. backtest_end_time回测结束时间

本文档使用 掘金量化 构建 - 31 -
布林线均值回归(股票)

64. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
65. backtest_initial_cash回测初始资金
66. backtest_commission_ratio回测佣金比例
67. backtest_slippage_ratio回测滑点比例
68. '''
69. run(strategy_id='strategy_id',
70. filename='main.py',
71. mode=MODE_BACKTEST,
72. token='token_id',
73. backtest_start_time='2009-09-17 13:00:00',
74. backtest_end_time='2020-03-21 15:00:00',
75. backtest_adjust=ADJUST_PREV,
76. backtest_initial_cash=1000,
77. backtest_commission_ratio=0.0001,
78. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金1000元,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

回测期累计收益率为99.77%,年化收益率为9.49%。沪深300收益率为10.03%,策略整体跑输大盘。最大回撤为32.04%,胜
率为73.47%。

为了验证策略的稳定性,改变回测周期,观察收益情况。

标的 回测期 年化收益率 最大回撤

SHSE.600004 2009.09.17-2020.03.21 9.49% 32.04%

SHSE.600004 2009.01.01-2014.12.30 2.64% 17.07%

SHSE.600004 2014.01.01-2020.03.21 20.75% 17.21%

SHSE.600004 2009.01.01-2019.03.21 8.18% 31.95%

本文档使用 掘金量化 构建 - 32 -
布林线均值回归(股票)

调整不同的回测期后,策略的收益情况发生变化。整体收益均为正,但均跑输大盘。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 33 -
alpha对冲(股票+期货)

alpha对冲(股票+期货)
Alpha对冲策略
1. 策略原理
何为alpha?
什么是alpha对冲策略?
怎么对冲?
策略收益情况
策略要点
2. 策略实现
3. 策略代码
4. 回测结果与稳健性分析

Alpha对冲策略

1. 策略原理

何为alpha?

提到Alpha策略,首先要理解什么是CAPM模型。

CAPM模型于1964年被Willian Sharpe等人提出。Sharpe等人认为,假设市场是均衡的,资产的预期超额收益率就由市场收
益超额收益和风险暴露决定的。如下式所示。

其中rm为市场组合,rf为无风险收益率。

根据CAPM模型可知,投资组合的预期收益由两部分组成,一部分为无风险收益率rf,另一部分为风险收益率。

CAPM模型一经推出就受到了市场的追捧。但在应用过程中发现,CAPM模型表示的是在均衡状态下市场的情况,但市场并不总是
处于均衡状态,个股总会获得超出市场基准水平的收益,即在CAPM模型的右端总是存在一个alpha项。

为了解决这个问题,1968年,美国经济学家迈克·詹森(Michael Jensen)提出了詹森指数来描述这个alpha,因此又称
alpha指数。计算方式如式2所示。

因此,投资组合的收益可以改写成

可将投资组合的收益拆分为alpha收益和beta收益。其中beta的计算公式为

β是由市场决定的,属于系统性风险,与投资者管理能力无关,只与投资组合与市场的关系有关。当市场整体下跌时,β对应的
收益也会随着下跌(假设beta为正)。alpha收益与市场无关,是投资者自身能力的体现。投资者通过自身的经验进行选股择
时,得到超过市场的收益。

什么是alpha对冲策略?

本文档使用 掘金量化 构建 - 34 -
alpha对冲(股票+期货)

所谓的alpha对冲不是将alpha收益对冲掉,恰恰相反,alpha对冲策略是将β收益对冲掉,只获取alpha收益,如下图所示。

alpha对冲策略将市场性风险对冲掉,只剩下alpha收益,整体收益完全取决于投资者自身的能力水平,与市场无关。目前,
有许多私募基金采用alpha对冲策略。

怎么对冲?

alpha对冲策略常采用股指期货做对冲。在股票市场上做多头,在期货市场上做股指期货空头。当股票现货市场亏损时,可以
通过期货市场弥补亏损;当期货市场亏损时,可以通过股票现货市场弥补亏损。

策略收益情况

目前alpha对冲策略主要用于各类基金中。国际上比较知名的桥水基金、AQR基金等都采用过这种策略。国内也有许多利用
alpha对冲策略的基金,比如海富通阿尔法对冲混合、华宝量化对冲混合等,近一年平均收益率约为36.70%。

策略要点

alpha策略能否成功,主要包括以下几个要点

获取到的alpha收益是否足够高,能否超过无风险利率以及指数.
期货和现货之间的基差变化.
期货合约的选择.

alpha对冲只是一种对冲市场风险的方法,在创建策略时需要结合其他理论一起使用,怎样获取到较高的alpha收益才是决定
策略整体收益的关键。

2. 策略实现
第一步:制定一个选股策略,构建投资组合,使其同时拥有alpha和beta收益。
(本策略选取过去一天EV/EBITDA值并选取30只EV/EBITDA值最小且大于零的股票)
第二步:做空股指期货,将投资组合的beta抵消,只剩alpha部分。
第三步:进行回测。

股票池:沪深300指数
期货标的:CFFEX.IF对应的真实合约
回测时间:2017-07-01 08:00:00 至 2017-10-01 16:00:00
回测初始资金:1000万

3. 策略代码

本文档使用 掘金量化 构建 - 35 -
alpha对冲(股票+期货)

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. from gm.api import *
4.
5.
6. '''
7. 本策略每隔1个月定时触发计算SHSE.000300成份股的过去一天EV/EBITDA值并选取30只EV/EBITDA值最小且大于零
的股票
8. 对不在股票池的股票平仓并等权配置股票池的标的
9. 并用相应的CFFEX.IF对应的真实合约等额对冲
10. 回测数据为:SHSE.000300和他们的成份股和CFFEX.IF对应的真实合约
11. 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00
12. 注意:本策略仅供参考,实际使用中要考虑到期货和股票处于两个不同的账户,需要人为的保证两个账户的资金相同。
13. '''
14.
15.
16. def init(context):
17. # 每月第一个交易日09:40:00的定时执行algo任务(仿真和实盘时不支持该频率)
18. schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
19. # 设置开仓在股票和期货的资金百分比(期货在后面自动进行杠杆相关的调整)
20. context.percentage_stock = 0.4
21. context.percentage_futures = 0.4
22.
23.
24. def algo(context):
25. # 获取当前时刻
26. now = context.now
27.
28. # 获取上一个交易日
29. last_day = get_previous_trading_date(exchange='SHSE', date=now)
30.
31. # 获取沪深300成份股的股票代码
32. stock300 = get_history_constituents(index='SHSE.000300', start_date=last_day,
33. end_date=last_day)[0]
['constituents'].keys()
34.
35. # 获取上一个工作日的CFFEX.IF对应的合约
36. index_futures = get_continuous_contracts(csymbol='CFFEX.IF', start_date=last_day,
end_date=last_day)[-1]['symbol']
37.
38. # 获取当天有交易的股票
39. not_suspended_info = get_history_instruments(symbols=stock300, start_date=now,
end_date=now)
40. not_suspended_symbols = [item['symbol'] for item in not_suspended_info if not
item['is_suspended']]
41.
42. # 获取成份股EV/EBITDA大于0并为最小的30个
43. fin = get_fundamentals(table='trading_derivative_indicator',
symbols=not_suspended_symbols,
44. start_date=now, end_date=now, fields='EVEBITDA',
45. filter='EVEBITDA>0', order_by='EVEBITDA', limit=30, df=True)
46. fin.index = fin.symbol
47.
48. # 获取当前仓位
49. positions = context.account().positions()

本文档使用 掘金量化 构建 - 36 -
alpha对冲(股票+期货)

50.
51. # 平不在标的池或不为当前股指期货主力合约对应真实合约的标的
52. for position in positions:
53. symbol = position['symbol']
54. sec_type = get_instrumentinfos(symbols=symbol)[0]['sec_type']
55.
56. # 若类型为期货且不在标的池则平仓
57. if sec_type == SEC_TYPE_FUTURE and symbol != index_futures:
58. order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
59. position_side=PositionSide_Short)
60. print('市价单平不在标的池的', symbol)
61. elif symbol not in fin.index:
62. order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
63. position_side=PositionSide_Long)
64. print('市价单平不在标的池的', symbol)
65.
66. # 获取股票的权重
67. percent = context.percentage_stock / len(fin.index)
68.
69. # 买在标的池中的股票
70. for symbol in fin.index:
71. order_target_percent(symbol=symbol, percent=percent,
order_type=OrderType_Market,
72. position_side=PositionSide_Long)
73. print(symbol, '以市价单调多仓到仓位', percent)
74.
75. # 获取股指期货的保证金比率
76. ratio = get_history_instruments(symbols=index_futures, start_date=last_day,
end_date=last_day)[0]['margin_ratio']
77.
78. # 更新股指期货的权重
79. percent = context.percentage_futures * ratio
80.
81. # 买入股指期货对冲
82. # 注意:股指期货的percent参数是按照期货的保证金来算比例,不是按照合约价值, 比如说0.1就是用0.1的
仓位的资金全部买入期货。
83. order_target_percent(symbol=index_futures, percent=percent,
order_type=OrderType_Market,
84. position_side=PositionSide_Short)
85. print(index_futures, '以市价单调空仓到仓位', percent)
86.
87.
88. if __name__ == '__main__':
89. '''
90. strategy_id策略ID,由系统生成
91. filename文件名,请与本文件名保持一致
92. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
93. token绑定计算机的ID,可在系统设置-密钥管理中生成
94. backtest_start_time回测开始时间
95. backtest_end_time回测结束时间
96. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
97. backtest_initial_cash回测初始资金
98. backtest_commission_ratio回测佣金比例
99. backtest_slippage_ratio回测滑点比例
100. '''

本文档使用 掘金量化 构建 - 37 -
alpha对冲(股票+期货)

101. run(strategy_id='strategy_id',
102. filename='main.py',
103. mode=MODE_BACKTEST,
104. token='token_id',
105. backtest_start_time='2017-07-01 08:00:00',
106. backtest_end_time='2017-10-01 16:00:00',
107. backtest_adjust=ADJUST_PREV,
108. backtest_initial_cash=10000000,
109. backtest_commission_ratio=0.0001,
110. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金1000万,手续费率为0.01%,滑点比率为0.01%。策略回测结果如下图所示。

回测期累计收益率为0.32%,年化收益率为1.32%,沪深300指数收益率为5.09%,策略整体跑输指数。最大回撤为1.17%,胜
率为74.29%。

以同样的策略进行选股,不对冲beta时回测结果如下图所示。

本文档使用 掘金量化 构建 - 38 -
alpha对冲(股票+期货)

对比可以看出,利用alpha对冲策略比未对冲策略收益低,但胜率高于普通策略,最大回撤低于未对冲策略。这也说明了
alpha对冲策略能够规避一部分由市场带来的风险。

改变回测期,观察策略收益情况如下表所示(以2020年10月30日为结束期)。

指标 近三月 近六月 今年来 近1年 近2年 近3年

年化收益率 -3.72% 7.11% -2.26% -0.77% -0.52% -3.05%

最大回撤 3.14% 3.09% 7.88% 7.86% 14.72% 16.12%

胜率 86.96% 90.00% 42.96% 64.36% 60.48% 50.55%

由上表可知,近几年该策略的整体收益为负,只有近六月的收益率为正。策略最大回撤一直维持在相对较低的水平上,随着时
间周期拉长,最大回撤不断增加,胜率不断下降。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 39 -
多因子选股(股票)

多因子选股(股票)
多因子策略
1. 原理
Fama-French三因子模型
策略设计思路(假设三因子模型是完全有效的)
2. 策略步骤
3. 策略代码
4. 回测结果与稳健性检验

多因子策略
(注:以下部分内容引用自《因子投资:方法与实践》一书)

1. 原理

多因子策略是最广泛应用的策略之一。CAPM模型的提出为股票的收益提供了解释,但随着各种市场异象的出现,使得人们发现
股票存在超额收益,这种收益不能为市场因子所解释,因此,出现了多因子模型。

多因子模型最早是由Fama-French提出,包括三因子和五因子模型。Fama认为,股票的超额收益可以由市场因子、市值因子
和账面价值比因子共同解释。随着市场的发展,出现许多三因子模型难以解释的现象。因此,Fama又提出了五因子模型,加入
了盈利水平、投资水平因子。

此后,陆续出现了六因子模型、八因子模型等,目前多少个因子是合适的尚无定论。

市场上常用的多因子模型包括如下几个。

模型 出处 所含因子

Fama-French三因子 Fama and Farench(1993) 市场、规模、价值

Carhart四因子 Carhart(1997) 市场、规模、价值、动量

Novy-Marx四因子 Novy-Marx(2013) 市场、规模、价值、盈利

Fama-French五因子 Fama and Farench(2015) 市场、规模、价值、盈利、投资

Hou-Xue-Zhang四因子 Hou et al 市场、规模、盈利、投资

Stambaugh-Yuan四因子 Stambaugh and Yuan(2017) 市场、规模、管理、表现

Daniel-Hirshleifer-Sun三因子 Daniel et al(2020) 市场、长周期行为、短周期行为

本策略以Fama提出的三因子模型作为基础。

Fama-French三因子模型

在多因子模型出现以前,CAPM模型被奉为典型,几乎所有定价均是按照CAPM模型计算的。后来学者们发现了各种异象,这些异
象无法用CAPM模型解释。较为典型的有Basu发现的盈利市值比效应和Banz发现的小市值效应。遗憾的是,虽然单一异象被发
现后都对CAPM提出了挑战,但并没有形成合力,直到Fama三因子模型出现。

Fama等人在CAPM的基础上,Fama加入了HML和SMB两个因子,提出了三因子模型,也是多因子模型的基础。

本文档使用 掘金量化 构建 - 40 -
多因子选股(股票)

其中E[R_i]代表股票i的预期收益率,R_f代表无风险收益率,E[R_m]为市场组合预期收益率,E[R_SMB]和E[R_HML]分别
为规模因子收益率和价值因子预期收益率。

为构建价值因子和规模因子,Fama选择BM和市值两个指标进行双重排序,将股票分为大市值组B和小市值组S;按照账面市值比
将股票分为BM高于70%分位数的H组,BM低于30%分位数的L组,BM处于二者之间的记为M组。如表所示。

得到上述分组以后,就可以构建规模和价值两个因子。

上述式子解释一下可以发现,规模因子是三个小市值组合的等权平均减去三个大市值组合的等权平均;价值因子是两个高BM组
合的等权平均减去两个低BM组合的等权平均。

策略设计思路(假设三因子模型是完全有效的)

在用三因子模型估算股票预期收益率时,经常会发现并非每只股票都能严格吻合式1,大部分股票都会存在一个alpha截距项。
当存在alpha截距项时,说明股票当前价格偏离均衡价格。基于此,可以设计套利策略。

alpha < 0时,说明股票收益率低于均衡水平,股票价格被低估,应该买入。


alpha > 0时,说明股票收益率高于均衡水平,股票价格被高估,应该卖出。

因此,可以获取alpha最小并且小于0的10只的股票买入开仓。

2. 策略步骤

第一步:获取股票市值以及账面市值比数据。
第二步:将股票按照各个因子进行排序分组,分组方法如上表所示。
第三步:依据式2式3,计算SMB、HML因子。
第四步:因子回归,计算alpha值。获取alpha最小并且小于0的10只的股票买入开仓。

回测期:2017-07-01 8:00:00 至 2017-10-01 16:00:00


回测初始资金:1000万
回测标的:沪深300成分股

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np
4. from gm.api import *
5. from pandas import DataFrame
6. '''
7. 本策略每隔1个月定时触发,根据Fama-French三因子模型对每只股票进行回归,得到其alpha值。

本文档使用 掘金量化 构建 - 41 -
多因子选股(股票)

8. 假设Fama-French三因子模型可以完全解释市场,则alpha为负表明市场低估该股,因此应该买入。
9. 策略思路:
10. 计算市场收益率、个股的账面市值比和市值,并对后两个进行了分类,
11. 根据分类得到的组合分别计算其市值加权收益率、SMB和HML.
12. 对各个股票进行回归(假设无风险收益率等于0)得到alpha值.
13. 选取alpha值小于0并为最小的10只股票进入标的池
14. 平掉不在标的池的股票并等权买入在标的池的股票
15. 回测数据:SHSE.000300的成份股
16. 回测时间:2017-07-01 08:00:00到2017-10-01 16:00:00
17. '''
18. def init(context):
19. # 每月第一个交易日的09:40 定时执行algo任务(仿真和实盘时不支持该频率)
20. schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
21. # 数据滑窗
22. context.date = 20
23. # 设置开仓的最大资金量
24. context.ratio = 0.8
25. # 账面市值比的大/中/小分类
26. context.BM_BIG = 3.0
27. context.BM_MID = 2.0
28. context.BM_SMA = 1.0
29. # 市值大/小分类
30. context.MV_BIG = 2.0
31. context.MV_SMA = 1.0
32.
33. # 计算市值加权的收益率的函数,MV为市值的分类对应的组别,BM为账目市值比的分类对应的组别
34. def market_value_weighted(stocks, MV, BM):
35. select = stocks[(stocks['NEGOTIABLEMV'] == MV) & (stocks.['BM'] == BM)] # 选出市值为
MV,账目市值比为BM的所有股票数据
36. market_value = select['mv'].values # 对应组的全部市值数据
37. mv_total = np.sum(market_value) # 市值求和
38. mv_weighted = [mv / mv_total for mv in market_value] # 市值加权的权重
39. stock_return = select['return'].values
40.
41. # 返回市值加权的收益率的和
42. return_total = []
43. for i in range(len(mv_weighted)):
44. return_total.append(mv_weighted[i] * stock_return[i])
45. return_total = np.sum(return_total)
46. return return_total
47.
48. def algo(context):
49. # 获取上一个交易日的日期
50. last_day = get_previous_trading_date(exchange='SHSE', date=context.now)
51. # 获取沪深300成份股
52. context.stock300 = get_history_constituents(index='SHSE.000300',
start_date=last_day,
53. end_date=last_day)[0]
['constituents'].keys()
54. # 获取当天有交易的股票
55. not_suspended = get_history_instruments(symbols=context.stock300,
start_date=last_day, end_date=last_day)
56. not_suspended = [item['symbol'] for item in not_suspended if not
item['is_suspended']]

本文档使用 掘金量化 构建 - 42 -
多因子选股(股票)

57. fin = get_fundamentals(table='trading_derivative_indicator', symbols=not_suspended,


58. start_date=last_day,
end_date=last_day,fields='PB,NEGOTIABLEMV', df=True) # 获取P/B和市值数据
59.
60. # 计算账面市值比,为P/B的倒数
61. fin['PB'] = (fin['PB'] ** -1)
62. # 计算市值的50%的分位点,用于后面的分类
63. size_gate = fin['NEGOTIABLEMV'].quantile(0.50)
64. # 计算账面市值比的30%和70%分位点,用于后面的分类
65. bm_gate = [fin['PB'].quantile(0.30), fin['PB'].quantile(0.70)]
66. fin.index = fin.symbol
67. # 设置存放股票收益率的list
68. x_return = []
69.
70. # 对未停牌的股票进行处理
71. for symbol in not_suspended:
72. # 计算收益率,存放到x_return里面
73. close = history_n(symbol=symbol, frequency='1d', count=context.date + 1,
end_time=last_day, fields='close',
74. skip_suspended=True, fill_missing='Last', adjust=ADJUST_PREV,
df=True)['close'].values
75. stock_return = close[-1] / close[0] - 1
76. pb = fin['PB'][symbol]
77. market_value = fin['NEGOTIABLEMV'][symbol]
78. # 获取[股票代码, 股票收益率, 账面市值比的分类, 市值的分类, 流通市值]
79. # 其中账面市值比的分类为:大(3)、中(2)、小(1)
80. # 流通市值的分类:大(2)、小(1)
81. if pb < bm_gate[0]:
82. if market_value < size_gate:
83. label = [symbol, stock_return, context.BM_SMA, context.MV_SMA,
market_value]
84. else:
85. label = [symbol, stock_return, context.BM_SMA, context.MV_BIG,
market_value]
86. elif pb < bm_gate[1]:
87. if market_value < size_gate:
88. label = [symbol, stock_return, context.BM_MID, context.MV_SMA,
market_value]
89. else:
90. label = [symbol, stock_return, context.BM_MID, context.MV_BIG,
market_value]
91. elif market_value < size_gate:
92. label = [symbol, stock_return, context.BM_BIG, context.MV_SMA,
market_value]
93. else:
94. label = [symbol, stock_return, context.BM_BIG, context.MV_BIG,
market_value]
95. if len(x_return) == 0:
96. x_return = label
97. else:
98. x_return = np.vstack([x_return, label])
99.
100. # 将股票代码、 股票收益率、 账面市值比的分类、 市值的分类、 流通市值存为数据表
101. stocks = DataFrame(data=x_return, columns=['symbol', 'return', 'BM',

本文档使用 掘金量化 构建 - 43 -
多因子选股(股票)

'NEGOTIABLEMV', 'mv'])
102. stocks.index = stocks.symbol
103. columns = ['return', 'BM', 'NEGOTIABLEMV', 'mv']
104. for column in columns:
105. stocks[column] = stocks[column].astype(np.float64)
106.
107. # 计算SMB.HML和市场收益率(市值加权法)
108. smb_s = (market_value_weighted(stocks, context.MV_SMA, context.BM_SMA) +
109. market_value_weighted(stocks, context.MV_SMA, context.BM_MID) +
110. market_value_weighted(stocks, context.MV_SMA, context.BM_BIG)) / 3
111.
112. # 获取大市值组合的市值加权组合收益率
113. smb_b = (market_value_weighted(stocks, context.MV_BIG, context.BM_SMA) +
114. market_value_weighted(stocks, context.MV_BIG, context.BM_MID) +
115. market_value_weighted(stocks, context.MV_BIG, context.BM_BIG)) / 3
116. smb = smb_s - smb_b
117.
118. # 获取大账面市值比组合的市值加权组合收益率
119. hml_b = (market_value_weighted(stocks, context.MV_SMA, 3) +
120. market_value_weighted(stocks, context.MV_BIG, context.BM_BIG)) / 2
121.
122. # 获取小账面市值比组合的市值加权组合收益率
123. hml_s = (market_value_weighted(stocks, context.MV_SMA, context.BM_SMA) +
124. market_value_weighted(stocks, context.MV_BIG, context.BM_SMA)) / 2
125. hml = hml_b - hml_s
126.
127. # 获取市场收益率
128. close = history_n(symbol='SHSE.000300', frequency='1d', count=context.date + 1,
129. end_time=last_day, fields='close', skip_suspended=True,
130. fill_missing='Last', adjust=ADJUST_PREV, df=True)['close'].values
131. market_return = close[-1] / close[0] - 1
132. coff_pool = []
133.
134. # 对每只股票进行回归获取其alpha值
135. for stock in stocks.index:
136. x_value = np.array([[market_return], [smb], [hml], [1.0]])
137. y_value = np.array([stocks['return'][stock]])
138. # OLS估计系数
139. coff = np.linalg.lstsq(x_value.T, y_value)[0][3]
140. coff_pool.append(coff)
141.
142. # 获取alpha最小并且小于0的10只的股票进行操作(若少于10只则全部买入)
143. stocks['alpha'] = coff_pool
144. stocks = stocks[stocks.alpha < 0].sort_values(by='alpha').head(10)
145. symbols_pool = stocks.index.tolist()
146. positions = context.account().positions()
147.
148. # 平不在标的池的股票
149. for position in positions:
150. symbol = position['symbol']
151. if symbol not in symbols_pool:
152. order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
153. position_side=PositionSide_Long)
154. print('市价单平不在标的池的', symbol)
155.

本文档使用 掘金量化 构建 - 44 -
多因子选股(股票)

156. # 获取股票的权重
157. percent = context.ratio / len(symbols_pool)
158.
159. # 买在标的池中的股票
160. for symbol in symbols_pool:
161. order_target_percent(symbol=symbol, percent=percent,
order_type=OrderType_Market,
162. position_side=PositionSide_Long)
163. print(symbol, '以市价单调多仓到仓位', percent)
164.
165. if __name__ == '__main__':
166. '''
167. strategy_id策略ID,由系统生成
168. filename文件名,请与本文件名保持一致
169. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
170. token绑定计算机的ID,可在系统设置-密钥管理中生成
171. backtest_start_time回测开始时间
172. backtest_end_time回测结束时间
173. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
174. backtest_initial_cash回测初始资金
175. backtest_commission_ratio回测佣金比例
176. backtest_slippage_ratio回测滑点比例
177. '''
178. run(strategy_id='strategy_id',
179. filename='main.py',
180. mode=MODE_BACKTEST,
181. token='token_id',
182. backtest_start_time='2017-07-01 08:00:00',
183. backtest_end_time='2017-10-01 16:00:00',
184. backtest_adjust=ADJUST_PREV,
185. backtest_initial_cash=10000000,
186. backtest_commission_ratio=0.0001,
187. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性检验

设定初始资金1000万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 45 -
多因子选股(股票)

回测期累计收益为4.71%,年化收益率为19.33%,沪深300指数收益率为5.09%,策略整体跑输沪深300指数。最大回撤为
4.18%,胜率为65%。

为了检验策略的稳健性,改变回测时间,得到回测结果如下。

回测时间 时间长度 年化收益率 最大回撤

2017.07.01-2017.10.01 3个月 19.33% 4.18%

2017.07.01-2017.12.31 5个月 12.54% 7.53%

2017.07.01-2018.07.01 12个月 -8.09% 23.17%

2017.07.01-2019.07.01 24个月 3.27% 35.38%

2017.07.01-2020.07.01 36个月 6.19% 35.37%

由上表可以看出,策略收益除了在2017年7月1日至2018年7月1日以外其他时间段收益均为正。在2017年7月1日至2017年10
月1日期间收益率最高,年化收益率为19.33%。回测期最大回撤随着时间长度的增加而增加,最高达到35.38%,与获得的收益
相比,承受风险过大。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 46 -
网格交易(期货)

网格交易(期货)
网格交易法(期货)
1. 原理
什么是网格交易法?
怎样设计网格?
网格交易法的盈利情况
核心
2. 策略思路
策略难点:
3. 策略代码
4. 回测结果和稳健性分析

网格交易法(期货)

1. 原理

什么是网格交易法?

网格交易法是一种利用行情震荡进行获利的策略。在标的价格不断震荡的过程中,对标的价格绘制网格,在市场价格触碰到某
个网格线时进行加减仓操作尽可能获利。

网格交易法属于左侧交易的一种。与右侧交易不同,网格交易法并非跟随行情,追涨杀跌,而是逆势而为,在价格下跌时买
入,价格上涨时卖出。

怎样设计网格?

投资者可以随意设置网格的宽度和数量。既可以设置为等宽度,也可以设置为不等宽度的。设置等宽度网格可能会导致买点卖
点过早,收益率较低。设置不等宽度网格能够避免这个问题,但如果行情出现不利变动,可能会错失买卖机会。

网格交易法的盈利情况

在行情震荡上涨时:

假设格子之间的差为1元钱,每变化一个格子相应的买入或卖出1手,则通过网格交易当前账户的净收益为6元,持空仓4手,持

本文档使用 掘金量化 构建 - 47 -
网格交易(期货)

仓均价为12.5元。

行情震荡下跌时:

同理可知,净收益为8元,持4手多仓,平均成本为7.5元。

可以看到,无论行情上涨还是下跌,已平仓的部分均为正收益,未平仓的部分需要等下一个信号出现再触发交易。

即使网格交易能够获得较为稳定的收益,但也存在一定的风险。如果行情呈现大涨或大跌趋势,会导致不断开仓,增加风险敞
口。这也是为什么网格交易更适用震荡行情,不合适趋势性行情。

核心

网格交易主要包括以下几个核心要点:

- 挑选的标的最好是价格变化较大,交易较为活跃
网格交易是基于行情震荡进行获利的策略,如果标的不活跃,价格波动不大,很难触发交易。
- 选出网格的压力位和阻力位
确定适当的压力位和阻力位,使价格大部分时间能够在压力位和阻力位之间波动。如果压力位和阻力位设置范围过大,会导致
难以触发交易;如果压力位和阻力位设置范围过小,则会频繁触发交易。
- 设置网格的宽度和数量
设定多少个网格以及网格的宽度可根据投资者自身喜好自行确定。

2. 策略思路

第一步:确定价格中枢、压力位和阻力位
第二步:确定网格的数量和间隔
第三步:当价格触碰到网格线时,若高于买入价,则每上升一格卖出m手;若低于买入价,则每下跌一格买入m手。

回测标的:SHFE.rb1901
回测时间:2018-07-01 到 2018-10-01
回测初始资金:10万
注意:若修改回测期,需要修改对应的回测标的。

策略难点:

怎样记录价格是否突破网格线?

本文档使用 掘金量化 构建 - 48 -
网格交易(期货)

解决方法:有些人可能会想到用当前价格与网格线对应的价格进行比较,但这样操作比较麻烦,步骤繁琐。这里采用区域判断
方式。根据网格线划分网格区域为1、2、3、4、5、6.利用pandas库提供的cut函数,将当前价格所处的网格区域表示出来。
当网格区域发生变化,说明价格突破了一个网格线。

如何避免出现4区-5区开仓一次,5区-4区又平仓一次这种“假突破”?

解决方法:4-5开仓一次和5-4平仓一次实际上突破的是一根线,此时的形态是价格沿着这根线上下波动。只有第一次穿过这条
线时才是真正的交易信号,其他的并没有形成突破。因此我们需要一个变量储存每一次交易时网格区域的变化形态(按照从大到
小的顺序),比如5-4可以记为[4,5],4-5记为[4,5]。当新的记录=旧的记录时,信号失效。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np
4. import pandas as pd
5. from gm.api import *
6.
7. '''
8. 本策略标的为:SHFE.rb1901
9. 价格中枢设定为:前一交易日的收盘价
10. 从阻力位到压力位分别为:1.03 * open、1.02 * open、1.01 * open、open、0.99 * open、0.98 *
open、0.97 * open
11. 每变动一个网格,交易量变化100个单位
12. 回测数据为:SHFE.rb1901的1min数据
13. 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00
14. '''
15.
16.
17. def init(context):
18. # 策略标的为SHFE.rb1901
19. context.symbol = 'SHFE.rb1901'
20. # 订阅SHFE.rb1901, bar频率为1min
21. subscribe(symbols = context.symbol, frequency='60s')
22. # 设置每变动一格,增减的数量
23. context.volume = 1
24. # 储存前一个网格所处区间,用来和最新网格所处区间作比较
25. context.last_grid = 0
26. # 以前一日的收盘价为中枢价格
27. context.center = history_n(symbol=
context.symbol,frequency='1d',end_time=context.now,count = 1,fields = 'close')[0]
['close']
28. # 记录上一次交易时网格范围的变化情况(例如从4区到5区,记为4,5)
29. context.grid_change_last = [0,0]
30.
31.
32. def on_bar(context, bars):
33. bar = bars[0]
34. # 获取多仓仓位
35. position_long = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
36. # 获取空仓仓位
37. position_short = context.account().position(symbol=context.symbol,

本文档使用 掘金量化 构建 - 49 -
网格交易(期货)

side=PositionSide_Short)
38.
39. # 设置网格和当前价格所处的网格区域
40. context.band = np.array([0.97, 0.98, 0.99, 1, 1.01, 1.02, 1.03]) * context.center
41. grid = pd.cut([bar.close], context.band, labels=[1, 2, 3, 4, 5, 6])[0]
42.
43. # 如果价格超出网格设置范围,则提示调节网格宽度和数量
44. if np.isnan(grid):
45. print('价格波动超过网格范围,可适当调节网格宽度和数量')
46.
47. # 如果新的价格所处网格区间和前一个价格所处的网格区间不同,说明触碰到了网格线,需要进行交易
48. # 如果新网格大于前一天的网格,做空或平多
49. if context.last_grid < grid:
50. # 记录新旧格子范围(按照大小排序)
51. grid_change_new = [context.last_grid,grid]
52. # 几种例外:
53. # 当last_grid = 0 时是初始阶段,不构成信号
54. # 如果此时grid = 3,说明当前价格仅在开盘价之下的3区域中,没有突破网格线
55. # 如果此时grid = 4,说明当前价格仅在开盘价之上的4区域中,没有突破网格线
56. if context.last_grid == 0:
57. context.last_grid = grid
58. return
59. if context.last_grid != 0:
60. # 如果前一次开仓是4-5,这一次是5-4,算是没有突破,不成交
61. if grid_change_new != context.grid_change_last:
62. # 更新前一次的数据
63. context.last_grid = grid
64. context.grid_change_last = grid_change_new
65. # 如果有多仓,平多
66. if position_long:
67. order_volume(symbol=context.symbol, volume=context.volume,
side=OrderSide_Sell, order_type=OrderType_Market,
68. position_effect=PositionEffect_Close)
69. print('以市价单平多仓{}手'.format(context.volume))
70. # 否则,做空
71. if not position_long:
72. order_volume(symbol=context.symbol, volume=context.volume,
side=OrderSide_Sell, order_type=OrderType_Market,
73. position_effect=PositionEffect_Open)
74. print('以市价单开空{}手'.format(context.volume))
75.
76. # 如果新网格小于前一天的网格,做多或平空
77. if context.last_grid > grid:
78. # 记录新旧格子范围(按照大小排序)
79. grid_change_new = [grid,context.last_grid]
80. # 几种例外:
81. # 当last_grid = 0 时是初始阶段,不构成信号
82. # 如果此时grid = 3,说明当前价格仅在开盘价之下的3区域中,没有突破网格线
83. # 如果此时grid = 4,说明当前价格仅在开盘价之上的4区域中,没有突破网格线
84. if context.last_grid == 0:
85. context.last_grid = grid
86. return
87. if context.last_grid != 0:
88. # 如果前一次开仓是4-5,这一次是5-4,算是没有突破,不成交

本文档使用 掘金量化 构建 - 50 -
网格交易(期货)

89. if grid_change_new != context.grid_change_last:


90. # 更新前一次的数据
91. context.last_grid = grid
92. context.grid_change_last = grid_change_new
93. # 如果有空仓,平空
94. if position_short:
95. order_volume(symbol=context.symbol, volume=context.volume,
side=OrderSide_Buy,
96. order_type=OrderType_Market,
97. position_effect=PositionEffect_Close)
98. print('以市价单平空仓{}手'.format(context.volume))
99.
100. # 否则,做多
101. if not position_short:
102. order_volume(symbol=context.symbol, volume=context.volume,
side=OrderSide_Buy,
103. order_type=OrderType_Market,
104. position_effect=PositionEffect_Open)
105. print('以市价单开多{}手'.format(context.volume))
106.
107. # 设计一个止损条件:当持仓量达到10手,全部平仓
108. if position_short == 10 or position_long == 10:
109. order_close_all()
110. print('触发止损,全部平仓')
111.
112.
113. if __name__ == '__main__':
114. '''
115. strategy_id策略ID,由系统生成
116. filename文件名,请与本文件名保持一致
117. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
118. token绑定计算机的ID,可在系统设置-密钥管理中生成
119. backtest_start_time回测开始时间
120. backtest_end_time回测结束时间
121. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
122. backtest_initial_cash回测初始资金
123. backtest_commission_ratio回测佣金比例
124. backtest_slippage_ratio回测滑点比例
125. '''
126. run(strategy_id='strategy_id',
127. filename='main.py',
128. mode=MODE_BACKTEST,
129. token='token_id',
130. backtest_start_time='2018-07-01 08:00:00',
131. backtest_end_time='2018-10-01 16:00:00',
132. backtest_adjust=ADJUST_PREV,
133. backtest_initial_cash=100000,
134. backtest_commission_ratio=0.0001,
135. backtest_slippage_ratio=0.0001)

4. 回测结果和稳健性分析

设定初始资金10万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 51 -
网格交易(期货)

回测期间策略累计收益率为4.16%,年化收益率为16.50%,基准收益率为0.91%,整体跑赢指数。最大回撤为0.72%,胜率为
100%。在2018年7月12日以后,标的没有交易,说明此时标的价格已经超过设置的网格范围,可以适当加宽或增加网格数量。

为了检验策略的稳健性,保持标的和回测期不变,改变网格间隔和网格数量,得到回测结果如下表所示。

网格间隔 网格数量 手续费 年化收益率 最大回撤 胜率 未平头寸

0.01*价格中枢 6 50.55 16.50% 0.72% 100% 0手多单

0.02*价格中枢 6 36.89 26.21% 7.82% 100% 2手空单

0.005*价格中枢 6 61.42 -30.24% 22.04% 85.71% 3手空单

0.01*价格中枢 4 18.11 15.49% 4.17% 100% 1手多单

0.02*价格中枢 4 18.16 16.08% 4.16% 100% 1手空单

0.005*价格中枢 4 21.72 -51.27% 31.39% 100% 4手空单

可以看到,改变网格间隔和网格数量对回测结果的影响较大。整体胜率较高,但存在部分未平头寸。在网格间隔设置为0.01倍
价格中枢时,整体收益率最高,最大回撤也处于较低水平;在网格间隔为0.02倍中枢价格时,整体收益率最差。由此可以看
出,网格间隔对收益率的影响要高于网格数量。因此,在利用网格交易法时,需要设置合理的网格间隔。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 52 -
指数增强(股票)

指数增强(股票)
指数增强策略
1. 策略原理
2. 策略步骤
3. 策略代码
4. 回测结果和稳健性分析

指数增强策略

1. 策略原理

说到指数增强,就不得不说指数。

在进行股票投资时,有一种分类方式是将投资分为主动型投资和被动型投资。被动型投资是指完全复制指数,跟随指数的投资
方式。与被动型投资相反,主动型投资是根据投资者的知识结合经验进行主动选股,不是被动跟随指数。主动型投资者期望获
得超越市场的收益,被动型投资者满足于市场平均收益率水平。

指数增强是指在跟踪指数的基础上,采用一些判断基准,将不看好的股票权重调低或平仓,将看好的股票加大仓位,以提高收
益率的方法。

既然如此,我已经判断出来哪只是“好股票”,哪只是“一般”的股票,为什么不直接买入?而是要买入指数呢?

指数增强不同于其他主动投资方式,除了注重获取超越市场的收益,还要兼顾降低组合风险,注重收益的稳定性。如果判断失
误,只买入选中股票而非指数会导致投资者承受巨大亏损。

怎样选择股票?

和alpha对冲策略类似,指数增强仅仅是一个思路,怎样选择“好股”还需投资者结合自身经验判断。

本策略利用“动量”这一概念,认为过去5天连续上涨的股票具备继续上涨的潜力,属于强势股;过去5天连续下跌的股票未来会
继续下跌,属于弱势股。

2. 策略步骤

第一步:选择跟踪指数,以权重大于0.35%的成分股为股票池。
第二步:根据个股价格动量来判断是否属于优质股,即连续上涨5天则为优势股;间隔连续下跌5天则为劣质股。
第三步:将优质股权重调高0.2,劣质股权重调低0.2。

回测时间:2017-07-01 08:00:00 到 2017-10-01 16:00:00


回测选股股票池:沪深300成分股
回测初始资金:1000万

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np
4. from gm.api import *
5. from pandas import DataFrame

本文档使用 掘金量化 构建 - 53 -
指数增强(股票)

6.
7.
8. '''
9. 本策略以0.8为初始权重跟踪指数标的沪深300中权重大于0.35%的成份股.
10. 个股所占的百分比为(0.8*成份股权重)*100%.然后根据个股是否:
11. 1.连续上涨5天 2.连续下跌5天
12. 来判定个股是否为强势股/弱势股,并对其把权重由0.8调至1.0或0.6
13. 回测时间为:2017-07-01 08:50:00到2017-10-01 17:00:00
14. '''
15.
16.
17. def init(context):
18. # 资产配置的初始权重,配比为0.6-0.8-1.0
19. context.ratio = 0.8
20.
21. # 获取沪深300当时的成份股和相关数据
22. stock300 = get_history_constituents(index='SHSE.000300', start_date='2017-06-30',
end_date='2017-06-30')[0][
23. 'constituents']
24. stock300_symbol = []
25. stock300_weight = []
26. for key in stock300:
27. # 保留权重大于0.35%的成份股
28. if (stock300[key] / 100) > 0.0035:
29. stock300_symbol.append(key)
30. stock300_weight.append(stock300[key] / 100)
31. context.stock300 = DataFrame([stock300_weight], columns=stock300_symbol, index=
['weight']).T
32. print('选择的成分股权重总和为: ', np.sum(stock300_weight))
33. subscribe(symbols=stock300_symbol, frequency='1d', count=5, wait_group=True)
34.
35.
36. def on_bar(context, bars):
37. # 若没有仓位则按照初始权重开仓
38. for bar in bars:
39. symbol = bar['symbol']
40. position = context.account().position(symbol=symbol, side=PositionSide_Long)
41. if not position:
42. buy_percent = context.stock300['weight'][symbol] * context.ratio
43. order_target_percent(symbol=symbol, percent=buy_percent,
order_type=OrderType_Market,
44. position_side=PositionSide_Long)
45. print(symbol, '以市价单开多仓至仓位:', buy_percent)
46. else:
47.
48. # 获取过去5天的价格数据,若连续上涨则为强势股,权重+0.2;若连续下跌则为弱势股,权重-0.2
49. recent_data = context.data(symbol=symbol, frequency='1d', count=5,
fields='close')['close'].tolist()
50. if all(np.diff(recent_data) > 0):
51. buy_percent = context.stock300['weight'][symbol] * (context.ratio +
0.2)
52. order_target_percent(symbol=symbol, percent=buy_percent,
order_type=OrderType_Market,
53. position_side=PositionSide_Long)
54. print('强势股', symbol, '以市价单调多仓至仓位:', buy_percent)

本文档使用 掘金量化 构建 - 54 -
指数增强(股票)

55. elif all(np.diff(recent_data) < 0):


56. buy_percent = context.stock300['weight'][symbol] * (context.ratio -
0.2)
57. order_target_percent(symbol=symbol, percent=buy_percent,
order_type=OrderType_Market,
58. position_side=PositionSide_Long)
59. print('弱势股', symbol, '以市价单调多仓至仓位:', buy_percent)
60.
61.
62. if __name__ == '__main__':
63. '''
64. strategy_id策略ID,由系统生成
65. filename文件名,请与本文件名保持一致
66. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
67. token绑定计算机的ID,可在系统设置-密钥管理中生成
68. backtest_start_time回测开始时间
69. backtest_end_time回测结束时间
70. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
71. backtest_initial_cash回测初始资金
72. backtest_commission_ratio回测佣金比例
73. backtest_slippage_ratio回测滑点比例
74. '''
75. run(strategy_id='strategy_id',
76. filename='main.py',
77. mode=MODE_BACKTEST,
78. token='token_id',
79. backtest_start_time='2017-07-01 08:00:00',
80. backtest_end_time='2017-10-01 16:00:00',
81. backtest_adjust=ADJUST_PREV,
82. backtest_initial_cash=10000000,
83. backtest_commission_ratio=0.0001,
84. backtest_slippage_ratio=0.0001)

4. 回测结果和稳健性分析
设定初始资金1000万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。回测期结果如下图所示:

本文档使用 掘金量化 构建 - 55 -
指数增强(股票)

回测期累计收益率为2.76%,年化收益率为11.34%,沪深300指数收益率为5.09%,整体跑输指数。最大回撤为1.88%,胜率
为74.62%。

为了探究策略的稳健性,改变回测期,策略表现如下表所示。

回测期 回测期长度 年化收益率 最大回撤

2017-07-01至2017-10-01 3个月 11.34% 1.88%

2017-07-01至2017-12-31 6个月 29.06% 5.90%

2017-07-01至2018-07-01 12个月 4.71% 18.73%

2017-07-01至2019-07-01 24个月 11.11% 24.33%

2017-07-01至2020-07-01 36个月 5.48% 24.30%

由上表可知,改变策略回测周期长度,策略收益率均为正,但都处于较低水平(除了2017年7月1日至2018年7月1日收益率达
到29.06%)。随着策略回测期拉长,最大回撤不断增大。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 56 -
跨品种套利(期货)

跨品种套利(期货)
跨品种套利
1. 原理
2. 策略思路
3. 策略代码
4. 回测结果与稳健性分析

跨品种套利

1. 原理

什么是套利?

套利是指在买入或卖出一种金融资产的同时卖出或买入另一种相关的金融资产从中利用价差获得套利的过程。

什么是跨品种套利?

当两个合约有很强的相关性时,可能存在相似的变动关系,两种合约之间的价差会维持在一定的水平上。当市场出现变化时,
两种合约之间的价差会偏离均衡水平。此时,可以买入其中一份合约同时卖出其中一份合约,当价差恢复到正常水平时平仓,
获取收益。

以大商所玉米和淀粉为例,合约分别为DCE.c1801和DCE.cs1801。二者之间相关性为0.7333,价差处于相对稳定合理区
间。如图所示。

二者价差整体处于250-350之间。当价差偏离此区间时,可以进行跨品种套利。

跨品种套利有以下几个特点:

1.套利的两种资产必须有一定的相关性。
2.两种合约标的不同,到期时间相同。
3.两种资产之间的价差呈现一定规律。

本文档使用 掘金量化 构建 - 57 -
跨品种套利(期货)

怎样确定合约之间有相关性?

最常用的方法是利用EG两步法对两个序列做协整检验,判断两个序列是否平稳。只有单整阶数相同,二者才有可能存在一定的
关系。

以大豆和豆粕为例,选取其在2017年1月1日至2018年1月1日的主力合约价格时间序列,利用statsmodels包进行协整检验。

检验结果为:
焦炭的t = -1.7886,1%置信区间的临界值为-3.4576,说明该序列在99%的置信水平下平稳。
焦煤的t = -2.0500,1%置信区间的临界值为-3.4576,说明该序列在99%的置信水平下平稳。

因此,二者都为平稳序列。

利用OLS回归检残差序列是否平稳,残差的t=-2.3214,临界值为-3.4577,说明残差平稳。因此,可以认为二者之间存在一
定关系。

回归后的残差图如下:

对残差进行ks检验,检验结果p=0,说明残差分布为正态分布。

策略设计

传统利用价差进行跨品种套利的方法是计算出均值和方差,设定开仓、平仓和止损阈值。当新的价格达到阈值时,进行相应的
开仓和平仓操作。

应该怎样确定均值?

均值的选取主要有两种方法,第一种方法是固定均值。先历史价格计算相应的阈值(比如利用2017年2月-2017年6月的数据计
算阈值,在2019年7月进行套利),再用最新价差进行比较,会发现前后均值差异很大。如图所示。

本文档使用 掘金量化 构建 - 58 -
跨品种套利(期货)

因此,常用变动的均值设定阈值。即用过去N天两个标的之间差值的均值和方差。

2. 策略思路
第一步:选择相关性较高的两个合约,本例选择大商所的焦炭和焦煤。
第二步:以过去30个的1d频率bar的均值正负0.75个标准差作为开仓阈值,以正负2个标准差作为止损阈值。
第三步:最新价差上穿上界时做空价差,回归到均值附近平仓;下穿下界时做多价差,回归到均值附近平仓。设定止损点,触
发止损点则全部平仓。

回测期:2018-02-01 8:00:00 至 2018-12-31 16:00:00


回测标的:DCE.j1901, DCE.jm1901
回测初始资金:200万
注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. from gm.api import *
4. import numpy as np
5.
6. def init(context):
7. # 选择的两个合约
8. context.symbol = ['DCE.j1901', 'DCE.jm1901']
9. # 订阅历史数据
10. subscribe(symbols=context.symbol,frequency='1d',count=11,wait_group=True)
11.
12. def on_bar(context, bars):
13. # 数据提取
14. j_close =

本文档使用 掘金量化 构建 - 59 -
跨品种套利(期货)

context.data(symbol=context.symbol[0],frequency='1d',fields='close',count=31).values
15. jm_close =
context.data(symbol=context.symbol[1],frequency='1d',fields='close',count=31).values
16. # 提取最新价差
17. new_price = j_close[-1] - jm_close[-1]
18. # 计算历史价差,上下限,止损点
19. spread_history = j_close[:-2] - jm_close[:-2]
20. context.spread_history_mean = np.mean(spread_history)
21. context.spread_history_std = np.std(spread_history)
22. context.up = context.spread_history_mean + 0.75 * context.spread_history_std
23. context.down = context.spread_history_mean - 0.75 * context.spread_history_std
24. context.up_stoppoint = context.spread_history_mean + 2 * context.spread_history_std
25. context.down_stoppoint = context.spread_history_mean - 2 *
context.spread_history_std
26. # 查持仓
27. position_jm_long = context.account().position(symbol=context.symbol[0],side=1)
28. position_jm_short = context.account().position(symbol=context.symbol[0],side=2)
29.
30. # 设计买卖信号
31. # 设计开仓信号
32. if not position_jm_short and not position_jm_long:
33. if new_price > context.up:
34. print('做空价差组合')
35.
order_volume(symbol=context.symbol[0],side=OrderSide_Sell,volume=1,order_type=OrderType_Market
36. order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
37. if new_price < context.down:
38. print('做多价差组合')
39. order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
40. order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
41.
42. # 设计平仓信号
43. # 持jm多仓时
44. if position_jm_long:
45. if new_price >= context.spread_history_mean:
46. # 价差回归到均值水平时,平仓
47. print('价差回归到均衡水平,平仓')
48. order_volume(symbol=context.symbol[0], side=OrderSide_Sell, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
49. order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
50.
51. if new_price < context.down_stoppoint:
52. # 价差达到止损位,平仓止损
53. print('价差超过止损点,平仓止损')
54. order_volume(symbol=context.symbol[0], side=OrderSide_Sell, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
55. order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
56.
57. # 持jm空仓时

本文档使用 掘金量化 构建 - 60 -
跨品种套利(期货)

58. if position_jm_short:
59. if new_price <= context.spread_history_mean:
60. # 价差回归到均值水平时,平仓
61. print('价差回归到均衡水平,平仓')
62. order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
63. order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
64.
65. if new_price > context.up_stoppoint:
66. # 价差达到止损位,平仓止损
67. print('价差超过止损点,平仓止损')
68. order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
69. order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
70.
71.
72. if __name__ == '__main__':
73. '''
74. strategy_id策略ID,由系统生成
75. filename文件名,请与本文件名保持一致
76. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
77. token绑定计算机的ID,可在系统设置-密钥管理中生成
78. backtest_start_time回测开始时间
79. backtest_end_time回测结束时间
80. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
81. backtest_initial_cash回测初始资金
82. backtest_commission_ratio回测佣金比例
83. backtest_slippage_ratio回测滑点比例
84. '''
85. run(strategy_id='strategy_id',
86. filename='main.py',
87. mode=MODE_BACKTEST,
88. token='token',
89. backtest_start_time='2018-02-01 08:00:00',
90. backtest_end_time='2018-12-31 16:00:00',
91. backtest_adjust=ADJUST_PREV,
92. backtest_initial_cash=2000000,
93. backtest_commission_ratio=0.0001,
94. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析

设定初始资金200万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 61 -
跨品种套利(期货)

回测期累计收益率2.80%,年化收益率为3.06%,沪深300收益率为-29.09%,策略收益跑赢基准收益。最大回撤率为
2.03%,胜率为48.25%。

为了检验策略的稳健性,改变数据的频率和均线的计算周期,结果如下。

数据频率 均线周期 年化收益率 最大回撤

1d 10 3.06% 2.30%

1d 20 3.51% 2.53%

1d 30 0.55% 2.45%

3600s 10 -7.84% 7.40%

3600s 20 -4.11% 5.28%

3600s 30 -2.89% 3.91%

900s 10 -10.07% 9.38%

900s 20 -9.39% 8.82%

900s 30 -7.65% 7.32%

可以看出,该策略只在1d的频率下实现了盈利,在其他频率下,收益均为负,说明该策略对于高频场景的适用有一定限制。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 62 -
跨期套利(期货)

跨期套利(期货)
跨期套利策略
1. 原理
什么是跨期套利?
套利方法
协整检验
2. 策略步骤
3. 策略代码
4. 回测结果

跨期套利策略

1. 原理

什么是跨期套利?

跨期套利是指在同益市场利用标的相同、交割月份不同的商品期货合约进行长短期套利的策略。跨期套利本质上是一种风险对
冲,当价格出现单方向变动时,单边投机者要承担价格反向变动的风险,而跨期套利过滤了大部分的价格波动风险,只承担价
差反向变动的风险。

跨期套利相较于跨品种套利而言更复杂一些。跨期套利分为牛市套利、熊市套利、牛熊交换套利。每种套利方式下还有正向套
利和反向套利。不管是哪种套利方式,其核心都是认为“价差会向均值回归”。因此,在价差偏离均值水平时,按照判断买入被
低估的合约,卖出被高估的合约。

套利方法

套利方法可归结为以下几类:

价差(近-远) 未来价 原理 操作

近月增长 >远月增长
偏大 上涨/下跌 买近卖远
近月下跌 < 远月下跌

近月增长 < 远月增长


偏小 上涨/下跌 卖近买远
近月下跌 > 远月下跌

协整检验

要想判断两个序列之间是否存在关系,需要对序列进行协整检验。以大商所豆粕为例,对DCE.m1701和DCE.m1705进行检验。

1701合约的t值 = -2.1176,临界值为-3.4769,t > 临界值说明序列平稳。


1705合约的t值 = -2.5194,临界值为-3.4769,t > 临界值说明序列平稳。

两个序列都为单整序列,残差序列也平稳,说明二者之间存在长期稳定的均衡关系。

2. 策略步骤

第一步:选择同一标的不同月份的合约,本策略以豆粕为例。
第二步:计算价差的上下轨。

本文档使用 掘金量化 构建 - 63 -
跨期套利(期货)

第三步:设计信号。价差上穿上轨,买近卖远;价差下穿下轨,卖近买远。
价差达到止损点时平仓,价差回归到均值附近时平仓。

回测标的:DCE.m1801、DCE.m1805
回测时间:2017-09-25 到 2017-10-01
回测初始资金:200万
注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np
4. from gm.api import *
5. '''
6. 通过计算两个真实价格序列回归残差的0.9个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做
多价差
7. 并在回归至标准差水平内的时候平仓
8. 回测数据为:DCE.m1801和DCE.m1805的1min数据
9. 回测时间为:2017-09-25 08:00:00到2017-10-01 15:00:00
10. '''
11. def init(context):
12. context.goods = ['DCE.m1801', 'DCE.m1805']
13. # 订阅品种数据
14. subscribe(symbols = context.goods,frequency = '1d',count = 31,wait_group = True)
15.
16. def on_bar(context, bars):
17. # 获取历史数据
18. close_1801 = context.data(symbol=context.goods[0], frequency='1d', count=31,
fields='close')['close'].values
19. close_1805 = context.data(symbol=context.goods[1], frequency='1d', count=31,
fields='close')['close'].values
20. # 计算上下轨
21. spread = close_1801[:-2] - close_1805[:-2]
22. spread_new = close_1801[-1] - close_1805[-1]
23. up = np.mean(spread) + 0.75 * np.std(spread)
24. down = np.mean(spread) - 0.75 * np.std(spread)
25. up_stop = np.mean(spread) + 2 * np.std(spread)
26. down_stop = np.mean(spread) - 2 * np.std(spread)
27.
28. # 获取仓位
29. position1801_long = context.account().position(symbol = context.goods[0],side
=PositionSide_Long)
30. position1801_short = context.account().position(symbol = context.goods[0],side
=PositionSide_Short)
31.
32. # 没有仓位时
33. if not position1801_short and not position1801_long:
34. # 上穿上轨时,买近卖远
35. if spread_new > up:
36. order_volume(symbol=context.goods[0], volume=1,
order_type=OrderType_Market, side=OrderSide_Buy, position_effect=PositionEffect_Open)
37. order_volume(symbol=context.goods[1], volume=1,

本文档使用 掘金量化 构建 - 64 -
跨期套利(期货)

order_type=OrderType_Market, side=OrderSide_Sell, position_effect=PositionEffect_Open)


38. print('上穿上轨,买近卖远')
39. # 下穿下轨时,卖近买远
40. if spread_new < down:
41. order_volume(symbol=context.goods[0], volume=1,
order_type=OrderType_Market, side=OrderSide_Sell, position_effect=PositionEffect_Open)
42. order_volume(symbol=context.goods[1], volume=1,
order_type=OrderType_Market, side=OrderSide_Buy, position_effect=PositionEffect_Open)
43. print('下穿下轨,卖近买远')
44.
45. # 价差回归到上轨时,平仓
46. if position1801_long:
47. if spread_new <= np.mean(spread):
48. order_close_all()
49. print('价差回归,平仓')
50. if spread_new > up_stop:
51. order_close_all()
52. print('达到止损点,全部平仓')
53.
54. # 价差回归到下轨时,平仓
55. if position1801_short:
56. if spread_new >= np.mean(spread):
57. order_close_all()
58. print('价差回归,平全部仓')
59. if spread_new < down_stop:
60. order_close_all()
61. print('达到止损点,全部平仓')
62.
63. if __name__ == '__main__':
64. '''
65. strategy_id策略ID,由系统生成
66. filename文件名,请与本文件名保持一致
67. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
68. token绑定计算机的ID,可在系统设置-密钥管理中生成
69. backtest_start_time回测开始时间
70. backtest_end_time回测结束时间
71. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
72. backtest_initial_cash回测初始资金
73. backtest_commission_ratio回测佣金比例
74. backtest_slippage_ratio回测滑点比例
75. '''
76. run(strategy_id='strategy_id',
77. filename='main.py',
78. mode=MODE_BACKTEST,
79. token='token_id',
80. backtest_start_time='2017-07-01 08:00:00',
81. backtest_end_time='2017-12-31 16:00:00',
82. backtest_adjust=ADJUST_PREV,
83. backtest_initial_cash=2000000,
84. backtest_commission_ratio=0.0001,
85. backtest_slippage_ratio=0.0001)

4. 回测结果

本文档使用 掘金量化 构建 - 65 -
跨期套利(期货)

设定初始资金200万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

回测期累计收益率-8.12%,年化收益率为-16.46%。最大回撤率为11.48%,胜率为50.00%。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 66 -
日内回转交易(股票)

日内回转交易(股票)
日内回转策略(股票)
1. 原理
1.1 日内回转交易
1.2 股票的日内回转交易
1.3 MACD指标简介
2. 策略思路
3. 策略代码
4. 回测结果与稳健性分析

日内回转策略(股票)

1. 原理

1.1 日内回转交易

日内回转交易,顾名思义就是在一天内完成“买”和“卖”两个相反方向的操作(可一次也可多次),也就是“T+0”交易。

日内回转可用于股票和期货。其中期货采用“T+0”交易制度,可以直接进行日内回转交易。由于A股采用的是“T+1”交易制度,
无法直接进行日内回转交易,需要先配置一定的底仓再进行回转交易。

1.2 股票的日内回转交易

怎样对股票进行日内回转交易?

首先,在正式交易的前一个交易日配置一定的底仓。以500股为例,记做total = 500。

然后开始正式的日内回转交易。

配置底仓的作用是利用替代法实现“T+0”。由于当天买入的股票当天不能卖出,但底仓是可以卖出的,用底仓替代新买入的股
票进行卖出操作。假设在第二个交易日发生了1次买入,5次卖出交易,每次交易买卖数量为100股。利用turnaround =
[0,0]变量记录每次交易的数量,也是当天收盘时需要回转的记录。其中第一个数据表示当日买入数量,第二个数据表示当日
卖出数量。下表为单个交易日的买卖信号。

信号方向 数量 交易记录 剩余可回转的数量 总仓位

买 100 [100,0] 500 600

卖 100 [100,100] 400 500

卖 100 [100,200] 300 400

卖 100 [100,300] 200 300

卖 100 [100,400] 100 200

假设在表的最后再加一个卖出信号是否可行?

答案是不可行。

因为如果再加一个卖出信号,需要回转的股票数量变为[100,500],即开多100股,开空500股。这就意味着在当天收盘之
前,需要卖出100股,再买入500股进行回转。这个交易日内已经出现5次卖出信号,底仓的500股已经全部卖出,仅有100股今
日买入的仓位,这部分股票是不能当日卖出的。所以,不能再添加卖出信号。

本文档使用 掘金量化 构建 - 67 -
日内回转交易(股票)

因此,在判断买入或卖出信号是否能执行时,隐含一个判断条件。即:

每次交易的数量 + 当日买入的数量(turnaround的第一位)< 底仓数量(以卖出信号为例)。

1.3 MACD指标简介

MACD又称“异移动平均线”,是根据双指数移动平均线发展而来。由快的指数(常12)减去慢的指数(常26)得到DIF,再用
2×(快线DIF-DIF的9日加权移动均线DEA)得到MACD柱。

DIF的计算方法为: DIF = 当天的12日指数移动平均值 - 当天的26日指数应对平均值。


注:上市首日的EMA12和EMA26利用当天的收盘价替代。

2. 策略思路

第一步:设置变量
context.first:底仓配置信号,0表示未配置底仓;1表示配置底仓。
context.trade_n:每次交易数量。
context.day:用来获取前一交易日的时间和最新交易日的时间,第一位是最新交易日,第二位是前一交易日。当二者不同
时,意味着新的一天,需要初始化其他变量。
context.ending:开始回转信号,0表示未触发;1表示已触发。
context.turnaround:当日买卖股票操作记录,也是回转记录。第一位代表买入股数,第二位代表卖出股数。

第二步:计算MACD指标,设计交易信号
当 MACD 小于 0 时,买入对应股票100手;
当 MACD 大于 0 时,卖出对应股票100手;

第三步:接近收盘时,全部回转

回测标的:SHSE.600000
回测期:2017-09-01 8:00:00 到2017-10-01 16:00:00
回测初始资金:200万

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import sys
4. try:
5. import talib
6. except:
7. print('请安装TA-Lib库')
8. # 安装talib请看文档https://www.myquant.cn/docs/gm3_faq/154?
9. sys.exit(-1)
10. from gm.api import *
11.
12.
13. def init(context):
14. # 设置标的股票
15. context.symbol = 'SHSE.600000'
16. # 用于判定第一个仓位是否成功开仓
17. context.first = 0
18. # 订阅浦发银行, bar频率为1min
19. subscribe(symbols=context.symbol, frequency='60s', count=35)

本文档使用 掘金量化 构建 - 68 -
日内回转交易(股票)

20. # 日内回转每次交易100股
21. context.trade_n = 100
22. # 获取昨今天的时间
23. context.day = [0, 0]
24. # 用于判断是否到达接近收盘,所以不再交易
25. context.ending = 1
26.
27.
28. def on_bar(context, bars):
29. bar = bars[0]
30. # 配置底仓
31. if context.first == 0:
32. # 需要保持的总仓位
33. context.total = 10000
34. # 购买10000股浦发银行股票
35. order_volume(symbol=context.symbol, volume=context.total, side=OrderSide_Buy,
36. order_type=OrderType_Market, position_effect=PositionEffect_Open)
37. print(context.symbol, '以市价单开多仓10000股')
38. context.first = 1.
39. day = bar.bob.strftime('%Y-%m-%d')
40. context.day[-1] = int(day[-2:])
41. # 每天的仓位操作
42. context.turnaround = [0, 0]
43. return
44.
45. # 更新最新的日期
46. day = bar.bob.strftime('%Y-%m-%d %H:%M:%S')
47. context.day[0] = bar.bob.day
48.
49. # 若为新的一天,获取可用于回转的昨仓
50. if context.day[0] != context.day[-1]:
51. context.ending = 0
52. context.turnaround = [0, 0]
53.
54. # 如果接近收盘,则不再交易
55. if context.ending == 1:
56. return
57.
58. # 若有可用的昨仓则操作
59. if context.total >= 0:
60. # 获取时间序列数据
61. symbol = bar['symbol']
62. recent_data = context.data(symbol=symbol, frequency='60s', count=35,
fields='close')
63. # 计算MACD线
64. macd = talib.MACD(recent_data['close'].values)[0][-1]
65. # 根据MACD>0则开仓,小于0则平仓
66. if macd > 0:
67. # 多空单向操作都不能超过昨仓位,否则最后无法调回原仓位
68. if context.turnaround[0] + context.trade_n < context.total:
69. # 计算累计仓位
70. context.turnaround[0] += context.trade_n
71. order_volume(symbol=context.symbol, volume=context.trade_n,
side=OrderSide_Buy,

本文档使用 掘金量化 构建 - 69 -
日内回转交易(股票)

72. order_type=OrderType_Market,
position_effect=PositionEffect_Open)
73. print(symbol, '市价单开多仓', context.trade_n, '股')
74. elif macd < 0:
75. if context.turnaround[1] + context.trade_n < context.total:
76. context.turnaround[1] += context.trade_n
77. order_volume(symbol=context.symbol, volume=context.trade_n,
side=OrderSide_Sell,
78. order_type=OrderType_Market,
position_effect=PositionEffect_Close)
79. print(symbol, '市价单开空仓', context.trade_n, '股')
80. # 临近收盘时若仓位数不等于昨仓则回转所有仓位
81. if day[11:16] == '14:55' or day[11:16] == '14:57':
82. position = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
83. if position['volume'] != context.total:
84. order_target_volume(symbol=context.symbol, volume=context.total,
order_type=OrderType_Market,
85. position_side=PositionSide_Long)
86. print('市价单回转仓位操作...')
87. context.ending = 1
88. # 更新过去的日期数据
89. context.day[-1] = context.day[0]
90.
91.
92. if __name__ == '__main__':
93. '''
94. strategy_id策略ID,由系统生成
95. filename文件名,请与本文件名保持一致
96. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
97. token绑定计算机的ID,可在系统设置-密钥管理中生成
98. backtest_start_time回测开始时间
99. backtest_end_time回测结束时间
100. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
101. backtest_initial_cash回测初始资金
102. backtest_commission_ratio回测佣金比例
103. backtest_slippage_ratio回测滑点比例
104. '''
105. run(strategy_id='strategy_id',
106. filename='main.py',
107. mode=MODE_BACKTEST,
108. token='token_id',
109. backtest_start_time='2017-09-01 08:00:00',
110. backtest_end_time='2017-10-01 16:00:00',
111. backtest_adjust=ADJUST_PREV,
112. backtest_initial_cash=2000000,
113. backtest_commission_ratio=0.0001,
114. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析

设定初始资金200万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 70 -
日内回转交易(股票)

回测期累计收益率为-0.04%,年化收益率为-0.46%,沪深300收益率为0.16%,整体跑输指数。最大回撤为0.23%,胜率为
40.07%。

为了检验策略的稳健性,改变回测期,得到回测结果如下表所示。

指标 2020.5 2020.6 2020.7 2020.8 2020.9 2020.10

年化收益率 0.48% -1.68% 5.34% -1.41% -6.51% -0.58%

最大回撤 0.15% 0.21% 0.52% 0.30% 0.52% 0.21%

胜率 51.36% 40.38% 41.38% 31.04% 10.10% 51.33%

可以看出,日内回转交易的最大回撤都维持在较低水平。同时,胜率和年化收益率也相对偏低。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 71 -
做市商交易(期货)

做市商交易(期货)
做市商策略
1. 原理
做市商制度
2. 策略思路
3. 策略代码
4. 回测结果

做市商策略

1. 原理

做市商制度

做市商制度是一种报价驱动制度。做市商根据自己的判断,不断地报出买入报价和卖出报价,以自有资金与投资者进行交易。
做市商获取的收益就是买入价和卖出价的价差。

假设做市商以6344卖出一手合约,同时以6333买入一手合约。如果都成交,做市商可净获利11个点。但如果当时合约价格持
续走高或走低,做市商没有对手方能够成交,这时就不得不提高自己的买价或降低自己的卖价进行交易,做市商就会亏损。因
此,做市商并不是稳赚不赔的。

2. 策略思路

第一步:订阅tick数据(只有最近3个月数据)
第二步:获取tick数据中的卖一和买一价格。
第三步:以买一价格开多,以卖一价格开空。以卖一价格平多,以买一价格平空。

回测标的:CZCE.CF801
回测时间: 2017-09-29 11:25:00 至 2017-09-29 11:30:00
回测初始资金:50万
注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. from gm.api import *
4.
5.
6. '''
7. 本策略通过不断对CZCE.CF801进行:
8. 买(卖)一价现价单开多(空)仓和卖(买)一价平多(空)仓来做市
9. 并以此赚取差价
10. 回测数据为:CZCE.CF801的tick数据
11. 回测时间为:2017-09-29 11:25:00到2017-09-29 11:30:00
12. 需要特别注意的是:本平台对于回测对限价单固定完全成交,本例子 仅供参考.
13. 敬请通过适当调整回测参数
14. 1.backtest_commission_ratio回测佣金比例

本文档使用 掘金量化 构建 - 72 -
做市商交易(期货)

15. 2.backtest_slippage_ratio回测滑点比例
16. 3.backtest_transaction_ratio回测成交比例
17. 以及优化策略逻辑来达到更贴近实际的回测效果
18. 目前只支持最近三个月的tick数据,回测时间和标的需要修改
19. '''
20.
21.
22. def init(context):
23. # 订阅CZCE.CF801的tick数据
24. context.symbol = 'CZCE.CF801'
25. subscribe(symbols=context.symbol, frequency='tick')
26.
27.
28. def on_tick(context, tick):
29. quotes = tick['quotes'][0]
30. # 获取持有的多仓
31. position_long = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
32. # 获取持有的空仓
33. position_short = context.account().position(symbol=context.symbol,
side=PositionSide_Short)
34. # 没有仓位则双向开限价单
35. # 若有仓位则限价单平仓
36. if not position_long:
37. # 获取买一价
38. price = quotes['bid_p']
39. print('买一价为: ', price)
40. order_target_volume(symbol=context.symbol, volume=1, price=price,
order_type=OrderType_Limit,
41. position_side=PositionSide_Long)
42. print('CZCE.CF801开限价单多仓1手')
43. else:
44. # 获取卖一价
45. price = quotes['ask_p']
46. print('卖一价为: ', price)
47. order_target_volume(symbol=context.symbol, volume=0, price=price,
order_type=OrderType_Limit,
48. position_side=PositionSide_Long)
49. print('CZCE.CF801平限价单多仓1手')
50. if not position_short:
51. # 获取卖一价
52. price = quotes['ask_p']
53. print('卖一价为: ', price)
54. order_target_volume(symbol=context.symbol, volume=1, price=price,
order_type=OrderType_Limit,
55. position_side=PositionSide_Short)
56. print('CZCE.CF801卖一价开限价单空仓')
57. else:
58. # 获取买一价
59. price = quotes['bid_p']
60. print('买一价为: ', price)
61. order_target_volume(symbol=context.symbol, volume=0, price=price,
order_type=OrderType_Limit,
62. position_side=PositionSide_Short)

本文档使用 掘金量化 构建 - 73 -
做市商交易(期货)

63. print('CZCE.CF801买一价平限价单空仓')
64.
65.
66. if __name__ == '__main__':
67. '''
68. strategy_id策略ID,由系统生成
69. filename文件名,请与本文件名保持一致
70. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
71. token绑定计算机的ID,可在系统设置-密钥管理中生成
72. backtest_start_time回测开始时间
73. backtest_end_time回测结束时间
74. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
75. backtest_initial_cash回测初始资金
76. backtest_commission_ratio回测佣金比例
77. backtest_slippage_ratio回测滑点比例
78. backtest_transaction_ratio回测成交比例
79. '''
80. run(strategy_id='strategy_id',
81. filename='main.py',
82. mode=MODE_BACKTEST,
83. token='token_id',
84. backtest_start_time='2017-09-29 11:25:00',
85. backtest_end_time='2017-09-29 11:30:00',
86. backtest_adjust=ADJUST_PREV,
87. backtest_initial_cash=500000,
88. backtest_commission_ratio=0.00006,
89. backtest_slippage_ratio=0.0001,
90. backtest_transaction_ratio=0.5)

4. 回测结果

设定初始资金50万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

回测期累计收益率为0.05%,年化收益率为16.87%,基准收益率为0,整体收益跑赢指数。最大回撤为0,胜率97.14%。

本文档使用 掘金量化 构建 - 74 -
做市商交易(期货)

需要注意的是,本演示策略只是用作示例,在现实中,以买一和卖一挂单不一定会成交,实际收益率也达不到示例水平,需谨
慎。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 75 -
海龟交易法(期货)

海龟交易法(期货)
海龟交易法
1. 原理
1.1 起源
1.2 建仓资金
1.3 入市信号
1.4 加仓和止损
1.5 止盈
2. 策略思路
3. 策略代码
4. 回测结果与稳健性分析

海龟交易法

1. 原理

1.1 起源

海龟交易思想起源于上世纪八十年代的美国。理查德丹尼斯与好友比尔打赌,主题是一个成功的交易员是天生的还是后天的。
理查德用十年时间证明了通过日常系统培训,交易员可以通过后天培训成为一名优秀的交易者。这套培训系统就是海龟交易系
统。

海龟交易系统是一个完整的、机械的交易思想,可以系统地完成整个交易过程。它包括了买卖什么、头寸规模、何时买卖、何
时退出等一系列交易策略,是一个趋势交易策略。它最显著的特点是捕捉中长期趋势,力求在短期内获得最大的收益。

1.2 建仓资金

海龟交易法将建仓资金按照一定比例划分为若干个小部分,每次建仓头寸和加仓规模都与波动量N(又称平均真实波动振幅
average true range ATR)有关。ATR是日内指数最大波动的平均振幅,由当日最高、最低价和上一交易日的收盘价决
定。

ATR

其中PDC是前一交易日的收盘价,ATR就是TR在N天内的均值。

价值波动量

利用N值来体现价值波动量DV:
DV = N * 合约每点价值
其中每点代表的价值量是指每一个指数点数所代表的价格。
每一次开仓交易合约数unit的确定是将总资产的1%除以DV得到。

本文档使用 掘金量化 构建 - 76 -
海龟交易法(期货)

1.3 入市信号

海龟交易法使用的是以一个理查德唐奇安的通道突破系统为基础的入市系统。唐奇安通道分为系统一和系统二,对应短期突破
和中长期突破。其中,短期突破系统是以20日(最高价或最低价)突破为基础,当价格突破20日价格即为入市信号;中长期系
统是当盘中价格突破过去55日价格为入市信号。

1.4 加仓和止损

海龟交易法的加仓规则是当捕捉到入市信号后建立第一个交易单位的头寸,市价继续向盈利方向突破1/2N时加仓。

止损位为2N,同加仓一样采用平均真实振幅N值为止损单位。每加仓一次,止损位就提高1/2N。

1.5 止盈

短期:多头头寸在突破过去10日最低价处止盈离市,空头头寸在突破过去10日最高价处止盈离市。
中长期:多头头寸在突破过去20日最低价处止盈离市,空头头寸在突破过去20日最高价处止盈离市。

2. 策略思路

第一步:获取历史数据,计算唐奇安通道和ATR
第二步:当突破唐奇安通道时,开仓。
第三步:计算加仓和止损信号。

回测标的:DCE.i2012
回测时间:2020-02-15 至 2020-09-01
回测初始资金:100万

Tips:
ATR值是不断变化的,这就会导致在对期货平仓时,可能出现平仓数量 > 持仓数量的现象。比如前一交易日的持仓为10,今日
的ATR值为22.假设当前价格触发平仓条件,平仓1/2ATR。1/2ATR=11 > 10, 这样就会导致委托失败报错。所以要加入一个
变量volume_hold用来记录当前持仓量,与1/2*ATR作比较。

注意:若修改回测期,需要修改对应的回测标的。

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np
4. import pandas as pd
5. from gm.api import *
6.
7.
8. '''
9. 以短期为例:20日线
10. 第一步:获取历史数据,计算唐奇安通道和ATR
11. 第二步:当突破唐奇安通道时,开仓。
12. 第三步:计算加仓和止损信号。
13. '''
14.
15.
16. def init(context):
17. # 设置计算唐奇安通道的参数

本文档使用 掘金量化 构建 - 77 -
海龟交易法(期货)

18. context.n = 20
19. # 设置合约标的
20. context.symbol = 'DCE.i2012'
21. # 设置交易最大资金比率
22. context.ratio = 0.8
23. # 订阅数据
24. subscribe(symbols=context.symbol, frequency='60s', count=2)
25. # 获取当前时间
26. time = context.now.strftime('%H:%M:%S')
27. # 如果策略执行时间点是交易时间段,则直接执行algo定义atr等参数,以防直接进入on_bar()导致atr等未
定义
28. if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
29. algo(context)
30. # 如果是交易时间段,等到开盘时间确保进入algo()
31. schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')
32. schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')
33.
34.
35. def algo(context):
36. # 计算通道的数据:当日最低、最高、上一交易日收盘
37. # 注:由于talib库计算ATR的结果与公式求得的结果不符,所以这里利用公式计算ATR
38. # 如果是回测模式,当天的数据直接用history取到
39. if context.mode == 2:
40. data = history_n(symbol=context.symbol, frequency='1d', count=context.n+1,
end_time=context.now, fields='close,high,low,bob', df=True) # 计算ATR
41. tr_list = []
42. for i in range(0, len(data)-1):
43. tr = max((data['high'].iloc[i] - data['low'].iloc[i]),
data['close'].shift(-1).iloc[i] - data['high'].iloc[i],
44. data['close'].shift(-1).iloc[i] - data['low'].iloc[i])
45. tr_list.append(tr)
46. context.atr = int(np.floor(np.mean(tr_list)))
47. context.atr_half = int(np.floor(0.5 * context.atr))
48.
49.
50. # 计算唐奇安通道
51. context.don_open = np.max(data['high'].values[-context.n:])
52. context.don_close = np.min(data['low'].values[-context.n:])
53.
54. # 如果是实时模式,当天的数据需要用current取到
55. if context.mode == 1:
56. data = history_n(symbol=context.symbol, frequency='1d', count=context.n,
end_time=context.now, fields='close,high,low',
57. df=True) # 计算ATR
58. current_data = current(symbols=context.symbol) # 最新一个交易日的最高、最低
59. tr_list = []
60. for i in range(1, len(data)):
61. tr = max((data['high'].iloc[i] - data['low'].iloc[i]),
62. data['close'].shift(-1).iloc[i] - data['high'].iloc[i],
63. data['close'].shift(-1).iloc[i] - data['low'].iloc[i])
64. tr_list.append(tr)
65.
66. # 把最新一期tr加入列表中
67. tr_new = max((current_data[0]['high'] - current_data[0]['low']),

本文档使用 掘金量化 构建 - 78 -
海龟交易法(期货)

68. data['close'].iloc[-1] - current_data[0]['high'],


69. data['close'].iloc[-1] - current_data[0]['low'])
70. tr_list.append(tr_new)
71. context.atr = int(np.floor(np.mean(tr_list)))
72. context.atr_half = int(np.floor(0.5 * context.atr))
73.
74. # 计算唐奇安通道
75. context.don_open = np.max(data['high'].values[-context.n:])
76. context.don_close = np.min(data['low'].values[-context.n:])
77.
78. # 计算加仓点和止损点
79. context.long_add_point = context.don_open + context.atr_half
80. context.long_stop_loss = context.don_open - context.atr_half
81. context.short_add_point = context.don_close - context.atr_half
82. context.short_stop_loss = context.don_close + context.atr_half
83.
84.
85. def on_bar(context, bars):
86. # 提取数据
87. symbol = bars[0]['symbol']
88. recent_data = context.data(symbol=context.symbol, frequency='60s', count=2,
fields='close,high,low')
89. close = recent_data['close'].values[-1]
90.
91. # 账户仓位情况
92. position_long = context.account().position(symbol=symbol, side=PositionSide_Long)
93. position_short = context.account().position(symbol=symbol, side=PositionSide_Short)
94.
95. # 当无持仓时
96. if not position_long and not position_short:
97. # 如果向上突破唐奇安通道,则开多
98. if close > context.don_open:
99. order_volume(symbol=symbol, side=OrderSide_Buy, volume=context.atr,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
100. print('开多仓atr')
101.
102. # 如果向下突破唐奇安通道,则开空
103. if close < context.don_close:
104. order_volume(symbol=symbol, side=OrderSide_Sell, volume=context.atr,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
105. print('开空仓atr')
106.
107. # 有持仓时
108. # 持多仓,继续突破(加仓)
109. if position_long:
110. # 当突破1/2atr时加仓
111. if close > context.long_add_point:
112. order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy,
order_type=OrderType_Market,position_effect=PositionEffect_Open)
113. print('继续加仓0.5atr')
114. context.long_add_point += context.atr_half
115. context.long_stop_loss += context.atr_half
116. # 持多仓,止损位计算
117. if close < context.long_stop_loss:
118. volume_hold = position_long['volume']

本文档使用 掘金量化 构建 - 79 -
海龟交易法(期货)

119. if volume_hold >= context.atr_half:


120. order_volume(symbol=symbol, volume=context.atr_half,
side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close)
121. else:
122. order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Sell,
order_type=OrderType_Market,position_effect=PositionEffect_Close)
123. print('平多仓0.5atr')
124. context.long_add_point -= context.atr_half
125. context.long_stop_loss -= context.atr_half
126.
127. # 持空仓,继续突破(加仓)
128. if position_short:
129. # 当跌破加仓点时加仓
130. if close < context.short_add_point:
131. order_volume(symbol = symbol, volume=context.atr_half, side=OrderSide_Sell,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
132. print('继续加仓0.5atr')
133. context.short_add_point -= context.atr_half
134. context.short_stop_loss -= context.atr_half
135.
136. # 持多仓,止损位计算
137. if close > context.short_stop_loss:
138. volume_hold = position_short['volume']
139. if volume_hold >= context.atr_half:
140. order_volume(symbol=symbol, volume=context.atr_half,
side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close)
141. else:
142. order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Buy,
order_type=OrderType_Market,position_effect=PositionEffect_Close)
143. print('平空仓0.5atr')
144. context.short_add_point += context.atr_half
145. context.short_stop_loss += context.atr_half
146.
147.
148. if __name__ == '__main__':
149. '''
150. strategy_id策略ID,由系统生成
151. filename文件名,请与本文件名保持一致
152. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
153. token绑定计算机的ID,可在系统设置-密钥管理中生成
154. backtest_start_time回测开始时间
155. backtest_end_time回测结束时间
156. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
157. backtest_initial_cash回测初始资金
158. backtest_commission_ratio回测佣金比例
159. backtest_slippage_ratio回测滑点比例
160. '''
161. run(strategy_id='strategy_id',
162. filename='main.py',
163. mode=MODE_BACKTEST,
164. token='token',
165. backtest_start_time='2020-02-15 09:15:00',
166. backtest_end_time='2020-09-01 15:00:00',
167. backtest_adjust=ADJUST_PREV,

本文档使用 掘金量化 构建 - 80 -
海龟交易法(期货)

168. backtest_initial_cash=1000000,
169. backtest_commission_ratio=0.0001,
170. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析

设定初始资金100万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

回测期累计收益率为18.75%,年化收益率为6.42%,沪深300收益率为18.75%,策略跑输沪深300指数。最大回撤为4.20%,
胜率为48.15%。

为了检验策略的稳健性,改变策略标的和计算唐奇安通道的参数n,回测结果如下。

标的 唐奇安通道参数 年化收益率 最大回撤

DCE.i2012 20 6.42% 4.20%

DCE.i2012 25 3.45% 4.99%

DCE.i2012 30 -0.35% 4.23%

DCE.m2012 20 -0.28% 1.50%

DCE.m2012 25 1.19% 0.52%

DCE.m2012 30 1.23% 0.47%

SHFE.rb2012 20 -4.48% 2.61%

SHFE.rb2012 25 -2.80% 2.84%

SHFE.rb2012 30 -3.01% 2.39%

由上表可知,不同标的收益结果呈现差异。其中大商所的铁矿石收益情况最好,其他两个品种收益较差,整体收益情况较差。
说明该策略在使用上存在一定风险。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 81 -
行业轮动(股票)

行业轮动(股票)
行业轮动策略
1. 原理
行业轮动现象
行业轮动的原因
行业轮动下资产配置
2. 策略思路
3. 策略代码
4. 回测结果分析与稳健性检验

行业轮动策略

1. 原理

行业轮动现象

在某一段时间内,某一行业或某几个行业组内股票价格共同上涨或下降的现象。

行业轮动策略是根据行业轮动现象做成的策略,利用行业趋势进行获利的方法,属于主动交易策略。其本质是通过一段时期的
市场表现,力求抓住表现较好的行业以及投资品种,选择不同时期的强势行业进行获利。

行业轮动的原因

原因1:行业周期

行业的成长周期可以分为初创期、成长期、成熟期和衰退期,一般行业会按照这个周期运行。初创期属于行业刚刚起步阶段,
风险高、收益小。成长期内风险高、收益高。处于成熟期的企业风险低、收益高。处于衰退期的企业风险低、收益低。在一段
时间内,不同的行业会处于不同的行业周期,在时间维度上看会呈现行业轮动现象。

原因2:国家政策

国家政策对我国资本市场有重大影响。我国每年的财政政策和货币政策都是市场关注的热点,货币政策和财政政策会释放出影
响市场的信息,如利率。当政策释放出下调利率的信号,就为资金需求量大、项目周期长的行业缓解了压力,如房地产行业,
这时对于这类行业利好,相应的股价就会上涨。

原因3:重大事件

资本市场对于消息的反应是迅速的。根据有效市场理论,在半强式有效市场下,一切已公开的信息都会反映在股价当中。以疫
情为例,消息一出迅速拉动医疗行业股价水平,带动行业增长。

行业轮动下资产配置

1. 美林时钟:大类资产配置

根据经济增长和通货膨胀可以将经济分为四个周期:衰退、复苏、过热、滞涨。

美林时钟分析了四个不同时期,并总结出适合投资的资产类别。

周期阶段 经济增长 通货膨胀 最优资产类别 最优股票板块

衰退 下降 下降 债券 防御成长

本文档使用 掘金量化 构建 - 82 -
行业轮动(股票)

复苏 上升 下降 股票 周期成长

过热 上升 上升 商品 周期价值

滞涨 下降 上升 现金 防御价值

研究宏观经济时主要关注两个变量:GDP和CPI。

其中GDP选择不变价(剔除通货膨胀的影响),关心同比值差分后的符号。如果同比值>0,差分后仍然>0,说明GDP在加速上
涨;如果同比值<0,差分后仍然<0,说明GDP在加速下跌。

当经济增长速度加快时,与国家经济联系紧密的行业如钢铁、煤炭、电力等基建利润也会随之增长。

当经济增速放缓是,非周期性的行业如医药、基础消费品、基础建设等行业呈现较强的防御性。

当通货膨胀处于较低水平时,市场利率水平也处于较低水平。按照股票估值理论,此时的折现率处于低水平,价格相对而言较
高。此时,金融行业的股价会呈现明显的上涨。

当通货膨胀处于较高水平时,市场利率较高,此时现金为王,原材料价格走高。与此相关的原材料行业就会表现较好,如天然
气、石油等。

2. 策略设计

行业动量策略

部分研究表明,行业在日、月频率上会存在动量现象,在周频率上会存在反转现象,也就是行业间轮动。因此,在日和月频率
上可以利用行业动量设计策略,如果是在周频率上可以利用反转效应设计策略。
(引自:武文超. 中国A股市场的行业轮动现象分析——基于动量和反转交易策略的检验[J]. 金融理论与实践, 2014,
000(009):111-114.)

行业因子策略

将行业变量作为一个因子放入多因子模型中,利用多因子模型预测各个行业的周期收益率,采用滚动预测方法每次得到一个样
本外预测值,根据这些预测值判断该买入哪些行业,卖出哪些行业。
(引自:高波, 任若恩. 基于主成分回归模型的行业轮动策略及其业绩评价[J]. 数学的实践与认识, 2016, 46(019):82-
92.)

2. 策略思路
策略示例采用第一种策略构建方法,利用行业动量设计策略。为了提高策略速度,以6个行业为例进行演示。

第一步:确定行业指数,获取行业指数收益率。
第二步:根据行业动量获取最佳行业指数。
第三步:在最佳行业中,选择最大市值的5支股票买入。

回测时间:2017-07-01 08:00:00 到 2017-10-01 16:00:00


回测标的:SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914
(300工业.300材料.300可选.300消费.300医药.300金融)
回测初始资金:1000万

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. import numpy as np

本文档使用 掘金量化 构建 - 83 -
行业轮动(股票)

4. from gm.api import *


5. '''
6. 本策略每隔1个月定时触发计算
SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914
7. (300工业.300材料.300可选.300消费.300医药.300金融)这几个行业指数过去
8. 20个交易日的收益率并选取了收益率最高的指数的成份股获取并获取了他们的市值数据
9. 随后把仓位调整至市值最大的5只股票上
10. 回测数据为:SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914和他们的
成份股
11. 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00
12. '''
13. def init(context):
14. # 每月第一个交易日的09:40 定时执行algo任务(仿真和实盘时不支持该频率)
15. schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
16. # 用于筛选的行业指数
17. context.index = ['SHSE.000910', 'SHSE.000909', 'SHSE.000911', 'SHSE.000912',
'SHSE.000913', 'SHSE.000914']
18. # 用于统计数据的天数
19. context.date = 20
20. # 最大下单资金比例
21. context.ratio = 0.8
22.
23. def algo(context):
24. # 获取当天的日期
25. today = context.now
26. # 获取上一个交易日
27. last_day = get_previous_trading_date(exchange='SHSE', date=today)
28. return_index = []
29.
30. # 获取并计算行业指数收益率
31. for i in context.index:
32. return_index_his = history_n(symbol=i, frequency='1d', count=context.date,
fields='close,bob',
33. fill_missing='Last', adjust=ADJUST_PREV,
end_time=last_day, df=True)
34. return_index_his = return_index_his['close'].values
35. return_index.append(return_index_his[-1] / return_index_his[0] - 1)
36.
37. # 获取指定数内收益率表现最好的行业
38. sector = context.index[np.argmax(return_index)]
39. print('最佳行业指数是: ', sector)
40.
41. # 获取最佳行业指数成份股
42. symbols = get_history_constituents(index=sector, start_date=last_day,
end_date=last_day)[0]['constituents'].keys()
43.
44. # 获取当天有交易的股票
45. not_suspended_info = get_history_instruments(symbols=symbols, start_date=today,
end_date=today)
46. not_suspended_symbols = [item['symbol'] for item in not_suspended_info if not
item['is_suspended']]
47.
48. # 获取最佳行业指数成份股的市值,从大到小排序并选取市值最大的5只股票
49. fin = get_fundamentals(table='trading_derivative_indicator',

本文档使用 掘金量化 构建 - 84 -
行业轮动(股票)

symbols=not_suspended_symbols, start_date=last_day,
50. end_date=last_day, limit=5, fields='NEGOTIABLEMV',
order_by='-NEGOTIABLEMV', df=True)
51. fin.index = fin['symbol']
52.
53. # 计算权重
54. percent = 1.0 / len(fin.index) * context.ratio
55.
56. # 获取当前所有仓位
57. positions = context.account().positions()
58.
59. # 如标的池有仓位,平不在标的池的仓位
60. for position in positions:
61. symbol = position['symbol']
62. if symbol not in fin.index:
63. order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
64. position_side=PositionSide_Long)
65. print('市价单平不在标的池的', symbol)
66.
67. # 对标的池进行操作
68. for symbol in fin.index:
69. order_target_percent(symbol=symbol, percent=percent,
order_type=OrderType_Market,
70. position_side=PositionSide_Long)
71. print(symbol, '以市价单调整至仓位', percent)
72.
73. if __name__ == '__main__':
74. '''
75. strategy_id策略ID,由系统生成
76. filename文件名,请与本文件名保持一致
77. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
78. token绑定计算机的ID,可在系统设置-密钥管理中生成
79. backtest_start_time回测开始时间
80. backtest_end_time回测结束时间
81. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
82. backtest_initial_cash回测初始资金
83. backtest_commission_ratio回测佣金比例
84. backtest_slippage_ratio回测滑点比例
85. '''
86. run(strategy_id='strategy_id',
87. filename='main.py',
88. mode=MODE_BACKTEST,
89. token='token_id',
90. backtest_start_time='2017-07-01 08:00:00',
91. backtest_end_time='2017-10-01 16:00:00',
92. backtest_adjust=ADJUST_PREV,
93. backtest_initial_cash=10000000,
94. backtest_commission_ratio=0.0001,
95. backtest_slippage_ratio=0.0001)

4. 回测结果分析与稳健性检验

设定初始资金1000万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 85 -
行业轮动(股票)

回测期累计收益率为10.10%,年化收益率为41.43%,沪深300收益率为5.09%,策略收益跑赢指数。最大回撤为6.93%,胜
率50%。

为了探究策略的稳健性,改变回测时间,观察策略收益情况。

回测期 时间长度 年化收益率 最大回撤

2017.7.1-2017.10.1 4个月 41.43% 6.93%

2018.7.1-2018.10.1 4个月 -23.40% 15.54%

2019.7.1-2019.10.1 4个月 9.98% 9.58%

2019.1.1-2019.12.31 12个月 63.82% 9.98%

2018.1.1-2019.12.31 24个月 7.63% 36.89%

2017.1.1-2019.12.31 36个月 22.11% 36.94%

通过观察回测结果发现,该策略在选择的回测期内除了2018年7月1日至2018年10月1日这段期间是负收益以外,在其他回测期
内都是正收益。其中2017年7月至2017年10月和2019年1月至2019年12月这两段期间的年化收益率最高,其他时间段收益率
相对较低,说明该策略在不同回测期之间收益差距较大。随着时间跨度拉长,最大回撤越来越大,最大可达到36.94%。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 86 -
机器学习(股票)

机器学习(股票)
机器学习
1. 原理
什么是机器学习?
什么是支持向量机?
利用支持向量机预测股票涨跌
参数优化
2. 策略思路
3. 策略代码
4. 回测结果与稳健性分析

机器学习

1. 原理

什么是机器学习?

随着计算机技术的发展,投资者不再只局限于传统投资策略,机器学习在资本市场得到广泛应用。机器学习的核心是通过机器
模仿人类的思考过程以及思维习惯,通过对现有数据的学习,对问题进行预测和决策。目前,机器学习已在人脸识别、智能投
顾、自然语言处理等方面得到广泛应用。

机器学习可以分为两类,一类是无监督学习,另一类是监督学习。监督学习是指按照已有的标记进行学习,即已经有准确的分
类信息。比如二分类问题,一类是“好”,另一类是“不好”,这种明确地指出分类基准的问题。这类模型包括:神经网络、决策
树、支持向量机等。

无监督学习是指针对未标记过的数据集进行学习。比如聚类问题,没有准确的标准说明应该聚成几类,只有相对概念。这类模
型包括:K_means聚类、层次聚类法等。

什么是支持向量机?

支持向量机是最典型的一类机器学习模型,常用于解决二分类问题。支持向量机的原理是在一个样本空间内,找到一个平面,
将样本数据分为两个部分,即两个分类,这个平面就叫做超平面。

怎样确定超平面?

假设有一个线性可分的二分类问题如图所示。

本文档使用 掘金量化 构建 - 87 -
机器学习(股票)

已知A、B、C三条线均可以将样本空间分为两类,那么问题来了,应该选择哪一个?

SVM模型指出,如果超平面能够将训练样本没有错误地分开,并且两类训练样本中离超平面最近的样本与超平面之间的距离是最
大的,则把这个超平面称作最优超平面,即上图中的B平面。两类样本中距离最优超平面的点成为支持向量,支持向量机模型的
名字由此得出。

支持向量机背后的数学原理十分优美,但由于推导过程过于复杂,这里不再赘述。总之,支持向量机的核心就是寻找最优超平
面。

支持向量机不仅可以解决线性可分问题,也可以解决非线性可分问题。其核心思想是将原始样本点映射到高维空间上,将非线
性转化为线性可分,在高维空间中找到满足条件的最优超平面,再映射到低维空间中。

利用支持向量机预测股票涨跌

在利用支持向量机进行预测之前,先将数据集分为训练集和测试集。常用的分类方法是将数据及进行8:2分解,0.8部分是训练
集,0.2部分是测试集。用训练集训练模型,再用测试集评价模型的准确率等指标。

在利用支持向量机预测时,还有很重要的一步是进行参数优化。SVM的参数包括以下几个。

参数符
参数说明

罚函数,错误项的惩罚系数,默认为1。C越大,对错误样本的惩罚力度越大,准确度越高但泛化能力越低
C
(泛化能力是指拓展到测试集中的准确率)。C越小,允许样本增加一点错误,使泛化能力提高。

核函数,包括linear(线型核函数)、poly(多项式核函数)、rbf(高斯核函数)、sigmod(sigmod核函
Kernel
数)。

degree 当核函数选成多项式核函数时对应的阶数。

Gamma 核函数系数。

还有一些其他的参数,因为本示例不对其进行优化,所以这里不再赘述了。

参数优化

本示例采用网格搜索算法优化参数,训练好的参数为C = 0.6, gamma = 0.001,训练后的准确率为 0.50。(这个准确率虽


然看起来很低,但在现实生活中准确率都处于较低水平,这里暂时用这个优化后的参数进行建模。)

2. 策略思路

本文档使用 掘金量化 构建 - 88 -
机器学习(股票)

第一步:获取原始数据,这里获取2016-04-01到2017-07-30的数据。
第二步:计算SVM模型的输入变量。

x 表示输入的特征值,共7个,分别为:

参数符号 计算方法

x1 最新收盘价/15日收盘价均值

x2 现量/15日均量

x3 最新最高价/15日均价

x4 最新最低价/15日均价

x5 现量

x6 15日区间收益率

x7 15日区间标准差

y 表示5个交易日后收盘价是否上涨,

参数符号 含义

y = 1 表示股价上涨

y = 0 表示股价下跌

第三步:利用训练好的模型预测股价未来走向。若上涨(y=1)则开仓。

第四步:设置止损止盈点。
若已经持有仓位则在盈利大于10%的时候止盈,在星期五损失大于2%的时候止损。

回测时间:2017-07-01 09:00:00 到 2017-10-01 09:00:00


回测初始资金:1000万
回测标的:SHSE.600000

3. 策略代码

1. # coding=utf-8
2. from __future__ import print_function, absolute_import, unicode_literals
3. from datetime import datetime
4. import numpy as np
5. from gm.api import *
6. import sys
7. try:
8. from sklearn import svm
9. except:
10. print('请安装scikit-learn库和带mkl的numpy')
11. sys.exit(-1)
12. '''
13. 本策略选取了七个特征变量组成了滑动窗口长度为15天的训练集,随后训练了一个二分类(上涨/下跌)的支持向量机模
型.
14. 若没有仓位则在每个星期一的时候输入标的股票近15个交易日的特征变量进行预测,并在预测结果为上涨的时候购买标
的.
15. 若已经持有仓位则在盈利大于10%的时候止盈,在星期五损失大于2%的时候止损.
16. 特征变量为:1.收盘价/均值2.现量/均量3.最高价/均价4.最低价/均价5.现量6.区间收益率7.区间标准差

本文档使用 掘金量化 构建 - 89 -
机器学习(股票)

17. 训练数据为:SHSE.600000浦发银行,时间从2016-04-01到2017-07-30
18. 回测时间为:2017-07-01 09:00:00到2017-10-01 09:00:00
19. '''
20.
21. def init(context):
22. # 订阅浦发银行的分钟bar行情
23. context.symbol = 'SHSE.600000'
24. subscribe(symbols=context.symbol, frequency='60s')
25. start_date = '2016-04-01' # SVM训练起始时间
26. end_date = '2017-07-30' # SVM训练终止时间
27.
28. # 用于记录工作日
29. # 获取目标股票的daily历史行情
30. recent_data = history(context.symbol, frequency='1d', start_time=start_date,
end_time=end_date, fill_missing='last',
31. df=True)
32. days_value = recent_data['bob'].values
33. days_close = recent_data['close'].values
34. days = []
35.
36. # 获取行情日期列表
37. print('准备数据训练SVM')
38. for i in range(len(days_value)):
39. days.append(str(days_value[i])[0:10])
40. x_all = []
41. y_all = []
42. for index in range(15, (len(days) - 5)):
43. # 计算三星期共15个交易日相关数据
44. start_day = days[index - 15]
45. end_day = days[index]
46. data = history(context.symbol, frequency='1d', start_time=start_day,
end_time=end_day, fill_missing='last',
47. df=True)
48. close = data['close'].values
49. max_x = data['high'].values
50. min_n = data['low'].values
51. amount = data['amount'].values
52. volume = []
53. for i in range(len(close)):
54. volume_temp = amount[i] / close[i]
55. volume.append(volume_temp)
56. close_mean = close[-1] / np.mean(close) # 收盘价/均值
57. volume_mean = volume[-1] / np.mean(volume) # 现量/均量
58. max_mean = max_x[-1] / np.mean(max_x) # 最高价/均价
59. min_mean = min_n[-1] / np.mean(min_n) # 最低价/均价
60. vol = volume[-1] # 现量
61. return_now = close[-1] / close[0] # 区间收益率
62. std = np.std(np.array(close), axis=0) # 区间标准差
63.
64. # 将计算出的指标添加到训练集X
65. # features用于存放因子
66. features = [close_mean, volume_mean, max_mean, min_mean, vol, return_now, std]
67. x_all.append(features)
68.

本文档使用 掘金量化 构建 - 90 -
机器学习(股票)

69. # 准备算法需要用到的数据
70. for i in range(len(days_close) - 20):
71. if days_close[i + 20] > days_close[i + 15]:
72. label = 1
73. else:
74. label = 0
75. y_all.append(label)
76. x_train = x_all[: -1]
77. y_train = y_all[: -1]
78.
79. # 训练SVM
80. context.clf = svm.SVC(C=0.6, kernel='rbf', gamma=0.001)
81. context.clf.fit(x_train, y_train)
82. print('训练完成!')
83.
84. def on_bar(context, bars):
85. bar = bars[0]
86.
87. # 获取当前年月日
88. today = bar.bob.strftime('%Y-%m-%d')
89.
90. # 获取数据并计算相应的因子
91. # 于星期一的09:31:00进行操作
92. # 当前bar的工作日
93. weekday = datetime.strptime(today, '%Y-%m-%d').isoweekday()
94.
95. # 获取模型相关的数据
96. # 获取持仓
97. position = context.account().position(symbol=context.symbol,
side=PositionSide_Long)
98.
99. # 如果bar是新的星期一且没有仓位则开始预测
100. if not position and weekday == 1:
101. # 获取预测用的历史数据
102. data = history_n(symbol=context.symbol, frequency='1d', end_time=today,
count=15,
103. fill_missing='last', df=True)
104. close = data['close'].values
105. train_max_x = data['high'].values
106. train_min_n = data['low'].values
107. train_amount = data['amount'].values
108. volume = []
109. for i in range(len(close)):
110. volume_temp = train_amount[i] / close[i]
111. volume.append(volume_temp)
112. close_mean = close[-1] / np.mean(close)
113. volume_mean = volume[-1] / np.mean(volume)
114. max_mean = train_max_x[-1] / np.mean(train_max_x)
115. min_mean = train_min_n[-1] / np.mean(train_min_n)
116. vol = volume[-1]
117. return_now = close[-1] / close[0]
118. std = np.std(np.array(close), axis=0)
119.
120. # 得到本次输入模型的因子

本文档使用 掘金量化 构建 - 91 -
机器学习(股票)

121. features = [close_mean, volume_mean, max_mean, min_mean, vol, return_now, std]


122. features = np.array(features).reshape(1, -1)
123. prediction = context.clf.predict(features)[0]
124.
125. # 若预测值为上涨则开仓
126. if prediction == 1:
127. # 获取昨收盘价
128. context.price = close[-1]
129. # 把浦发银行的仓位调至95%
130. order_target_percent(symbol=context.symbol, percent=0.95,
order_type=OrderType_Market,
131. position_side=PositionSide_Long)
132. print('SHSE.600000以市价单开多仓到仓位0.95')
133.
134. # 当涨幅大于10%,平掉所有仓位止盈
135. elif position and bar.close / context.price >= 1.10:
136. order_close_all()
137. print('SHSE.600000以市价单全平多仓止盈')
138.
139. # 当时间为周五并且跌幅大于2%时,平掉所有仓位止损
140. elif position and bar.close / context.price < 1.02 and weekday == 5:
141. order_close_all()
142. print('SHSE.600000以市价单全平多仓止损')
143.
144. if __name__ == '__main__':
145. '''
146. strategy_id策略ID,由系统生成
147. filename文件名,请与本文件名保持一致
148. mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
149. token绑定计算机的ID,可在系统设置-密钥管理中生成
150. backtest_start_time回测开始时间
151. backtest_end_time回测结束时间
152. backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
153. backtest_initial_cash回测初始资金
154. backtest_commission_ratio回测佣金比例
155. backtest_slippage_ratio回测滑点比例
156. '''
157. run(strategy_id='strategy_id',
158. filename='main.py',
159. mode=MODE_BACKTEST,
160. token='token_id',
161. backtest_start_time='2017-07-01 09:00:00',
162. backtest_end_time='2017-10-01 09:00:00',
163. backtest_adjust=ADJUST_PREV,
164. backtest_initial_cash=10000000,
165. backtest_commission_ratio=0.0001,
166. backtest_slippage_ratio=0.0001)

4. 回测结果与稳健性分析
设定初始资金1000万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。

本文档使用 掘金量化 构建 - 92 -
机器学习(股票)

回测期累计收益率为9.30%,年化收益率为38.13%,沪深300指数收益率为5.09%,策略收益率跑输指数。策略最大回撤为
0.56%,胜率50.0%。

为了检验策略的稳健性,改变回测时间,得到结果如下。

回测期 时长 年化收益率 最大回撤

2017.07.01-2017.10.01 3个月 38.13% 0.56%

2017.07.01-2017.12.31 6个月 18.85% 0.56%

2017.07.01-2018.07.01 12个月 9.38% 0.56%

2017.07.01-2019.07.01 24个月 2.84% 3.07%

2017.07.01-2020.07.01 36个月 1.89% 3.07%

由上表可知,策略整体收益均小于0,远远跑输基准水平。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

本文档使用 掘金量化 构建 - 93 -

You might also like