├── README.md ├── attach.png └── example.py /README.md: -------------------------------------------------------------------------------- 1 | # 跨期套利(期货) 2 | 期货的跨期套利策略 3 | 4 | ## 源码 5 | ```python 6 | # coding=utf-8 7 | from __future__ import print_function, absolute_import, unicode_literals 8 | import numpy as np 9 | from gm.api import * 10 | try: 11 | import statsmodels.tsa.stattools as ts 12 | except: 13 | print('请安装statsmodels库') 14 | ''' 15 | 本策略根据EG两步法(1.序列同阶单整2.OLS残差平稳)判断序列具有协整关系之后(若无协整关系则全平仓位不进行操作) 16 | 通过计算两个真实价格序列回归残差的0.9个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做多价差 17 | 并在回归至标准差水平内的时候平仓 18 | 回测数据为:SHFE.rb1801和SHFE.rb1805的1min数据 19 | 回测时间为:2017-09-25 08:00:00到2017-10-01 15:00:00 20 | ''' 21 | # 协整检验的函数 22 | def cointegration_test(series01, series02): 23 | urt_rb1801 = ts.adfuller(np.array(series01), 1)[1] 24 | urt_rb1805 = ts.adfuller(np.array(series01), 1)[1] 25 | # 同时平稳或不平稳则差分再次检验 26 | if (urt_rb1801 > 0.1 and urt_rb1805 > 0.1) or (urt_rb1801 < 0.1 and urt_rb1805 < 0.1): 27 | urt_diff_rb1801 = ts.adfuller(np.diff(np.array(series01)), 1)[1] 28 | urt_diff_rb1805 = ts.adfuller(np.diff(np.array(series01), 1))[1] 29 | # 同时差分平稳进行OLS回归的残差平稳检验 30 | if urt_diff_rb1801 < 0.1 and urt_diff_rb1805 < 0.1: 31 | matrix = np.vstack([series02, np.ones(len(series02))]).T 32 | beta, c = np.linalg.lstsq(matrix, series01)[0] 33 | resid = series01 - beta * series02 - c 34 | if ts.adfuller(np.array(resid), 1)[1] > 0.1: 35 | result = 0.0 36 | else: 37 | result = 1.0 38 | return beta, c, resid, result 39 | else: 40 | result = 0.0 41 | return 0.0, 0.0, 0.0, result 42 | else: 43 | result = 0.0 44 | return 0.0, 0.0, 0.0, result 45 | def init(context): 46 | context.goods = ['SHFE.rb1801', 'SHFE.rb1805'] 47 | # 订阅品种 48 | subscribe(symbols=context.goods, frequency='60s', count=801, wait_group=True) 49 | def on_bar(context, bars): 50 | # 获取过去800个60s的收盘价数据 51 | close_01 = context.data(symbol=context.goods[0], frequency='60s', count=801, fields='close')['close'].values 52 | close_02 = context.data(symbol=context.goods[1], frequency='60s', count=801, fields='close')['close'].values 53 | # 展示两个价格序列的协整检验的结果 54 | beta, c, resid, result = cointegration_test(close_01, close_02) 55 | # 如果返回协整检验不通过的结果则全平仓位等待 56 | if not result: 57 | print('协整检验不通过,全平所有仓位') 58 | order_close_all() 59 | return 60 | # 计算残差的标准差上下轨 61 | mean = np.mean(resid) 62 | up = mean + 0.9 * np.std(resid) 63 | down = mean - 0.9 * np.std(resid) 64 | # 计算新残差 65 | resid_new = close_01[-1] - beta * close_02[-1] - c 66 | # 获取rb1801的多空仓位 67 | position_01_long = context.account().position(symbol=context.goods[0], side=PositionSide_Long) 68 | position_01_short = context.account().position(symbol=context.goods[0], side=PositionSide_Short) 69 | if not position_01_long and not position_01_short: 70 | # 上穿上轨时做空新残差 71 | if resid_new > up: 72 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 73 | position_side=PositionSide_Short) 74 | print(context.goods[0] + '以市价单开空仓1手') 75 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 76 | position_side=PositionSide_Long) 77 | print(context.goods[1] + '以市价单开多仓1手') 78 | # 下穿下轨时做多新残差 79 | if resid_new < down: 80 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 81 | position_side=PositionSide_Long) 82 | print(context.goods[0], '以市价单开多仓1手') 83 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 84 | position_side=PositionSide_Short) 85 | print(context.goods[1], '以市价单开空仓1手') 86 | # 新残差回归时平仓 87 | elif position_01_short: 88 | if resid_new <= up: 89 | order_close_all() 90 | print('价格回归,平掉所有仓位') 91 | # 突破下轨反向开仓 92 | if resid_new < down: 93 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 94 | position_side=PositionSide_Long) 95 | print(context.goods[0], '以市价单开多仓1手') 96 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 97 | position_side=PositionSide_Short) 98 | print(context.goods[1], '以市价单开空仓1手') 99 | elif position_01_long: 100 | if resid_new >= down: 101 | order_close_all() 102 | print('价格回归,平所有仓位') 103 | # 突破上轨反向开仓 104 | if resid_new > up: 105 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 106 | position_side=PositionSide_Short) 107 | print(context.goods[0], '以市价单开空仓1手') 108 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 109 | position_side=PositionSide_Long) 110 | print(context.goods[1], '以市价单开多仓1手') 111 | if __name__ == '__main__': 112 | ''' 113 | strategy_id策略ID,由系统生成 114 | filename文件名,请与本文件名保持一致 115 | mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST 116 | token绑定计算机的ID,可在系统设置-密钥管理中生成 117 | backtest_start_time回测开始时间 118 | backtest_end_time回测结束时间 119 | backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST 120 | backtest_initial_cash回测初始资金 121 | backtest_commission_ratio回测佣金比例 122 | backtest_slippage_ratio回测滑点比例 123 | ''' 124 | run(strategy_id='strategy_id', 125 | filename='main.py', 126 | mode=MODE_BACKTEST, 127 | token='token_id', 128 | backtest_start_time='2017-09-25 08:00:00', 129 | backtest_end_time='2017-10-01 16:00:00', 130 | backtest_adjust=ADJUST_PREV, 131 | backtest_initial_cash=500000, 132 | backtest_commission_ratio=0.0001, 133 | backtest_slippage_ratio=0.0001) 134 | ``` 135 | 136 | ## 绩效图 137 | ![绩效图](attach.png) -------------------------------------------------------------------------------- /attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TruthHun/intertemporal-arbitrage/c7152417282f49c4dc58aaeab5ebde7ce5889274/attach.png -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 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 | try: 6 | import statsmodels.tsa.stattools as ts 7 | except: 8 | print('请安装statsmodels库') 9 | ''' 10 | 本策略根据EG两步法(1.序列同阶单整2.OLS残差平稳)判断序列具有协整关系之后(若无协整关系则全平仓位不进行操作) 11 | 通过计算两个真实价格序列回归残差的0.9个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做多价差 12 | 并在回归至标准差水平内的时候平仓 13 | 回测数据为:SHFE.rb1801和SHFE.rb1805的1min数据 14 | 回测时间为:2017-09-25 08:00:00到2017-10-01 15:00:00 15 | ''' 16 | # 协整检验的函数 17 | def cointegration_test(series01, series02): 18 | urt_rb1801 = ts.adfuller(np.array(series01), 1)[1] 19 | urt_rb1805 = ts.adfuller(np.array(series01), 1)[1] 20 | # 同时平稳或不平稳则差分再次检验 21 | if (urt_rb1801 > 0.1 and urt_rb1805 > 0.1) or (urt_rb1801 < 0.1 and urt_rb1805 < 0.1): 22 | urt_diff_rb1801 = ts.adfuller(np.diff(np.array(series01)), 1)[1] 23 | urt_diff_rb1805 = ts.adfuller(np.diff(np.array(series01), 1))[1] 24 | # 同时差分平稳进行OLS回归的残差平稳检验 25 | if urt_diff_rb1801 < 0.1 and urt_diff_rb1805 < 0.1: 26 | matrix = np.vstack([series02, np.ones(len(series02))]).T 27 | beta, c = np.linalg.lstsq(matrix, series01)[0] 28 | resid = series01 - beta * series02 - c 29 | if ts.adfuller(np.array(resid), 1)[1] > 0.1: 30 | result = 0.0 31 | else: 32 | result = 1.0 33 | return beta, c, resid, result 34 | else: 35 | result = 0.0 36 | return 0.0, 0.0, 0.0, result 37 | else: 38 | result = 0.0 39 | return 0.0, 0.0, 0.0, result 40 | def init(context): 41 | context.goods = ['SHFE.rb1801', 'SHFE.rb1805'] 42 | # 订阅品种 43 | subscribe(symbols=context.goods, frequency='60s', count=801, wait_group=True) 44 | def on_bar(context, bars): 45 | # 获取过去800个60s的收盘价数据 46 | close_01 = context.data(symbol=context.goods[0], frequency='60s', count=801, fields='close')['close'].values 47 | close_02 = context.data(symbol=context.goods[1], frequency='60s', count=801, fields='close')['close'].values 48 | # 展示两个价格序列的协整检验的结果 49 | beta, c, resid, result = cointegration_test(close_01, close_02) 50 | # 如果返回协整检验不通过的结果则全平仓位等待 51 | if not result: 52 | print('协整检验不通过,全平所有仓位') 53 | order_close_all() 54 | return 55 | # 计算残差的标准差上下轨 56 | mean = np.mean(resid) 57 | up = mean + 0.9 * np.std(resid) 58 | down = mean - 0.9 * np.std(resid) 59 | # 计算新残差 60 | resid_new = close_01[-1] - beta * close_02[-1] - c 61 | # 获取rb1801的多空仓位 62 | position_01_long = context.account().position(symbol=context.goods[0], side=PositionSide_Long) 63 | position_01_short = context.account().position(symbol=context.goods[0], side=PositionSide_Short) 64 | if not position_01_long and not position_01_short: 65 | # 上穿上轨时做空新残差 66 | if resid_new > up: 67 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 68 | position_side=PositionSide_Short) 69 | print(context.goods[0] + '以市价单开空仓1手') 70 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 71 | position_side=PositionSide_Long) 72 | print(context.goods[1] + '以市价单开多仓1手') 73 | # 下穿下轨时做多新残差 74 | if resid_new < down: 75 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 76 | position_side=PositionSide_Long) 77 | print(context.goods[0], '以市价单开多仓1手') 78 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 79 | position_side=PositionSide_Short) 80 | print(context.goods[1], '以市价单开空仓1手') 81 | # 新残差回归时平仓 82 | elif position_01_short: 83 | if resid_new <= up: 84 | order_close_all() 85 | print('价格回归,平掉所有仓位') 86 | # 突破下轨反向开仓 87 | if resid_new < down: 88 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 89 | position_side=PositionSide_Long) 90 | print(context.goods[0], '以市价单开多仓1手') 91 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 92 | position_side=PositionSide_Short) 93 | print(context.goods[1], '以市价单开空仓1手') 94 | elif position_01_long: 95 | if resid_new >= down: 96 | order_close_all() 97 | print('价格回归,平所有仓位') 98 | # 突破上轨反向开仓 99 | if resid_new > up: 100 | order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market, 101 | position_side=PositionSide_Short) 102 | print(context.goods[0], '以市价单开空仓1手') 103 | order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market, 104 | position_side=PositionSide_Long) 105 | print(context.goods[1], '以市价单开多仓1手') 106 | if __name__ == '__main__': 107 | ''' 108 | strategy_id策略ID,由系统生成 109 | filename文件名,请与本文件名保持一致 110 | mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST 111 | token绑定计算机的ID,可在系统设置-密钥管理中生成 112 | backtest_start_time回测开始时间 113 | backtest_end_time回测结束时间 114 | backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST 115 | backtest_initial_cash回测初始资金 116 | backtest_commission_ratio回测佣金比例 117 | backtest_slippage_ratio回测滑点比例 118 | ''' 119 | run(strategy_id='strategy_id', 120 | filename='main.py', 121 | mode=MODE_BACKTEST, 122 | token='token_id', 123 | backtest_start_time='2017-09-25 08:00:00', 124 | backtest_end_time='2017-10-01 16:00:00', 125 | backtest_adjust=ADJUST_PREV, 126 | backtest_initial_cash=500000, 127 | backtest_commission_ratio=0.0001, 128 | backtest_slippage_ratio=0.0001) --------------------------------------------------------------------------------