【期货策略】经典的跨期套利策略

根据经典的配对交易原理,只要两只标的的价差格序列满足平稳性,那么这两个标的可以构造成一个很好的配对交易对。从股票之中找这样的股票对在之前写的策略中有实现——链接在这统计套利(二),利用协整关系进行配对交易    ,那么利用不同期限到期的股指期货会不会更有效的,因为不同期限的股指期货的标的都是同一个指数,根据套利定价理论,他们的必要收益率应该相差不大,因为不同期限的合约除了需要承受的时间风险更多其他都是相同的,假设全体股市这一整体是服从维纳过程,他的漂移项就应为无风险收益——利率

那么自然时间更长的合约的按照利率折现后会比时间更短的合约多出时间的风险溢价。长期来看风险溢价是一个稳定水平,那么他们的价格波动自然会趋于一致性。所以很自然构建出利用跨期套利策略,差价绝对值高于一定水平就开仓,差价收敛到平均水平就平仓。

下面就是策略具体实现了,我们先选择适合作为跨期套利的合约,利用协整性检验,观察得到的p value

可以看到IF与IH的合约的p value都比较高,都不能拒绝原假设——序列不是协整的,但是IC合约具有很低的p value 可以作为我们跨期套利的合约。 在策略中,为避免极端情况下,差价平稳性实效导致严重亏损,在设定的时间窗口的长度上再去检验当月合约与下月合约在这段时间内是否是具有协整的,如满足协整形才进行开仓操作,但是即使不满足也可平仓,因为在极端行情时价格就不满足我们构建的模型了,所以此时还是尽早出场为妙。为避免价差持续扩大造成亏损加重,设置一个止损的临界值,为1.1倍标准差。

在构建的这样的portfolio下,是对于价格的一次差分进行建模,相比于对价格趋势建模更容易,在维度上也实现了降维操作,也就无惧市场是晴是阴,只需考虑市场是否还有效。

 

# 可以自己import我们平台支持的第三方python模块,比如pandas、numpy等。
import numpy as np
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
# 获取当前可以交易IC合约
contract=get_future_contracts(‘IC’)
# 取得当月主力合约与下月合约代码
context.s1=contract[0]
context.s2=contract[1]
# 设置回测的保证金率为20%
context.marin_rate = 20
# 无滑点影响
context.slippage = 0
# 设置佣金费率为万分之1
context.commission = 0.01
# 设置全局计数器
context.counter = 0
# 设置滚动窗口
context.window = 75
# 初始化跨期合约比列参数
context.ratio = 1
# 判断开仓时的差价是从上往下还是从下往上
context.up_cross_up_limit = False
context.down_cross_down_limit = False
# 初始化协整判断指示器
context.spread_coin=False
# 设置入场临界值
context.entry_score = 1
# 设置止损临界值
context.out=1.1
# 初始化时订阅合约行情。订阅之后的合约行情会在handle_bar中进行更新
subscribe([context.s1, context.s2])

# before_night_trading此函数会在每天夜盘交易开始前被调用,当天只会被调用一次
def before_night_trading(context):
# 样例商品期货在回测区间内有夜盘交易,所以在每日开盘前将计数器清零
contract=get_future_contracts(‘IC’)
context.s1=contract[0]
context.s2=contract[1]
# 初始化时订阅合约行情。订阅之后的合约行情会在handle_bar中进行更新
subscribe([context.s1, context.s2])
# 计数器每日归零
context.counter = 0

# 你选择的期货数据更新将会触发此段逻辑,例如日线或分钟线更新
def handle_bar(context, bar_dict):

# 获取当前一对合约的仓位情况。如尚未有仓位,则对应持仓量都为0

position_a = context.portfolio.positions[context.s1]
position_b = context.portfolio.positions[context.s2]

context.counter += 1
# 当累积满一定数量的bar数据时候,进行交易逻辑的判断
if context.counter > context.window:

# 获取当天历史分钟线价格队列
price_array_a = history_bars(context.s1, context.window, ‘1m’, ‘close’)
price_array_b = history_bars(context.s2, context.window, ‘1m’, ‘close’)
price_array_b_const = sm.add_constant(price_array_b)
# 进行最小二乘回归
result = (sm.OLS(price_array_b_const,price_array_a)).fit()
# 取得回归函数的常数项
params=result.params[0]
# 取得回归函数的参数项
context.ratio=params[1]
# 检验在时间窗口的长度下合约序列是否为协整
result = sm.tsa.stattools.coint(price_array_a, price_array_b)
# 取出并记录p值
pvalue = result[1]
if pvalue<0.05:
context.spread_coin=True

# 计算价差序列、其标准差、均值、上限、下限、上退场限、下退场限
spread_array = price_array_b – context.ratio * price_array_a
std = np.std(spread_array)
mean = np.mean(spread_array)

up_limit = mean + context.entry_score *std
down_limit = mean – context.entry_score *std
up_out_limt=mean+context.out*std
down_out_limt=mean-context.out*std

# 获取当前bar对应合约的收盘价格并计算价差
price_a = bar_dict[context.s1].close
price_b = bar_dict[context.s2].close
# 计算回归得到的合约对的差价
spread = price_b – context.ratio * price_a

# 如果价差低于预先计算得到的下限,则为建仓信号,’买入’价差合约
if spread <= down_limit and not context.down_cross_down_limit and context.spread_coin:
# 可以通过logger打印日志
logger.info(‘spread: {}, mean: {}, down_limit: {}’.format(spread, mean, down_limit))
logger.info(‘创建买入价差中…’)

# 获取当前剩余的应建仓的数量
qty_a = 1 – position_a.buy_quantity

qty_b =1 – position_b.sell_quantity

# 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
if qty_a > 0:
buy_open(context.s2, qty_a)
if qty_b > 0:
sell_open(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
# 已成功建立价差的’多仓’
context.down_cross_down_limit = True
logger.info(‘买入价差仓位创建成功!’)

# 如果价差向上回归移动平均线,则为平仓信号
if spread >= mean and context.down_cross_down_limit:
logger.info(‘spread: {}, mean: {}, down_limit: {}’.format(spread, mean, down_limit))
logger.info(‘对买入价差仓位进行平仓操作中…’)

# 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
qty_a = position_a.buy_quantity
qty_b = position_b.sell_quantity
if qty_a > 0:
sell_close(context.s2, qty_a)
if qty_b > 0:
buy_close(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
context.down_cross_down_limit = False
logger.info(‘买入价差仓位平仓成功!’)

if spread <= down_out_limt :
logger.info(‘spread: {}, mean: {}, down_limit: {}’.format(spread, mean, down_limit))
logger.info(‘对买入价差仓位进行平仓操作中…’)

# 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
qty_a = position_a.buy_quantity
qty_b = position_b.sell_quantity
if qty_a > 0:
sell_close(context.s2, qty_a)
if qty_b > 0:
buy_close(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
context.down_cross_down_limit = False
logger.info(‘买入价差仓位平仓成功!’)

# 如果价差高于预先计算得到的上限,则为建仓信号,’卖出’价差合约
if spread >= up_limit and not context.up_cross_up_limit and context.spread_coin:
logger.info(‘spread: {}, mean: {}, up_limit: {}’.format(spread, mean, up_limit))
logger.info(‘创建卖出价差中…’)
qty_a = 1 – position_a.sell_quantity
qty_b = 1- position_b.buy_quantity
if qty_a > 0:
sell_open(context.s2, qty_a)
if qty_b > 0:
buy_open(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
context.up_cross_up_limit = True
logger.info(‘卖出价差仓位创建成功’)

if spread >= up_out_limt :
logger.info(‘spread: {}, mean: {}, down_limit: {}’.format(spread, mean, down_limit))
logger.info(‘对买入价差仓位进行平仓操作中…’)

# 由于存在成交不超过下一bar成交量25%的限制,所以可能要通过多次发单成交才能够成功建仓
qty_a = position_a.buy_quantity
qty_b = position_b.sell_quantity
if qty_a > 0:
sell_close(context.s2, qty_a)
if qty_b > 0:
buy_close(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
context.down_cross_down_limit = False
logger.info(‘买入价差仓位平仓成功!’)

# 如果价差向下回归移动平均线,则为平仓信号
if spread <= mean and context.up_cross_up_limit:
logger.info(‘spread: {}, mean: {}, up_limit: {}’.format(spread, mean, up_limit))
logger.info(‘对卖出价差仓位进行平仓操作中…’)
qty_a = position_a.sell_quantity
qty_b = position_b.buy_quantity
if qty_a > 0:
buy_close(context.s2, qty_a)
if qty_b > 0:
sell_close(context.s1, qty_b)
if qty_a == 0 and qty_b == 0:
context.up_cross_up_limit = False
logger.info(‘卖出价差仓位平仓成功!’)