市场充满不确定性,投资应谨慎行事,过往的回测结果无法确保未来的业绩。本文档提供的策略实例仅供学习和交流使用,在实际操作前,务必全面理解并进行严格测试。以下代码基于天勤量化平台编写,仅供参考,不对交易结果承担责任。
一、交易策略解析
核心理念
裂解价差套利策略的核心在于迅速发现并利用原油期货与主要衍生产品(如汽油、柴油或取暖油)期货间出现的异常价差进行套利。该策略的基本前提是,尽管原油作为炼化产品的直接原料,两者价格通常维持在一个反映炼油成本和合理利润的稳定比例上,但市场短期内的各种因素,如季节性需求变化、炼油厂意外停运、原油供应中断、地缘政治风险或投机活动,都可能使这一价差暂时偏离其历史平均值或经济合理范围。一旦这种偏离达到一定程度,使价差显得过高(炼油利润异常高)或过低(炼油利润异常低甚至亏损),套利机会就可能出现。
在这种情况下,察觉到不平衡的套利者会立即采取行动,在期货市场建立反向头寸:如果认为价差过大,即成品油价格相对于原油过高,他们会“卖空”裂解价差,即卖出成品油期货(如汽油和柴油),同时买入原油期货,预期成品油价格将相对原油下降,价差缩小;相反,如果认为价差过小,即成品油价格相对于原油过低,他们将“买入”裂解价差,即买入成品油期货,同时卖出原油期货,预期成品油价格将相对原油上升,价差扩大。这一操作的关键在于,套利者不预测单一商品价格的具体涨跌,而是关注原油与炼化产品之间相对价值的变化。通过在金融市场构建一个模拟炼油厂运营的头寸组合——例如,买入原油期货并卖出汽油和柴油期货,就像在纸上“锁定”了炼油过程的潜在利润——目标是在价差从当前的“非正常”状态回归到历史平均水平或更“正常”水平时,通过平仓这些对冲头寸来获利。
理论依据
商品间的长期均衡关系:原油是炼化产品的上游,两者价格在长期内存在经济关联。炼油成本、运输费用、仓储费用及供需关系共同确定了一个合理的裂解价差区间。
市场短期失衡与价差偏离:因季节性需求变化、突发供应中断、投机行为等短期因素,原油和炼化产品的价格可能暂时失衡,导致裂解价差偏离其长期均衡水平。
套利机制与价差回归:当裂解价差过大(意味着炼油利润过高)时,套利者会买入原油期货,卖出炼化产品期货,这会增加对原油的需求,抑制其价格上涨;同时增加炼化产品的供应,抑制其价格下跌,从而缩小裂解价差。反之,当裂解价差过小时,套利者会卖出原油期货,买入炼化产品期货,促使价差扩大。这种套利行为有助于将价差恢复到其正常波动范围。
策略适用情境
历史价差波动稳定且存在均值回归特性:若历史数据显示特定原油和炼化产品组合的裂解价差在一定范围内波动,并显示出明显的均值回归趋势,则进行套利的成功率较高。
季节性需求变化显著:如Investopedia文章所指出,夏季汽油需求高涨,冬季取暖油需求增加。这些季节性因素可能导致裂解价差在特定时期内出现规律性的扩大或缩小,为套利者提供机会。
炼油产能或库存出现意外变化:炼油厂的计划外停运、维修延期,或原油及炼化产品库存的异常波动,都可能导致短期供需失衡,使裂解价差偏离正常水平。
地缘政治或自然灾害影响原油或炼化产品供应:此类突发事件可能导致原油和炼化产品价格出现不同程度的波动,从而产生套利机会。
期货合约流动性良好:由于套利涉及同时交易多个期货合约,因此相关原油和炼化产品期货合约必须具备足够的流动性,以确保能以合理成本迅速建仓和平仓。
二、天勤平台简介
天勤平台概述
天勤(TqSdk)是由信易科技开发的一款开源量化交易平台,专为期货、期权等衍生品交易提供专业的量化交易解决方案。平台特点包括:
- 丰富的行情数据:提供所有可交易合约的完整Tick和K线数据,基于内存数据库实现零延迟访问。
- 一站式的解决方案:涵盖从历史数据分析到实盘交易的完整工具链,实现开发、回测、模拟到实盘的全流程贯通。
- 专业的技术支持:提供近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型确保性能。
策略开发流程
环境准备
- 安装Python环境(建议使用Python 3.6或更高版本)
- 安装tqsdk包:pip install tqsdk
- 注册天勤账号以获取访问密钥
数据准备
- 订阅近期和远期合约的行情信息
- 获取历史K线或Tick数据,用于分析和行情推进
策略编写
- 设计信号生成逻辑(基于价差、均值和标准差)
- 编写交易执行模块(包括开仓和平仓逻辑)
- 实现风险管理措施(如止损和资金管理)
回测验证
- 设定回测的时间区间和初始资金
- 运行策略以获取回测结果
- 分析绩效指标(如胜率、收益率、夏普比率等)
策略优化
- 调整参数(如标准差倍数、窗口大小等)
- 增加过滤条件(如成交量、波动率等)
- 完善风险管理机制
三、天勤策略实现
策略原理
...
该裂解价差套利策略依赖于原油及其衍生品(如汽油和柴油)之间价格关系的异常来实施交易。以下是具体的计算步骤:
- 裂解价差计算:
采用以下公式:裂解价差 = (汽油价格 + 柴油价格) - 原油价格。
此计算中包括了合约乘数及比例设定(2:1:1),确保了价差能准确反映市场实际情况。
- 统计指标构建:
首先,收集过去30天的裂解价差数据;接着,计算这些数据的平均值(mean_spread)与标准差(std_spread)。
最后,将当前裂解价差标准化为Z分数:(current_spread - mean_spread) / std_spread。Z分数反映了当前价差偏离历史平均水平的程度,是策略中的关键指标。
- 价差均值回归假设:
该策略假定裂解价差在长期内会趋向于其历史平均值。因此,当裂解价差明显偏离这一平均值(超过或低于1.5个标准差)时,被视为市场暂时失衡,提供了套利机会。
交易逻辑
开仓信号
- 卖出裂解价差 (当Z分数 > 1.5):
表明当前裂解价差偏高,预计会回落至平均值。
操作建议:购买原油,同时出售汽油和柴油。
理由:若成品油价格相对于原油过高,则通过购买原材料并出售成品获利。
- 买入裂解价差 (当Z分数 < -1.5):
表明当前裂解价差偏低,预计会上升至平均值。
操作建议:出售原油,同时购买汽油和柴油。
理由:若成品油价格相对于原油过低,则通过出售原材料并购买成品获利。
平仓信号
当|Z分数| < 0.3时,即价差已回归到接近历史平均值的水平,应平仓所有头寸。因为此时套利机会已不复存在,可以锁定利润。
回测
为了验证该策略的有效性,进行了以下回测:
- 测试时间段:2024年1月26日至2024年3月1日
- 交易品种:INE.sc2405、SHFE.fu2405、INE.nr2405
- 初始资金:1000万元
回测结果如下图所示:

以上图表展示了回测期间的累计收益走势:

完整的代码示例如下:
#!/usr/bin/env python
# coding=utf-8
__author__ = "Chaos"
from datetime import date
from tqsdk import TqApi, TqAuth, TargetPosTask, TqBacktest, BacktestFinished
import numpy as np
import time
# === 用户参数 ===
# 合约参数
CRUDE_OIL = "INE.sc2405" # 原油期货合约
GASOLINE = "SHFE.fu2405" # 燃料油期货合约
DIESEL = "INE.nr2405" # 柴油期货合约
START_DATE = date(2024, 1, 26) # 回测开始日期
END_DATE = date(2024, 3, 1) # 回测结束日期
# 套利参数
LOOKBACK_DAYS = 30 # 计算历史价差的回溯天数
STD_THRESHOLD = 1.5 # 标准差阈值,超过此阈值视为套利机会
ORDER_VOLUME = 30 # 下单手数
CLOSE_THRESHOLD = 0.3 # 平仓阈值(标准差)
# 裂解价差比例
CRUDE_RATIO = 2
GASOLINE_RATIO = 1
DIESEL_RATIO = 1
# === 初始化API ===
api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE),
auth=TqAuth("快期账号", "快期密码"))
# 获取合约行情和K线
crude_quote = api.get_quote(CRUDE_OIL)
gasoline_quote = api.get_quote(GASOLINE)
diesel_quote = api.get_quote(DIESEL)
crude_klines = api.get_kline_serial(CRUDE_OIL, 60*60*24, LOOKBACK_DAYS)
gasoline_klines = api.get_kline_serial(GASOLINE, 60*60*24, LOOKBACK_DAYS)
diesel_klines = api.get_kline_serial(DIESEL, 60*60*24, LOOKBACK_DAYS)
# 创建目标持仓任务
crude_pos = TargetPosTask(api, CRUDE_OIL)
gasoline_pos = TargetPosTask(api, GASOLINE)
diesel_pos = TargetPosTask(api, DIESEL)
# 获取合约乘数
crude_volume_multiple = crude_quote.volume_multiple
gasoline_volume_multiple = gasoline_quote.volume_multiple
diesel_volume_multiple = diesel_quote.volume_multiple
# 初始化状态变量
position_time = 0 # 建仓时间
in_position = False # 是否有持仓
mean_spread = 0 # 历史价差均值
std_spread = 0 # 历史价差标准差
print(f"策略启动,监控合约: {CRUDE_OIL}, {GASOLINE}, {DIESEL}")
# === 主循环 ===
try:
# 初始计算历史统计值
spreads = []
for i in range(len(crude_klines) - 1):
crude_price = crude_klines.close.iloc[i] * crude_volume_multiple * CRUDE_RATIO
gasoline_price = gasoline_klines.close.iloc[i] * gasoline_volume_multiple * GASOLINE_RATIO
diesel_price = diesel_klines.close.iloc[i] * diesel_volume_multiple * DIESEL_RATIO
spread = (gasoline_price + diesel_price) - crude_price
spreads.append(spread)
mean_spread = np.mean(spreads)
std_spread = np.std(spreads)
print(f"历史裂解价差均值: {mean_spread:.2f}, 标准差: {std_spread:.2f}")
# 主循环
while True:
api.wait_update()
# 当K线数据有变化时进行计算
if api.is_changing(crude_klines) or api.is_changing(gasoline_klines) or api.is_changing(diesel_klines):
# 重新计算历史价差统计
spreads = []
for i in range(len(crude_klines) - 1):
crude_price = crude_klines.close.iloc[i] * crude_volume_multiple * CRUDE_RATIO
gasoline_price = gasoline_klines.close.iloc[i] * gasoline_volume_multiple * GASOLINE_RATIO
diesel_price = diesel_klines.close.iloc[i] * diesel_volume_multiple * DIESEL_RATIO
spread = (gasoline_price + diesel_price) - crude_price
spreads.append(spread)
mean_spread = np.mean(spreads)
std_spread = np.std(spreads)
# 计算当前裂解价差
crude_price = crude_klines.close.iloc[-1] * crude_volume_multiple * CRUDE_RATIO
gasoline_price = gasoline_klines.close.iloc[-1] * gasoline_volume_multiple * GASOLINE_RATIO
diesel_price = diesel_klines.close.iloc[-1] * diesel_volume_multiple * DIESEL_RATIO
current_spread = (gasoline_price + diesel_price) - crude_price
# 计算z-score (标准化的价差)
z_score = (current_spread - mean_spread) / std_spread
print(f"当前裂解价差: {current_spread:.2f}, Z-score: {z_score:.2f}")
# 获取当前持仓
crude_position = api.get_position(CRUDE_OIL)
gasoline_position = api.get_position(GASOLINE)
diesel_position = api.get_position(DIESEL)
current_crude_pos = crude_position.pos_long - crude_position.pos_short
current_gasoline_pos = gasoline_position.pos_long - gasoline_position.pos_short
current_diesel_pos = diesel_position.pos_long - diesel_position.pos_short
# === 交易信号判断 ===
if not in_position: # 如果没有持仓
if z_score > STD_THRESHOLD: # 价差显著高于均值
# 卖出裂解价差:买入原油,卖出汽油和柴油
print("卖出裂解价差:买入原油,卖出汽油和柴油")
crude_pos.set_target_volume(ORDER_VOLUME)
gasoline_pos.set_target_volume(-ORDER_VOLUME)
diesel_pos.set_target_volume(-ORDER_VOLUME)
position_time = time.time()
in_position = True
elif z_score < -STD_THRESHOLD: # 价差显著低于均值
# 买入裂解价差:卖出原油,买入汽油和柴油
print("买入裂解价差:卖出原油,买入汽油和柴油")
crude_pos.set_target_volume(-ORDER_VOLUME)
gasoline_pos.set_target_volume(ORDER_VOLUME)
diesel_pos.set_target_volume(ORDER_VOLUME)
position_time = time.time()
in_position = True
else: # 如果已有持仓
# 检查是否应当平仓
if abs(z_score) < CLOSE_THRESHOLD: # 价差恢复正常
print("价差恢复正常,平仓所有头寸")
crude_pos.set_target_volume(0)
gasoline_pos.set_target_volume(0)
diesel_pos.set_target_volume(0)
in_position = False
except BacktestFinished as e:
print("回测结束")
api.close()

雷达卡


京公网安备 11010802022788号







