请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3365791 新帖:0

趋势交易能赚钱吗?商品期货动量效应挖掘初探

今天你爆仓了吗发表于:5 月 9 日 19:32回复(1)

各位老板好,之前我做了一些期货方面的内容,感觉很不错,大概几百人都获取了源码。

今天想做点简单的,甚至可以说简单到不可思议的。为了验证结果可靠,我测试了很多期货品种(4个金属、6个化工、9个农产品、7个工业品,几乎要把期货全品种都验证了),把资金曲线整合到一起输出成今天的文章:

因为很多人都怀疑,期货难道使用很简单的趋势交易方法,就可以赚钱吗?也担心我们在模型里放置了很多复杂条件,或者说拟合调优了参数,才能有较好的绩效。

你们真是股票做太多了,没有去放眼看看其他资产。

我收集了一些资料,也做了一个最简单的模型,来证明商品期货市场,确实存在最简单原始的动量(可以理解为惯性)效应,无论是股票还是期货甚至数字货币市场,价格延续上一段时间的方向继续波动,是非常值得探索的一种价格运行方式,也是最容易被数量化的分析方式所捕捉的。

什么是动量效应

这种类似惯性方向运动的价格变化现象被称作动量效应。比较专业地说:动量效应是由Jegadeesh和Titman(1993)提出的,是指股票的收益率有延续原来的运动方向的趋势,即过去一段时间收益率较高的股票在未来获得的收益率仍会高于过去收益率较低的股票。

QQ图片20181213143554.png
美股市场,热门股票和市场指数都呈现很强的动量效应

我们尝试过通过均线、高低点突破、通道等方式捕捉动量,让模型在动量产生时候入场,在动量衰竭或已经呈现反向运行(反转)时候出场。其中均线表达了最近的平均波动,高低点突破表达了一个价格区间被突破后,交易者是否会一致认为价格继续运行,通道突破更大的含义在乎过滤噪音,在通道内部的动量我们确认为不被信任的动量,必须突破通道的上下轨,我们才开仓交易。

QQ图片20181213143634.png

动量是可以分析观测,并且把握的,而反转虽然也有办法捕捉,并抓化成收益,但是它更倾向于随机。随机波动非常可怕,这是我们大部分利润被消耗的核心原因,所以做模型,选品种,选周期一定要尽可能避免随机波动,且建模数据量尽可能大,这样才能准确观测把握动量波动。

商品期货或者股票等市场上的动量,又主要分为两种,其实是两个不同的观察维度导致的。进一步展开刚才说的动量效应,多位学者研究发现,动量效应主要以两种形式存在。

QQ图片20181213143721.png

第一种是时间序列动量效应,指的是前期上涨的品种在未来一段时间有可能继续上涨,前期下跌的品种在未来一段时间有可能继续下跌。

另一种是横截面动量效应,指的是在同一个时间点上,做多相同时间区间内涨幅较高的品种,做空涨幅较低的品种,可以获得持续稳定的收益。比如每日涨幅前5名,下一日我们继续做多,每日跌幅前5名,下一日继续做空。

为什么会产生动量

今天我们主要介绍时间序列动量的最原始验证和捕捉方法。但是在此之前,我想再说一点题外话,为什么会存在动量效应。股票市场上的这些动量效应和反转效应,来源于投资者的心理认知偏差,比如:

(1)过度自信
人们在决策中总是倾向于过高估计自己的判断力和决策力,进而容易忽视情况变化造成决策失误。通常人们认为对某事抱有90%的把握时,事实证明成功的概率大约只有70%。

(2)后悔厌恶
后悔厌恶指当人们做出错误的决策时,对自己的行为感到痛苦。为了避免痛苦,人们常常做出许多看起来是非理性的行为。

QQ图片20181213143809.png

(3)损失厌恶
损失厌恶是指人们面对同样数量的收益和损失时,感到损失比收益更加令他们难以忍受。

(4)锚定效应
锚定效应是指人们在对某人某事做出决策时,易受第一印象或者第一信息支配,人们在接受决策时,会不自觉地给予最初信息过多的关注。

(5)从众心理
当人们发现自己与多数人的判断不一致时,感受到一定的压力,从而改变原来自己正确的判断。

QQ图片20181213143835.png
图片来自《大数投资》

这些心理的认知偏差影响了投资者的决策行为,从而使股票市场对某种趋势反应过度或者反应不足,并最终导致动量效应和反转效应。

时间序列动量模型原理

股票或期货价格上涨和下跌,也就是价格运行的幅度,就是动量。动量可以使用百分比度量,也可以使用ATR度量,前者没有价格量纲,后者有价格量纲,可以再除以时间段内均价去量纲。

根据海通证券研报,Moskowitz, Ooi 和 Pedersen(2012)使用 1985 年至 2009 年间 58 个商品期货品种的数据研究发现,商品期货具有明显的时间序列动量效应,做多前期上涨的品种,做空前期下跌的品种构建的多品种投资组合可以取得持续稳定的超额收益。我们使用我国商品期货品种来验证该策略的有效性。

策略逻辑:做多前 R 个交易日上涨的品种,做空前 R 个交易日下跌的品种,每隔 H
个交易日调整一次;
回测时间:2005/01/04 – 2017/01/26;
品种选取:调仓日选取上市满半年,同时主力合约前 20 个交易日日均成交量大于
1 万手的期货品种作为可选标的;
排序期 R 和持有期 H:10 个交易日;
仓位:50%;
保证金:20%;
交易成本:单边万分之三;
备注:避免起点日期的影响,将初始资金等权分配到 10 个账户中,每个账户初始
日期相差一个交易日,将 10 个账户里的资金汇总计算每日的净值。

QQ图片20181213143929.png

如此简单的策略,可以取得23.58%的年化收益率,收益风险比和calmar 比率分别为1.15和0.96。而同期等权做多策略年化收益率仅为-1.76%,最大回撤高达88%。这说明了静态持有大宗商品肯定是不行的,这不是一种投资思路,大宗商品的价格具有极大的不确定性。

阅读更多资料我们可知:动量效应打破了有效市场假说,曾被认为是一种市场“异常”,但随后,越来越多的股票市场和其他类型的交易市场(如期货市场)都被证明存在动量效应。且动量因子作为 CTA 量化策略追踪趋势的主要因子之一。

不知大家是否留意到,该策略是包含参数的,时间序列动量策略涉及的参数主要有回溯期和持有期。回溯期指计算动量因子的时间长度,用R表示,代表建仓日前一天开始向前推R天,计算这R天的平均收益率;持有期指建仓后持有组合的时间长度,用H表示,代表每隔H天建仓,持有组合至下个建仓日。

QQ图片20181213143954.png

根据华泰期货研究所的数据,当参数组合在10-5情况下收益最高,也就是说用过去10日衡量动量,再持有5天。这样不断循环向前回测,如果一个品种的动量为正,就做多,为负,就做空。策略不设计任何止损止盈逻辑,因为如果动量发生反转,我们更倾向于考核模型本省的动量分析能力,来发出出场指令。

在聚宽平台做期货策略相对轻松,因为有一些可用模板和函数,并且保证了模型的可移植性,关键问题还是,用指数回测,然后在当时的真实主力合约上交易,这件事显得非常靠谱,比一些本地化的PC软件全部交易在指数合约上完成要真实很多。

首先设计模型第一部分,做回测的环境设置,需要注意我们在这里使用了定时运行函数run_weekly,大致来完成每5个交易日的调仓效果,为什么不用计数器每过5天定期调仓呢,之后为大家解读:

from jqdata import * 
import talib

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')])
    # 期货类每笔交易时的手续费是:买入时万分之0.5,卖出时万分之0.5,平今仓为万分之5
    set_order_cost(OrderCost(open_commission=0.00005, close_commission=0.00005,close_today_commission=0.00005), 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'))

然后是模型中需要使用的部分全局变量:

# 参数变量设置函数
def set_parameters_and_vars(context):

    #######变量设置########
    g.LastRealPrice = {} # 最新真实合约价格字典(用于吊灯止损)
    g.future_list = []   # 设置期货品种列表
    g.TradeLots = {}     # 各品种的交易手数信息
    g.PriceArray = {}    # 信号计算价格字典
    g.Price_dict = {}    # 各品种价格列表字典
    g.MappingReal = {}   # 真实合约映射(key为symbol,value为主力合约)
    g.MappingIndex = {}  # 指数合约映射 (key为 symbol,value为指数合约
    g.StatusTimer = {}   # 当前状态计数器
    g.ATR = {}           # ATR
    g.CurrentPrice = {}  # 当前价格
    g.Price_DaysAgo = {} # 窗口期开始价格
    g.Momentum = {}      # 动量值
    g.Signal = {}        # 信号值
    g.ClosePrice = {}    # 收盘价

    #######参数设置########
    g.HoldingWeek = 1 # 持有窗口长度
    g.Timer = 0
    g.BackWindow = 10 # 回溯窗口长度
    g.Cross = 0       # 均线交叉判定信号
    g.MarginRate = 0.15 
    # 交易的期货品种信息
    g.instruments = 
['AL','NI','CU','AG','RU','MA','PP','TA','L','V','M','A','P','Y','OI','C','CS','JD','SR','HC','J','I','SF','RB','ZC','FG']
    # 价格列表初始化
    set_future_list(context)

通过before_market_open函数,我们完成了主力合约的搜索和换月。该函数包括了对replace_old_futures函数的调用,完成了移仓模块(当主力合约更换时,平当前持仓,更换为最新主力合约)。

关于移仓换月这部分,我已经问了聚宽的产品小哥哥,他们会提供相应的,更简单的函数,完成自动移仓换月。

我这段逻辑,以后就不用写了!不用写了!

def before_market_open(context):
    # 输出运行时间
    log.info('函数运行时间(before_market_open):' str(context.current_dt.time()))
    send_message('开始交易')

    # 过滤无主力合约的品种,传入并修改期货字典信息
    for ins in g.instruments:
        dom = get_dominant_future(ins)
        if dom == '':
            pass
        else:
            # 判断是否执行replace_old_futures
            if dom == g.MappingReal[ins]:
                pass
            else:
                replace_old_futures(context,ins,dom)
                g.future_list.append(dom)

            g.TradeLots[dom] = 
get_lots(context.portfolio.starting_cash/len(g.instruments),ins)

然后通过market_open函数,我们完成了价格数据的获取,计算了ATR值,计算了动量值g.Momentum,发出了动量大于0还是小于0的信号值g.Signal。

Trade函数负责下单,因为我们是一次完成多品种下单,所以要用for循环完成,这里的ins是每个期货品种,g.instruments我们最初定义的本次模型所覆盖的品种。这种下单模式,可以理解为,使用TB、MC等软件,讲模型挂载在很多期货合约上,一起运行。

    for ins in g.instruments:
        RealFuture = g.MappingReal[ins]
        if ins in g.Signal.keys() and RealFuture in g.LastRealPrice.keys():

get_future_code函数是我们自己写出来各品种的合约完整代码,如A8888.XDCE,它可以帮助我们在模型里更便捷调用商品期货品种属性,其实也可以通过聚宽的API完成,但是我已经习惯了直接写成一个dict数据类型。

在获取交易手数的函数方面,涉及到分配资金这个重要工作,我们撰写了两个版本的函数,分别是ATR倒数头寸,和无ATR等资金版本。

前者也是ATR波动率倒数头寸(每次使用10%的资金去承担风险),可以理解为是等波动率配置资金:
cash0.10/(g.ATR[IndexFuture]future_coef_list[symbol])
后者完全没有考虑波动率,是等权配置头寸
合约保证金的表达式是:open_pricefuture_Contract_Size[symbol]g.MarginRate,实际上对于cash,也不应该全部使用,而是应该一个仓位比率,比如cash0.33,以不超过1/3的仓位去开仓,这里测试阶段,我没有乘以这个比率:
cash/(open_pricefuture_Contract_Size[symbol]g.MarginRate)

时间序列动量测试效果

之前说过,我们使用了定时函数完成回测工作,而没有采用日期计数器模式。因为在测试中我们发现,时间序列动量模型会存在较为严重的路径依赖。

时间序列动量多品种_20170116-20181205.png

何为路径依赖?比如我们从2017年1月2日和1月16日,两个不同的回测周期运行模型,会得到不一样的结果,在某些品种上差异还挺大。实际上我们发现,上图中下半部分回测只不过是错过了关键的一两次开仓,导致整体收益大幅度落后上图。这属于测试中的重要误差。

所以解决此问题方法有两种:

1、将调仓分散:比如T日开始,每隔5天调仓,则在T日,T 1日,T 2日,T 3日,T 4日,每天都运行模型,分配20%资金交易。这样基本上避免了路径依赖问题,但是较为消耗资金,股票类模型尤其如此。

2、使用定时函数:5日的调仓变为run_weekly驱动的每周调仓,这样还能避免因为3天、7天的节假日造成的某些周日期较少,但是依然间隔周末的问题。因为每隔一个周末,交易者心态都会发生较大变化,市场资金面也是一样,所以理论上的每5日调仓,应该等同于每周调仓。在资金非常充裕的情况下,要分配到周1~周5,资金不足可以不分配。

等周期绩效.png

按照研报上推荐的周期,10日计算动量,5日调仓,我们获得此绩效如上图。看起来资金曲线很棒,说明期货动量效应确实显著,但是如果变化调仓期,到半个月(等效10日)效果就不行了(下图),看来还是需要以较快的频度调仓。需要注意的是,多组资金曲线的回测,是通过【研究】调用【回测】得到的,具体过程在这里讲过:
https://www.joinquant.com/post/4351?tag=algorithm

持有期半个月,无ATR,参数 5 10 20 30 60.png
上图为持有期半个月,无ATR的资金曲线

切换回到5日调仓,以等资金模式测试,变化动量计算周期参数,得到下图。直观感受是,盈利更高了,回撤更小了,在2016年之后模型的盈利保持情况较好:

五ATR-5日调仓.png
上图为等资金仓位,5日调仓的效果

然后我们注释掉等资金模块,打开ATR倒数头寸模块,得到多组测试绩效如下:

ATR-5日调仓.png
上图为ATR倒数仓位,5日调仓的效果

两组看上去差异不大,实际上,我们通过导出绩效到Excel仔细观察后发现显然ATR组的收益风险比或者说Calmar比率(Calmar Ratio,收益和最大回撤之间的关系)要更好:

QQ图片20181213145100.png

以研报和我们实测最佳参数来看,的确是ATR的回撤要更小,在商品期货这种杠杆市场,更小的回撤,意味着我们可以以更大的杠杆比率去交易,所以一定要多关注最大回撤这个绩效指标。在这个很小范围内(5组)的参数变化测试中,等资金组的Calmar比率是3.99,ATR倒数(等波动率)组的Calmar比率是2.66,提升比率达到50%左右。

QQ截图20181212104940.png

今天的对于期货市场时间序列动量策略的分析基本结束,也许你会认为计算规则过于简单,缺乏了很多非线性约束或者过滤条件,但实际上,我们保持了系统的鲁棒性,验证了基本的时间序列动量存在性,通过ATR倒数头寸保持了系统的收益风险比提升。我们想告诉各位读者,商品期货上,做时间序列动量,来实现中长期的盈利,忍受略长时间(半年左右)的回撤,是绝对没错的

接下来我们可能将研究重点放在截面动量上,以做多“上涨前N名的期货品种”,做空“下跌前N名的期货品种”,依然是构建一个相当清晰简单的策略,来寻找在日频截面上,全市场是否可以通过做多做空的对冲方式得到较为稳健的收益。

全部回复

0/140

量化课程

    移动端课程