何谓截面动量
在了解和测试了时间序列动量之后,我们自然想到按照经典策略分类的截面动量。时间序列动量很好理解,就是指单个商品自身是否发生上涨、下跌,以及其幅度是否构成我们应该买入多头,卖出空头这样的操作。时间序列动量的假设是:某品种的价格产生方向会延续一定幅度,并且方向反转也是个阶段性过程,我们可以评估动量反转而做出平仓或反手交易。
截面动量就不同了,它也被称作是横截面动量,在每个日期截面上,选择上涨最多的N个品种做多,下跌最多的N个品种做空。它的假设是:强者恒强,弱者恒弱。动量是相对出现于品种之间的,而不是以一个绝对数值,体现出方向性。截面动量模型的持仓是对冲形态的,每一期账户内总有N个品种多头,N个品种空头。
我们查阅资料有显示:Jegadeesh和Titman (1993)等人在对美国股市的研究开始了最早的金融市场中动量效应的研究,在排序期内考察各个股票的动量表现(即历史业绩),而在持有期开始时买入近期的胜者(业绩较好),卖出近一期内的输者(业绩较差),并持有H的长度,直至下一个持有周期。之后Miffre和Rallis (2007),Shen (2007) 以及Szakmary (2010)对国外商品期货市场中的动量效应也做了测试,发现期货市场中也确实存在着“胜者更强,输者更弱”的现象。
截面动量的几个关键问题
1、截面分组大小
首先需要讨论的便是截面上不同品种的排名是否真的能够区分开来,也就是说截面效应是否真的有效。为了查看分组对收益表现的影响,有机构研报里,通过截面动量从大到小的排名进行分组,标记排名前20%为做多组,后20%为做空组。
但是在期货品种较少的年份,横截面上品种数量不足20个的情况下,每次做多做空的品种可能只有3~4个左右,无法形成一个稳定的组合。所以我们在后期测试中,也尝试了,前20%,前30%,前40%,这样三组参数。
2、动量衡量和持有参数
华泰期货的测试显示:横截面动量策略涉及的参数主要有回溯期和持有期。回溯期指计算动量因子的时间长度,用 R 表示,代表建仓日前一天开始向前推 R 天,计算这 R 天的平均收益率;持有期指建仓后持有组合的时间长度,用 H 表示,代表每隔 H 天建仓,持有组合至下个建仓日。回溯期和持有期的取值范围均是 5 天、10 天、22 天、60 天、120 天和 250 天,用(R,H)表示,共 36 个组合如下表所示。
所以按照测试思路,华泰将动量的测试周期放在250日,在此周期下,测试了多个持有期的绩效图如下:
很可惜,我们不能认同250这个动量持有期。原因非常显著:动量的计算仅考虑了开始和结束两个时间端点,将近一年(250日)的考核期太过长久,这个策略变为一个典型的长线动量模型。试想,通过一年的考察期来评估各期货品种动量,这个动量是难以发生变化和排序变化的,在调仓时间点可能不会产生调仓操作。所以实际测试中,我们更倾向于采用较为短期的动量考核期,构成一个相对容易解释的策略。
截面动量参数问题上,东方证券衍生品研究院的观点是,截面动量因子的表现具有很强的参数敏感性。这是因为由不同的计算周期所得到动量特征表现结果不一,不同的品种的动量特征也会表现不一,所以在构建截面动量因子组合之后,组合的整体表现就会很强地受参数选择的影响。
另外一个原因则和前文中提到的换手率的结论是一样的,即持有期的长度取值较低时,交易次数会很多,由交易成本带来的负面冲击也较大。故综合考虑之后,我们认为截面动量因子的有效性只存在部分参数区域内,较为合适的K值范围为10天左右,而H的合理取值范围为25天以上。
总之今天将这个框架公布出来,请大家自行确定动量的考察期和持有期参数。
代码介绍
具体到写码环节,和之前讲的价格模型类似,首先是初始化,我们给与的手续费还是挺高的,但是因为这是中低频模型,对于手续费不敏感。
def initialize(context):
# 设置参数
set_parameter(context)
# 设定基准银华日利,在多品种的回测当中基准没有参考意义
set_benchmark('511880.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
### 期货相关设定 ###
# 设定账户为金融账户
set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='futures')])
# 期货类每笔交易时的手续费是:买入时万分之1,卖出时万分之1,平今仓为万分之1
set_order_cost(OrderCost(open_commission=0.0001, close_commission=0.0001,close_today_commission=0.0001), type='index_futures')
# 设定保证金比例
set_option('futures_margin_rate', 0.15)
# 设置滑点(单边万5,双边千1)
set_slippage(PriceRelatedSlippage(0.001),type='future')
# 开盘前运行
run_daily( before_market_open, time='before_open', reference_security=get_future_code('RB'))
# 开盘时运行
run_weekly(market_open, 1,time='open', reference_security=get_future_code('RB'))
# 交易运行
run_weekly(Trade, 1, time='open', reference_security=get_future_code('RB'))
# 收盘后运行
run_daily( after_market_close, time='after_close', reference_security=get_future_code('RB'))
在动量计算方面,代码如下:
g.Momentum[ins]作为每个品种的代码dict数据结构,可以看到它实际上是很简单的一种动量计算方式。
## 开盘时运行函数
def market_open(context):
# 输出函数运行时间
#log.info('函数运行时间(market_open):' str(context.current_dt.time()))
# 以下是主循环
for ins in g.instruments:
# 过滤空主力合约品种
if g.MappingReal[ins] != '':
IndexFuture = g.MappingIndex[ins]
RealFuture = g.MappingReal[ins]
# 获取当月合约交割日期
end_date = get_CCFX_end_date(RealFuture)
# 当月合约交割日当天不开仓
if (context.current_dt.date() == end_date):
return
else:
g.LastRealPrice[RealFuture] = attribute_history(RealFuture,1,'1d',['close'])['close'][-1]
# 获取价格list
#if g.StatusTimer[ins] == 0:
g.PriceArray[IndexFuture] = attribute_history(IndexFuture,g.BackWindow 5,'1d',['close','open','high','low'])
# 如果没有数据,返回
if len(g.PriceArray[IndexFuture]) < 1:
return
else:
g.ClosePrice[ins] = g.PriceArray[IndexFuture]['close']
g.CurrentPrice[ins] = g.ClosePrice[ins][-1]
g.Price_DaysAgo[ins] = g.ClosePrice[ins][-g.BackWindow]
g.close = np.array(g.PriceArray[IndexFuture]['close'])
g.high = np.array(g.PriceArray[IndexFuture]['high'])
g.low = np.array(g.PriceArray[IndexFuture]['low'])
g.ATR[IndexFuture] = talib.ATR(g.high,g.low,g.close, g.BackWindow)[-1]
g.Momentum[ins] = g.CurrentPrice[ins]/g.Price_DaysAgo[ins] -1
动量的排序在这里:
## 交易模块
def Trade(context):
# 定义每周买卖的列表
log.info(g.ClosePrice.keys())
BuyList = []
SellList = []
# 将字典进行排序,并将数值前五品种的symbol写入列表
a = sorted(g.Momentum.items(), key=lambda x: x[1],reverse = True)
for i in range(int(g.Range*len(g.MappingReal.keys()))):
BuyList.append(a[i][0])
SellList.append(a[-i-1][0])
首先放上一个长期测试的绩效图如下,不知道这样的绩效你能否满意,反正我觉得很难:
对于横截面动量,在2017年开始的回撤比较显著,并且在历史上也没有带来非常理想的回报。我们测试了以5日为持有期,10、15、20这3组动量测算期的绩效,并且测试了持有前20%,30%,40%的品种绩效组合,也就是3*3=9个参数绩效如下图:
上图分别是多组参数测试绩效、资金曲线图、收益风险矩阵。它们都是周度调仓,或者说接近研报上的5日调仓(持有)参数H。最终发现,做多前30%涨幅最多的品种,做空后30%跌幅最多的品种,持有5日,以10日考核期动量效应,这个组合绩效最好。
我们还测试了双周调仓,也就是每半个月调仓的效果,类似于研报中的10日调仓(持有)参数H,情况更不理想。
我们感觉还需要反复迭代或者思考的是:
1、多头和空头的动量衡量周期,是不是应该保持一致,还是应该拆分?
2、以较大的周期进行动量考核(某机构给出的250日动量考核期R参数)是否合理?
3、较为严格的止损方案,在较短周期的截面动量模型中,是否有效?
总之,还请大家多多提出意见,提出修改观点,来完善这个多空都持有的比较另类的动量模型。