菜油、豆油、棕榈油多品种套利策略(难度:初级)

Table of Contents

跨品种套利是指利用两种不同、但相互关联的资产间的价格差异进行套利交易。跨品种套利的逻辑在于寻找不同品种但具有一定相关性的商品间的相对稳定的关系,以期价差或者价比从偏离区域回到正常区间过程中追逐价差波动的利润。

多品种套利讲解

举例来说,豆粕和菜粕是期货合约中的经典组合套利,它们都可以用作动物饲料来源,所以存在一定的替代关系。然后市场上一般豆粕和菜粕的价格比与其蛋白质含量比会有直接联系,因此量化交易者可以根据历史数据进行分析和推测,来设定关于这个套利组合的自己策略代码,当其中一只合约价格较大偏离了策略设定价格时,便可通过量化交易第一时间进行套利操作。

豆油、棕榈油与菜籽油期货的价格协整

本文策略选择的标的为豆油、棕榈油和菜籽油期货主力合约,让我们先来看一看从18年到现在三种油期货的价格!

可以看到,大部分时候三种油的价格都是接近平行的,至少也是通向运动的,这说明三种油期货的价格是协整的~

换句话说,三种油期货的价差应该是较为稳定的,当价差出现较大变动时,我们便认为其中有期货被高估了或是被低估了,相应的我们便进行做空与做多操作。

下面让我们来看一看具体应该怎么实现~

策略实现

通过价格曲线的局部放大图我们可以看到,由于三条曲线几乎是平行的,就使得三条曲线之间产生了两段间距,但是又由于价格波动还是会有差异,这就使得这两段间距的宽度会发生变化,因此,我们就想到用两段间距的宽度变化来衡量相对价差的扩大和缩小。

在图中,上间距相比下间距来说变大了,因此我们可以认为是菜籽油的价格相对豆油的价格被高估了,于是我们做空菜籽油,做多豆油。基本思路就是这样,相对还是套利思想中很简单的

下面我们要做的,就是创建一个指标,来反映上间距与下间距的相对变化,我们把这个指标叫做index_spread,让我们来看一看index_spread怎么计算吧~

index_spread = ((豆油价 – 棕榈油价) – (菜油价-豆油价))/(菜油价 – 棕榈油价)

# 设置指标计算函数,计算三种合约品种的相对位置
def cal_spread(klines_y, klines_p, klines_oi):
    index_spread = ((klines_y.close - klines_p.close) - (klines_oi.close - klines_oi.close)) / (
            klines_oi.close - klines_p.close)

这个指标表示的是下间距比上间距多出来的百分比,该指标越大,则说明下间距相比上间距大,也就是相对于Y,P被低估,我们就做空Y,做多P;反之则做多Y,做空OI。

让我们来把 index_spread 的指标图打印出来看看,具体是什么样子?

从图中可以看到该指标的波动性还是蛮大的,用来用作判断的指标是不错的选择~

那该怎么用这个指标判断间距的变大和缩小呢?我们用的计算该指标的 5日与15日MA值,来进行判断

开仓逻辑

指数上涨,短期MA上穿长期,则认为相对于y,oi被低估,做多oi,做空y
指数下跌,短期MA下穿长期,则认为相对于y,p被高估,做多y,做空p
如果该指数表现平稳,当前数值大于0.98*短期MA,小于1.02倍短期MA,则平仓赚取利润
# 指数上涨,短期上穿长期,则认为相对于y,oi被低估,做多oi,做空y
if (ma_short.iloc[-2] > ma_long.iloc[-2]) and (ma_short.iloc[-3] < ma_long.iloc[-3]) and (
        index_spread.iloc[-2] > 1.02 * ma_short.iloc[-2]):
    target_pos_y.set_target_volume(-100)
    target_pos_oi.set_target_volume(100)

# 指数下跌,短期下穿长期,则认为相对于y,p被高估,做多y,做空p
elif (ma_short.iloc[-2] < ma_long.iloc[-2]) and (ma_short.iloc[-3] > ma_long.iloc[-3]) and (
        index_spread.iloc[-2] < 0.98 * ma_short.iloc[-2]):
    target_pos_y.set_target_volume(100)
    target_pos_p.set_target_volume(-100)

# 现在策略表现平稳,则平仓,赚取之前策略收益
elif ma_short.iloc[-2] * 0.98 < index_spread.iloc[-2] < ma_long.iloc[-2] * 1.02:
    target_pos_oi.set_target_volume(0)
    target_pos_p.set_target_volume(0)
    target_pos_y.set_target_volume(0)

策略源码

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
__author__ = "Ringo"

"""
豆油、棕榈油、菜油套利策略
注: 该示例策略仅用于功能示范, 实盘时请根据自己的策略/经验进行修改
"""
from tqsdk import TqApi, TargetPosTask
from tqsdk.tafunc import ma

# 设定豆油,菜油,棕榈油指定合约
SYMBOL_Y = "DCE.y2001"
SYMBOL_OI = "CZCE.OI001"
SYMBOL_P = "DCE.p2001"

api = TqApi()

klines_y = api.get_kline_serial(SYMBOL_Y, 24 * 60 * 60)
klines_oi = api.get_kline_serial(SYMBOL_OI, 24 * 60 * 60)
klines_p = api.get_kline_serial(SYMBOL_P, 24 * 60 * 60)

target_pos_oi = TargetPosTask(api, SYMBOL_OI)
target_pos_y = TargetPosTask(api, SYMBOL_Y)
target_pos_p = TargetPosTask(api, SYMBOL_P)


# 设置指标计算函数,计算三种合约品种的相对位置,并将指标画在副图
def cal_spread(klines_y, klines_p, klines_oi):
    index_spread = ((klines_y.close - klines_p.close) - (klines_oi.close - klines_y.close)) / (
            klines_oi.close - klines_p.close)
    klines_y["index_spread"] = index_spread
    ma_short = ma(index_spread, 5)
    ma_long = ma(index_spread, 15)
    return index_spread, ma_short, ma_long


index_spread, ma_short, ma_long = cal_spread(klines_y, klines_p, klines_oi)

klines_y["index_spread.board"] = "index_spread"

print("ma_short是%.2f,ma_long是%.2f,index_spread是%.2f" % (ma_short.iloc[-2], ma_long.iloc[-2], index_spread.iloc[-2]))

while True:
    api.wait_update()
    if api.is_changing(klines_y.iloc[-1], "datetime"):
        index_spread, ma_short, ma_long = cal_spread(klines_y, klines_p, klines_oi)
        print("日线更新,ma_short是%.2f,ma_long是%.2f,index_spread是%.2f" % (
            ma_short.iloc[-2], ma_long.iloc[-2], index_spread.iloc[-2]))

    # 指数上涨,短期上穿长期,则认为相对于y,oi被低估,做多oi,做空y
    if (ma_short.iloc[-2] > ma_long.iloc[-2]) and (ma_short.iloc[-3] < ma_long.iloc[-3]) and (
            index_spread.iloc[-2] > 1.02 * ma_short.iloc[-2]):
        target_pos_y.set_target_volume(-100)
        target_pos_oi.set_target_volume(100)
    # 指数下跌,短期下穿长期,则认为相对于y,p被高估,做多y,做空p
    elif (ma_short.iloc[-2] < ma_long.iloc[-2]) and (ma_short.iloc[-3] > ma_long.iloc[-3]) and (
            index_spread.iloc[-2] < 0.98 * ma_short.iloc[-2]):
        target_pos_y.set_target_volume(100)
        target_pos_p.set_target_volume(-100)
    # 现在策略表现平稳,则平仓,赚取之前策略收益
    elif ma_short.iloc[-2] * 0.98 < index_spread.iloc[-2] < ma_long.iloc[-2] * 1.02:
        target_pos_oi.set_target_volume(0)
        target_pos_p.set_target_volume(0)
        target_pos_y.set_target_volume(0)