PTA产业链套利策略
一、交易策略解释
核心思想
PTA产业链套利的核心在于监控和交易“涤纶短纤生产利润价差”。涤纶短纤(Polyester Staple Fiber, PF)的主要原料是精对苯二甲酸(PTA)和乙二醇(EG)。理论上,生产1吨涤纶短纤大约需要0.855-0.87吨的PTA和0.335-0.34吨的EG(具体比例因工艺和产品规格略有差异)。
- 涤纶短纤生产利润价差 (PF Production Margin Spread / Polyester Spread): 利润价差 = 涤纶短纤价值 - (PTA成本价值 + 乙二醇成本价值)
核心思想是:
- 这个生产利润价差会受到涤纶短纤、PTA、乙二醇各自供需基本面以及聚酯行业整体状况的影响,但通常会围绕一个由生产成本、合理利润和市场预期构成的“均衡水平”波动。
- 做多生产利润 (Long the Polyester Spread / "Buying the Margin"): 当交易者认为当前市场计算出的生产利润过低,未来有望扩大时,他们会买入涤纶短纤期货,同时卖出PTA期货和乙二醇期货。这在金融市场上模拟了聚酯工厂锁定未来更高加工利润的行为(或对冲当前低利润风险)。
- 做空生产利润 (Short the Polyester Spread / "Selling the Margin"): 当交易者认为当前市场计算出的生产利润过高,未来可能收窄时,他们会卖出涤纶短纤期货,同时买入PTA期货和乙二醇期货。这模拟了聚酯工厂锁定当前较高加工利润的行为。
该策略预期当此价差显著偏离其历史均值或行业盈亏平衡点时,会向其“正常”水平回归。
理论基础
-
产业链的经济联系: 涤纶短纤是PTA和乙二醇的主要下游产品之一。它们之间存在紧密的上下游价格传导和成本利润关系。PTA自身的主要原料是PX(对二甲苯),PX价格的波动会影响PTA成本,进而传导至涤纶短纤。
-
供需基本面的驱动:
- 涤纶短纤端 (PF): 纺织服装行业景气度、出口订单、自身产能利用率、库存水平、替代品(如棉花)价格等。
- PTA端 (TA): PX价格(主要成本)、PTA装置开工率、新增产能、下游聚酯(包括涤纶长丝、瓶片等)需求、库存等。
- 乙二醇端 (EG): 原料路线(石脑油、乙烯、煤制等)的成本、国内外装置开工率、港口库存、进口量、下游聚酯需求等。
- 这些因素的变化导致三者价格波动,进而影响涤纶短纤的生产利润价差。
-
均值回归特性 (Mean Reversion): 历史数据显示,聚酯产品的生产利润价差虽然波动,但往往具有均值回归的特性。当利润过高时,可能刺激聚酯工厂提高开工率或新建产能,未来PF供应增加将压低PF价格,同时增加对TA和EG的需求可能推高其价格,从而压缩利润。当利润过低甚至亏损时,工厂可能减产检修,未来PF供应减少将推升PF价格,同时减少对TA和EG的需求可能压低其价格,从而修复利润。
-
企业套期保值行为: 聚酯生产企业、PTA和EG生产及贸易企业会利用期货市场进行套期保值。聚酯厂可能在远期生产利润可观时卖出PF期货、买入TA和EG期货(锁定利润)。这些行为本身也会影响价差的动态。
策略适用场景
- 生产利润价差显著偏离: 当计算出的涤纶短纤生产利润(经过适当的比例和标准化处理后)显著高于或低于其历史均值或关键的盈亏平衡点时。
- 预期均值回归: 交易者基于对市场周期或短期供需失衡的判断,预期价差将向更可持续的水平回归。
- 基本面分析支持: 结合对PF、TA、EG各自基本面的分析,如果基本面也支持价差向预期方向回归,则策略的可靠性更高。
- 流动性充足: 策略涉及三个品种的期货合约(郑州商品交易所的TA和PF,大连商品交易所的EG),需要保证各合约均有良好的流动性。
二、天勤介绍
天勤平台概述
天勤(TqSdk)是一个由信易科技开发的开源量化交易系统,为期货、期权等衍生品交易提供专业的量化交易解决方案。平台具有以下特 点:
- 丰富的行情数据: 提供所有可交易合约的全部Tick和K线数据,基于内存数据库实现零延迟访问。
- 一站式的解决方案: 从历史数据分析到实盘交易的完整工具链,打通开发、回测、模拟到实盘的全流程。
- 专业的技术支持: 近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型保证性能。
策略开发流程
- 环境准备
- 安装Python环境(推荐Python 3.6或以上版本)
- 安装tqsdk包:pip install tqsdk
- 注册天勤账户获取访问密钥
- 数据准备
- 订阅近月与远月合约行情
- 获取历史K线或者Tick数据(用于分析与行情推进)
- 策略编写
- 设计信号生成逻辑(基于价差、均值和标准差)
- 编写交易执行模块(开仓、平仓逻辑)
- 实现风险控制措施(止损、资金管理)
- 回测验证
- 设置回测时间区间和初始资金
- 运行策略获取回测结果
- 分析绩效指标(胜率、收益率、夏普率等)
- 策略优化
- 调整参数(标准差倍数、窗口大小等)
- 添加过滤条件(成交量、波动率等)
- 完善风险控制机制
三、天勤实现策略
策略原理
核心价差定义与计算(涤纶短纤生产利润价差):
该策略的核心是追踪一个根据涤纶短纤(PF)生产中原料(PTA和乙二醇EG)的理论消耗比例,并结合各期货合约价值构建的“生产利润价差”。这个价差的计算方式如下:
-
首先,为涤纶短纤、PTA和乙二醇期货分别设定一个代表其在生产关系中相对量的比例因子(例如,生产1单位涤纶短纤需要0.86单位PTA和0.34单位乙二醇)。
-
然后,将各期货品种的最新收盘价乘以其对应的合约乘数(代表每手合约的价值规模),再乘以各自的生产比例因子,得到它们在价差组合中的调整后价值。
-
涤纶短纤生产利润价差被定义为:涤纶短纤期货的调整后价值,减去PTA期货和乙二醇期货调整后价值的总和。 这个计算出的价差,反映了按照预设生产配比模拟的“原料成本”与“成品价值”之间的理论利润空间。
-
指标构建:
策略采用标准化得分(Z-score) 来衡量当前计算出的“涤纶短纤生产利润价差”与其近期历史水平的偏离程度。
- 历史价差序列: 收集特定涤纶短纤、PTA、乙二醇期货合约在过去一段固定交易日(例如30天)内的每日收盘价。基于这些价格和上述价差计算方法,得到一个每日“涤纶短纤生产利润价差”的历史序列。
- 计算历史均值: 对这个历史价差序列计算其算术平均值。
- 计算历史标准差: 对同一个历史价差序列计算其标准差。
- 计算标准化得分: 用最新的每日收盘价计算出当前的“涤纶短纤生产利润价差”,然后通过以下公式得到其标准化得分: 标准化得分 = (当前涤纶短纤生产利润价差 - 历史均值) / 历史标准差 (若历史标准差为零,则此得分无意义或需特殊处理)。
交易逻辑
开仓信号 (入场):
-
预期利润缩小(做空生产利润价差):
- 信号: 当计算出的“标准化得分”显著高于一个预设的正阈值(例如,大于2个标准差)。这表明当前的生产利润远高于其历史平均水平,策略预期其会向均值回落。
- 操作: 卖出指定数量的涤纶短纤期货,同时买入相应比例数量的PTA期货和乙二醇期货。
-
预期利润扩大(做多生产利润价差):
- 信号: 当计算出的“标准化得分”显著低于一个预设的负阈值(例如,小于负2个标准差)。这表明当前的生产利润远低于其历史平均水平,策略预期其会向均值回升。
- 操作: 买入指定数量的涤纶短纤期货,同时卖出相应比例数量的PTA期货和乙二醇期货。
平仓信号 (止盈离场):
- 信号: 当“标准化得分”的绝对值回落到一个较小的预设阈值以内(例如,小于0.5个标准差)。这表示生产利润价差已经回归到接近其历史平均的水平。
- 操作: 将所有持有的期货头寸全部平掉(即目标持仓设为零)。
特定条件下的平仓信号 (类似止损或极端行情退出): 当策略当前已持有头寸时,若出现以下情况也会触发平仓:
-
信号:
- 若最初是因利润过低而做多(涤纶短纤多头,原料空头),之后利润不仅未按预期回升,反而大幅反向运动至显著高于历史均值(例如,标准化得分超过一个更高的正阈值,如3个标准差)。
- 若最初是因利润过高而做空(涤纶短纤空头,原料多头),之后利润不仅未按预期回落,反而大幅反向运动至显著低于历史均值(例如,标准化得分低于一个更低的负阈值,如负3个标准差)。
-
操作: 与止盈操作类似,立即平掉所有持有的期货头寸。
回测
回测初始设置
- 测试周期: 2024 年 2 月 1 日 - 2024 年 3 月 13 日
- 交易品种: CZCE.PF409/CZCE.TA409/DCE.eg2409
- 初始资金: 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
# === 用户参数 ===
# 合约参数
PF = "CZCE.PF409" # 涤纶短纤期货合约
PTA = "CZCE.TA409" # PTA期货合约
EG = "DCE.eg2409" # 乙二醇期货合约
START_DATE = date(2024, 2, 1) # 回测开始日期
END_DATE = date(2024, 4, 30) # 回测结束日期
# 套利参数
LOOKBACK_DAYS = 30 # 计算历史价差的回溯天数
STD_THRESHOLD = 2.0 # 标准差阈值,超过此阈值视为套利机会
ORDER_VOLUME = 500 # 涤纶短纤的下单手数
CLOSE_THRESHOLD = 0.5 # 平仓阈值(标准差)
# 生产比例(可根据实际工艺调整)
PF_RATIO = 1
PTA_RATIO = 0.86
EG_RATIO = 0.34
# === 初始化API ===
api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE),
auth=TqAuth("快期账号", "快期密码"))
# 获取合约行情和K线
pf_quote = api.get_quote(PF)
pta_quote = api.get_quote(PTA)
eg_quote = api.get_quote(EG)
pf_klines = api.get_kline_serial(PF, 60*60*24, LOOKBACK_DAYS)
pta_klines = api.get_kline_serial(PTA, 60*60*24, LOOKBACK_DAYS)
eg_klines = api.get_kline_serial(EG, 60*60*24, LOOKBACK_DAYS)
# 创建目标持仓任务
pf_pos = TargetPosTask(api, PF)
pta_pos = TargetPosTask(api, PTA)
eg_pos = TargetPosTask(api, EG)
# 获取合约乘数
pf_volume_multiple = pf_quote.volume_multiple
pta_volume_multiple = pta_quote.volume_multiple
eg_volume_multiple = eg_quote.volume_multiple
# 初始化状态变量
position_time = 0 # 建仓时间
in_position = False # 是否有持仓
mean_spread = 0 # 历史价差均值
std_spread = 0 # 历史价差标准差
print(f"策略启动,监控合约: {PF}, {PTA}, {EG}")
# === 主循环 ===
try:
# 初始计算历史统计值
spreads = []
for i in range(len(pf_klines) - 1):
pf_price = pf_klines.close.iloc[i] * pf_volume_multiple * PF_RATIO
pta_price = pta_klines.close.iloc[i] * pta_volume_multiple * PTA_RATIO
eg_price = eg_klines.close.iloc[i] * eg_volume_multiple * EG_RATIO
# 涤纶短纤生产利润 = 涤纶短纤价值 - (PTA成本 + EG成本)
spread = pf_price - (pta_price + eg_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(pf_klines) or api.is_changing(pta_klines) or api.is_changing(eg_klines):
# 重新计算历史价差统计
spreads = []
for i in range(len(pf_klines) - 1):
pf_price = pf_klines.close.iloc[i] * pf_volume_multiple * PF_RATIO
pta_price = pta_klines.close.iloc[i] * pta_volume_multiple * PTA_RATIO
eg_price = eg_klines.close.iloc[i] * eg_volume_multiple * EG_RATIO
spread = pf_price - (pta_price + eg_price)
spreads.append(spread)
mean_spread = np.mean(spreads)
std_spread = np.std(spreads)
# 计算当前利润价差
pf_price = pf_klines.close.iloc[-1] * pf_volume_multiple * PF_RATIO
pta_price = pta_klines.close.iloc[-1] * pta_volume_multiple * PTA_RATIO
eg_price = eg_klines.close.iloc[-1] * eg_volume_multiple * EG_RATIO
current_spread = pf_price - (pta_price + eg_price)
# 计算z-score (标准化的价差)
z_score = (current_spread - mean_spread) / std_spread
print(f"当前涤纶短纤利润: {current_spread:.2f}, Z-score: {z_score:.2f}")
# 获取当前持仓
pf_position = api.get_position(PF)
pta_position = api.get_position(PTA)
eg_position = api.get_position(EG)
current_pf_pos = pf_position.pos_long - pf_position.pos_short
current_pta_pos = pta_position.pos_long - pta_position.pos_short
current_eg_pos = eg_position.pos_long - eg_position.pos_short
# 计算实际下单手数(依据比例)
pta_volume = int(ORDER_VOLUME * PTA_RATIO / PF_RATIO)
eg_volume = int(ORDER_VOLUME * EG_RATIO / PF_RATIO)
# === 交易信号判断 ===
if not in_position: # 如果没有持仓
if z_score > STD_THRESHOLD: # 利润显著高于均值
# 做空利润:卖出PF,买入PTA和EG
print(f"做空利润:卖出PF{ORDER_VOLUME}手,买入PTA{pta_volume}手和EG{eg_volume}手")
pf_pos.set_target_volume(-ORDER_VOLUME)
pta_pos.set_target_volume(pta_volume)
eg_pos.set_target_volume(eg_volume)
position_time = time.time()
in_position = True
elif z_score < -STD_THRESHOLD: # 利润显著低于均值
# 做多利润:买入PF,卖出PTA和EG
print(f"做多利润:买入PF{ORDER_VOLUME}手,卖出PTA{pta_volume}手和EG{eg_volume}手")
pf_pos.set_target_volume(ORDER_VOLUME)
pta_pos.set_target_volume(-pta_volume)
eg_pos.set_target_volume(-eg_volume)
position_time = time.time()
in_position = True
else: # 如果已有持仓
# 检查是否应当平仓
if abs(z_score) < CLOSE_THRESHOLD: # 利润回归正常
print("利润回归正常,平仓所有头寸")
pf_pos.set_target_volume(0)
pta_pos.set_target_volume(0)
eg_pos.set_target_volume(0)
in_position = False
# 止损逻辑
if (z_score > STD_THRESHOLD * 1.5 and current_pf_pos > 0) or \
(z_score < -STD_THRESHOLD * 1.5 and current_pf_pos < 0):
print("止损:利润向不利方向进一步偏离")
pf_pos.set_target_volume(0)
pta_pos.set_target_volume(0)
eg_pos.set_target_volume(0)
in_position = False
except BacktestFinished as e:
print("回测结束")
api.close()