复现光大证券技术择时系列报告之三放量恰是入市时

光大证券技术择时系列报告之一是RSRS,之二是RSRS推广到行业轮动,之三是放量恰是入市时:成交量择时初探。
这篇研报主要讲的是用交易量来构造一个择时策略。
里面提到,“价涨量先行”,成交量较大时指数大概率上涨。

构造成交量的时间序列排名指标方式如下:

  1. 数据上除了当日的成交量以外,我们还需取前 N 个交易日的每日成 交量数据,共 N+1 个成交量的值。
  2. 将该 N+1 个成交量数据按从小到大进行排序,计算当日成交量在这 N+1 个数值中的排名 n,最小即为 1,最大即为 N+1。
  3. 通过运算 2 * (n-N-1) / N 将当日成交量排名标准化为[-1, 1]内的数 值。

上面是研报提到的构造方式,但是标准化的公式有问题,2 (n-N-1) / N 映射到的区间是[-2,0],应该改为(n-平均数)/平均数,即 2( n-(N-1)/2) /N才对。
择时方法是用上面的构造方式算出来[-1,1]的数去和阈值做判断,其含义其实就是当成交量时序排名处于最大的一段范围内就买入,否则卖出。

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bars = get_bars(g.security,g.N + 1,unit='1d',
fields=['date', 'open','high','low','close','volume'],
include_now=False, end_dt=None, fq_ref_date=None)
print bars
last_day_volume = bars[-1]['volume']
lt_last_day_volume_count = 0
for i in range(0,len(bars)-1):
if bars[i]['volume'] < last_day_volume:
lt_last_day_volume_count += 1
last_day_volume_rank = lt_last_day_volume_count + 1
#2*( n-(N-1)/2) /N
std_score = 2.0*(last_day_volume_rank -1 - g.N/2.0)/g.N

if std_score > g.S:
g.tc = 7
order_target_value(g.security, context.portfolio.total_value)
elif std_score < g.S:
order_target_value(g.security, 0)

然后里面提到参数(N=35,S=0.8)的择时效果更佳,我们将其 作为沪深 300 成交量时序排名择时策略的参数。中证 500 的参数由样本内表现确定为(N=30, S=0.5)。

我根据研报的策略写的代码应该没什么错,尝试复现了一下,达不到研报中说的收益。不知道是研报注水了,还是我忽略了什么东西,或者是里面隐藏了什么条件。
我的测试结果:
沪深300,N=35 S=0.8
2007-03-05 到 2014-01-01
策略收益 30.87% 策略年化收益 4.13% 基准收益 -7.12% Alpha 0.007 Beta 0.113 Sharpe 0.012 胜率 0.562 盈亏比 1.608 最大回撤 13.43%

中证500, N=30 S=0.5
2007-03-05 到 2014-01-01
策略收益 142.98% 策略年化收益 14.29% 基准收益 52.94% Alpha 0.097 Beta 0.237 Sharpe 0.614 胜率 0.504 盈亏比 1.677 最大回撤 18.19%

本文作者董春秋,首发于公众号春秋金经,http://www.chunqiujinjing.com/

在中证500上面还好一些,年化14% 最大回撤18%,但也没达到研报上写的中证500年化25.5% 最大回撤17%
沪深300上面就更差了,年化才4% 回撤13%,研报上面写的年化15.2% 回撤11%
我这算复现失败了吗?大佬们帮忙看看是哪里不对,还是说研报真的注水了。但按理说不会啊,这篇研报作者是RSRS的作者。

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 导入函数库
from jqdata import *

# 初始化函数,设定基准等等
def initialize(context):
# g.security = '000300.XSHG'
# g.N = 35
# g.S = 0.8

g.security = '000905.XSHG'
g.N = 30
g.S = 0.5

# 设定沪深300作为基准
set_benchmark(g.security)
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)

### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')

## 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入'000300.XSHG'或'510300.XSHG'是一样的)
# 开盘前运行
run_daily(before_market_open, time='before_open', reference_security='000300.XSHG')
# 开盘时运行
run_daily(market_open, time='open', reference_security='000300.XSHG')
# 收盘后运行
run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')

## 开盘前运行函数
def before_market_open(context):
pass



## 开盘时运行函数
def market_open(context):
log.info("market_open")
#获取历史数据
bars = get_bars(g.security,g.N + 1,unit='1d',
fields=['date', 'open','high','low','close','volume'],
include_now=False, end_dt=None, fq_ref_date=None)
print bars
last_day_volume = bars[-1]['volume']
lt_last_day_volume_count = 0
for i in range(0,len(bars)-1):
if bars[i]['volume'] < last_day_volume:
lt_last_day_volume_count += 1
last_day_volume_rank = lt_last_day_volume_count + 1
#2*( n-(N-1)/2) /N
std_score = 2.0*(last_day_volume_rank -1 - g.N/2.0)/g.N

if std_score > g.S:
g.tc = 7
order_target_value(g.security, context.portfolio.total_value)
elif std_score < g.S:
order_target_value(g.security, 0)

## 收盘后运行函数
def after_market_close(context):
log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
#得到当天所有成交记录
trades = get_trades()
for _trade in trades.values():
log.info('成交记录:'+str(_trade))
log.info('一天结束')
log.info('##############################################################')