第11节 期货市场的回测
作者: 阿布
阿布量化版权所有
上一节讲解的是比特币,莱特币市场的回测,以及使用abupy内置模块对市场进行分析优化策略,提高系统的稳定性,本节主要示例期货市场的回测。
abupy中内置的期货沙盒数据有如下交易品种
期货市场:
V0(PVC),P0(棕榈),M0(豆粕),I0(铁矿石),JD0(鸡蛋),L0(塑料),PP0(PP)
BB0(胶合板),Y0(豆油),C0(玉米),A0(豆一),B0(豆二),J0(焦炭),JM0(焦煤)
CS0(玉米淀粉),TA0(PTA),OI0(菜油),RS0(菜籽),RM0(菜粕),ZC0(动力煤)
WH0(强麦),FB0(纤维板),JR0(粳稻),SR0(白糖),CF0(棉花),RI0(早籼稻)
MA0(郑醇),FG0(玻璃),LR0(晚籼稻),SF0(硅铁),SM0(锰硅),FU0(燃油)
AL0(沪铝),RU0(橡胶),ZN0(沪锌),CU0(沪铜),AU0(黄金),RB0(螺纹钢)
WR0(线材),PB0(沪铅),AG0(白银),BU0(沥青),HC0(热轧卷板),SN0(沪锡)
NI0(沪镍)
1. 期货市场的特点
期货市场与之前示例讲解的股票市场,比特币市场有很大区别,期货的意思其实就是未来的商品。
下面首先使用AbuFuturesCn输出默认期货产品:
futures = AbuFuturesCn()futures.futures_cn_df[:10]
如上所示PVC,豆粕,鸡蛋,胶合板等都是所做的期货具体商品,比如上面的鸡蛋一行:
JD0为交易品种代码,注意这里请求的并非某一个具体合约代码,比如JD1709是具体的17年9月鸡蛋合约,因为做量化需要连续的数据,所以请求的都是合约的连续数据:
ABuSymbolPd.make_kl_df('JD0').tail()
min_uni的意思是交易每一手的数量,单位对应产品各不相同,比如鸡蛋这里的5的意思是5吨/手,胶合板的500的意思是500张/手
commission的意思是每一手的手续费,不同品种手续费不一样
min_deposit的意思是最低交易保证金比例,0.08的意思是8%做为最低保证金,期货使用保证金制度
比如上面看到的2017-07-19鸡蛋收盘价格为4014元/吨,如果你觉着还能涨,那就买入看涨合约,鸡蛋min_unit=5吨/手, 如果买20手,需要保证金账号里最少有多少钱呢?计算如下:
print('商品价值{}, 总数量{}顿鸡蛋, 保证金{}'.format(20 * 5 * 4014, 20 * 5, 20 * 5 * 4014 * 0.08))
商品价值401400, 总数量100顿鸡蛋, 保证金32112.0
如上所示保证金大概3万多块钱可以撬动价值40万的100吨鸡蛋商品,实际上这就是期货市场高风险高收益的根源,本来需要用40万能做的买卖,在期货市场用3万就能做,看似缩小了成本,但是比如你真有40万,你在期货市场不会还只做3万的买卖了,你将变成做400万的买卖,买卖做大了,风险和收益自然也大了。
下面让时间继续来到了2017-07-20日,由于你是买入的看涨,今天收盘价格下跌到3972,那么你今天就赔钱了,一共赔了:100吨 * (4014 - 3972)
(4014 - 3972) * 100
4200
假设你保证金账号里一共有35000,那么这时:35000 - 4200 = 30800, 还剩30800,现在的价格是3972元/吨,重新计算需要的保证金如下:
20 * 5 * 3972 * 0.08
31776.0
需要保证金31776,那么帐户里的钱就不够了,就会被期货公司强行平仓,实际上上面只是举例子,期货公司不会让帐户里的钱不够这种情况出现,帐户里资金不多时候,就会要求你继续追加保证金了。
下面假设帐户里的保证金很充足,时间继续向前,到了2017-07-25,当日的最高价格为4025,如果你真的以最高价格卖出了合约:
(4025 - 4014) * 100
1100
如上所示将最终赚钱1100块。
上面说的合约是在2017-07-19买的看涨合约期货,假如你在2017-07-19买的是看跌合约,好多人不理解看跌怎么挣钱,实际上看跌合约可理解为你向交易所借钱买入了商品然后马上卖出了,然后你的期望是价格下跌,然后当价格足够低的时候你再买入卖出,这样除了还给交易所之前借的钱之外,你还能获取这两次买卖的差价。
那么比如上面在2017-07-19以4014元/吨买入的是看跌合约,初始保证金和买入看涨合约时是一样的,然后假如你在2017-07-25,以当日的最低价格3930卖出了合约将最终获利8400元,如下所示:
(4014 - 3930) * 100
8400
再说个极端情况,假如2017-07-25是合约的最后交割日,那么是不是真的能提取100吨鸡蛋现货呢?
在你开户的时候就会让你选择你的账户类型:保值或者投机,但其实只有有资格的认证企业才能选保值,对于个人都只能选投机,投机是没有资格真的在交割日期进行提取现货的,只有企业账户才有资格,所以如果你真的在交割日还持有合约,交易所会强行平仓。
2 看涨合约的回测
下面首先还是和之前的章节使用一样的卖出因子,和初始资金量,唯一不同的是设置g_market_target为E_MARKET_TARGET_FUTURES_CN
abupy.env.g_market_target = EMarketTargetType.E_MARKET_TARGET_FUTURES_CN#买入因子,卖出因子等依然使用相同的设置,如下所示:read_cash = 3000000# 卖出因子继续使用上一节使用的因子sell_factors = [ {'stop_loss_n': 1.0, 'stop_win_n': 3.0, 'class': AbuFactorAtrNStop}, {'class': AbuFactorPreAtrNStop, 'pre_atr_n': 1.5}, {'class': AbuFactorCloseAtrNStop, 'close_atr_n': 1.5}]
买入因子组合稍微变动,之前使用的都是42, 60天突破,改为使用21,42天突破,上一节在示例讲解比特币市场时演示过通过ABuKLUtil.resample_close_mean计算出比特币市场应该选择的周期在10天上下,期货市场这里的周期选择实际上是因为市场的特点所改变的,因为期货有限定的交割周期,且很多期货产品有着明显的季节性。
# 买入因子依然延用AbuFactorBuyBreak,周期改变为21,42buy_factors = [{'xd': 21, 'class': AbuFactorBuyBreak}, {'xd': 42, 'class': AbuFactorBuyBreak}]
abupy.beta.atr.g_atr_pos_base = 0.03abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash, buy_factors, sell_factors, n_folds=2, choice_symbols=None)AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)
买入后卖出的交易数量:555胜率:48.1081%平均获利期望:9.0533%平均亏损期望:-4.8457%盈亏比:1.9529策略收益: 31.8571%基准收益: 17.8802%策略年化收益: 15.9286%基准年化收益: 8.9401%策略买入成交比例:61.7792%策略资金利用率比例:67.6269%策略共执行504个交易日
3 看跌合约的回测
看跌合约的回测首先需要一个看跌买入因子,下面的代码即示例向下突破做为买入因子的put策略:
class AbuFactorBuyPutXDBK(AbuFactorBuyXD, BuyPutMixin): '''示例继承AbuFactorBuyXD完成反向突破买入择时类''' def fit_day(self, today): ''' 针对每一个交易日拟合买入交易策略,寻找向上突破买入机会 :param today: 当前驱动的交易日金融时间序列数据 ''' # 与AbuFactorBuyBreak区别就是买向下突破的,即min() if today.close == self.xd_kl.close.min(): return self.buy_tomorrow() return None
上AbuFactorBuyPutBreak即是完成了向下突破put策略的代码,最大特点就是因子混入BuyPutMixin,即做为反向策略,看跌。
下面使用21天,42天向下突破看跌策略做为买入因子组合,其它都不变,代码如下所示:
buy_factors = [{'xd': 21, 'class': AbuFactorBuyPutBreak}, {'xd': 42, 'class': AbuFactorBuyPutBreak}]abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash, buy_factors, sell_factors, n_folds=2, choice_symbols=None)AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)
买入后卖出的交易数量:533胜率:40.5253%平均获利期望:5.2618%平均亏损期望:-4.6178%盈亏比:0.7937策略收益: 3.0812%基准收益: 17.8802%策略年化收益: 1.5406%基准年化收益: 8.9401%策略买入成交比例:66.2362%策略资金利用率比例:63.4951%策略共执行504个交易日
下面拿出交易单的一个看看,可以发现expect_direction列值是-1, buy_type_str显示为put,如下所示:
abu_result_tuple.orders_pd.head(1)
卖出因子在support_direction实现中需要声明自己支持的买入因子类型,且在fit_day中根据不同交易方向做处理,如下AbuFactorPreAtrNStop示例所示:
class AbuFactorPreAtrNStop(AbuFactorSellBase): '''示例单日最大跌幅n倍atr(止损)风险控制因子''' def _init_self(self, **kwargs): '''kwargs中参数pre_atr_n: 单日最大跌幅止损的atr倍数''' # 设置下跌止损倍数 self.pre_atr_n = kwargs['pre_atr_n'] def support_direction(self): '''单日最大跌幅n倍atr(止损)因子支持两个方向''' return [ESupportDirection.DIRECTION_CAll.value, ESupportDirection.DIRECTION_PUT.value] def fit_day(self, today, orders): ''' 止损event:今天相比昨天的收益 * 买入时的期望方向 > today.atr21 * pre_atr_n :param today: 当前驱动的交易日金融时间序列数据 :param orders: 买入择时策略中生成的订单序列 ''' for order in orders: if (today.pre_close - today.close) * order.expect_direction > today.atr21 * self.pre_atr_n: # 只要今天的收盘价格比昨天收盘价格差大于一个差值就止损卖出, 亦可以使用其它计算差值方式 self.sell_tomorrow(order)
上面fit_day中根据order.expect_direction的值对正向,反向买入因子做处理,即:
正向call看涨买入因子order.expect_direction的值为正1,则结果为今天相对昨天价格暴跌卖出
反向put看跌买入因子order.expect_direction的值为正-1, 则结果为今天相对昨天价格暴涨卖出
abupy中内置的几个卖出因子都实现了看涨和看跌两个方向,用户自己使用的卖出因子如果不考虑做期货等市场的情况下,则不需要支持看跌的买入因子。
更多详情自行阅读AbuFactorPreAtrNStop等卖出因子源代码
下面同时使用向上突破看涨call策略和向下突破看跌put策略进行回测,如下所示:
buy_factors = [{'xd': 21, 'class': AbuFactorBuyPutBreak}, {'xd': 42, 'class': AbuFactorBuyPutBreak}, {'xd': 21, 'class': AbuFactorBuyBreak}, {'xd': 42, 'class': AbuFactorBuyBreak}]abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash, buy_factors, sell_factors, n_folds=2, choice_symbols=None)AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)
买入后卖出的交易数量:1088胜率:44.3934%平均获利期望:7.3621%平均亏损期望:-4.7259%盈亏比:1.3535策略收益: 39.1533%基准收益: 17.8802%策略年化收益: 19.5766%基准年化收益: 8.9401%策略买入成交比例:48.0418%策略资金利用率比例:84.0932%策略共执行504个交易日
4. 位移路程比优化策略
上面的回测结果收益一般,上一节比特币的回测通过分析数据对参数进行优化,提高回测收益,港股回测那一节通过AbuTLine.show_least_valid_poly对策略进行优化,本节将根据期货的特点,编写一个选股策略对交易进行优化。
上面的回测共交易的期货品种有38种,如下所示:
len(set(abu_result_tuple.orders_pd.symbol))
40
实际上没有必要涉及这么多的品种,下面根据期货的交易特点编写一个选股策略,选取更合适策略的期货品种。
期货市场可以买涨也可以买跌,本节示例的策略使用向上突破买涨,向下突破买跌的策略,即多空都做,既然使用突破策略且突破策略使用参数为21天,42天这个突破周期对于期货市场来说并不短,那么期望买入的品种可以拥有一定的趋势,且有保持趋势一段时间的习惯,不希望品种的走势总是反复震荡,趋势总是短时间进行回复,并且也不希望走势太平稳,完全没有趋势形成。
下面通过位移路程比来进行选股主策略,位移路程比的计算通过AbuTLine中的show_shift_distance实现,首先定义个lambda函数可视化选股周期的位移路程比sd_line:
sd_line = lambda sym: tl.AbuTLine( ABuSymbolPd.make_kl_df(sym, start='2014-06-27', end='2015-07-07').close, '').show_shift_distance( step_x=1.2, show_log=False)
使用sd_line查看商品V0(PVC)在选股周期的位移路程比,如下所示,可以发现位移路程比值基本都在2以下,但也会有2以上的情况
sd_line('V0');
类似上面的V0的走势是符合我们的交易策略,因为不管你上涨还是下跌,只要商品可以惯性的保持一段时间趋势,使用的向上突破和向下突破就都可以找到盈利机会。
下面再看看商品FB0(纤维板)的位移路程比图:
sd_line('FB0');
从图中你可以发现FB0经常在短时间内上涨,然后反弹,趋势无法保持,所以FB0(纤维板)是我们应该过滤的商品,反复反弹就意外着位移路程比值大,你可以发现有多个大于2.0的位移路程比阶段。
再看看下面的Y0(豆油)位移路程比图:
sd_line('Y0');
从图中你可以发现走势太平稳了,没有好的趋势会形成,并不适合使用的突破策略,你可以发现有没有1个移路程比值大于2.0。
ok,下面就根据以上观察结果编写选股策略,代码如下所示:
备注:关于更多选股因子请阅读abu量化文档:第五节 选股策略的开发
from abupy import AbuPickStockBase, psclass AbuPickStockShiftDistance(AbuPickStockBase): '''位移路程比选股因子示例类''' def _init_self(self, **kwargs): '''通过kwargs设置位移路程比选股条件,配置因子参数''' self.threshold_sd = kwargs.pop('threshold_sd', 2.0) self.threshold_max_cnt = kwargs.pop('threshold_max_cnt', 4) self.threshold_min_cnt = kwargs.pop('threshold_min_cnt', 1) @ps.reversed_result def fit_pick(self, kl_pd, target_symbol): '''开始根据位移路程比边际参数进行选股''' pick_line = tl.AbuTLine(kl_pd.close, 'shift distance') shift_distance = pick_line.show_shift_distance(step_x=1.2, show_log=False, show=False) shift_distance = np.array(shift_distance) # show_shift_distance返回的参数为四组数据,最后一组是每个时间段的位移路程比值 sd_arr = (shift_distance)[:, -1] # 大于阀值的进行累加和计算 threshold_cnt = (sd_arr >= self.threshold_sd).sum() # 边际条件参数开始生效 if threshold_cnt < self.threshold_max_cnt and threshold_cnt >= self.threshold_min_cnt: return True return False def fit_first_choice(self, pick_worker, choice_symbols, *args, **kwargs): raise NotImplementedError('AbuPickStockShiftDistance fit_first_choice unsupported now!')
下面代码继续同时使用向上突破看涨call策略和向下突破看跌put策略进行回测,唯一的不同是使用选股策略因子AbuPickStockShiftDistance:
stock_pickers = [{'class': AbuPickStockShiftDistance, 'threshold_sd': 2.0, 'threshold_max_cnt': 4, 'threshold_min_cnt': 1, 'reversed': False}]buy_factors = [{'xd': 21, 'class': AbuFactorBuyPutBreak}, {'xd': 42, 'class': AbuFactorBuyPutBreak}, {'xd': 21, 'class': AbuFactorBuyBreak}, {'xd': 42, 'class': AbuFactorBuyBreak}]abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash, buy_factors, sell_factors, stock_pickers, n_folds=2, choice_symbols=None)
AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)
买入后卖出的交易数量:601胜率:44.7587%平均获利期望:8.5512%平均亏损期望:-4.8672%盈亏比:1.5452策略收益: 45.0541%基准收益: 17.8802%策略年化收益: 22.5270%基准年化收益: 8.9401%策略买入成交比例:79.4953%策略资金利用率比例:74.3811%策略共执行504个交易日
从上面结果看收益提升了大概7%,虽然不多,但是交易数量从1088下降到601,降低交易频率是最好的优化策略,涉及的交易商品品种也由之前的38个下降到21个, 如下所示:
len(set(abu_result_tuple.orders_pd.symbol))
21
5. 国际期货市场的回测
gb = AbuFuturesGB()gb.futures_gb_df.head()
将目标市场设置为国际期货市场,如下:
abupy.env.g_market_target = EMarketTargetType.E_MARKET_TARGET_FUTURES_GLOBAL
之后的章节会有国内,国际期货市场进行相关策略的统计套利策略示例,本节继续简单使用向上突破看涨call策略和向下突破看跌put策略进行回测,如下:
buy_factors = [{'xd': 21, 'class': AbuFactorBuyPutBreak}, {'xd': 42, 'class': AbuFactorBuyPutBreak}, {'xd': 21, 'class': AbuFactorBuyBreak}, {'xd': 42, 'class': AbuFactorBuyBreak}]abu_result_tuple_gb, _ = abu.run_loop_back(read_cash, buy_factors, sell_factors)AbuMetricsBase.show_general(*abu_result_tuple_gb, only_show_returns=True)
买入后卖出的交易数量:371买入后尚未卖出的交易数量:14胜率:40.9704%平均获利期望:5.4482%平均亏损期望:-4.6024%盈亏比:0.8230策略收益: -7.6953%基准收益: 11.5986%策略年化收益: -3.8477%基准年化收益: 5.7993%策略买入成交比例:34.5455%策略资金利用率比例:75.9042%策略共执行504个交易日
小结:
上面通过位移路程比进行选股的策略编写在实现细节上可以有多种变种,比如使用多个时间段的均值和阀值进行比较,针对震荡太过剧烈趋势反复的和走势太过平稳的情况分开阀值进行判断等等,上面的实现主要是为了好理解,而且介于篇幅这里没能再多介绍几个选股因子并行生效处理,在之后的章节会有更加完整详细的示例,请关注公众号的更新提醒
与期货市场类似的是美股期权市场,abupy同样支持美股期权市场的回测分析等操作,但由于暂时没有合适的可对外的数据源提供,所以暂时无示例,后续适配合适的期权数据源后会完善美股期权示例讲解,用户也可以在abupy中接入自己的美股期权数据源,详例请阅读第19节:数据源