玻璃-纯碱套利(化工产业链)策略

一、交易策略解释

核心思想

玻璃-纯碱套利的核心在于监控和交易“玻璃生产利润价差”。纯碱(SA)是生产玻璃(FG)的主要化工原料之一,通常生产1吨玻璃大约需要消耗0.2吨纯碱(具体消耗系数因玻璃种类、生产工艺等因素而异)。

  • 玻璃生产利润价差 (Glass Production Profit Spread):
    • 该价差代表了在期货市场上,基于特定手数配比的玻璃期货价值与相应生产所需的纯碱期货价值之间的差额。例如,可以构建一个基于5手玻璃(100吨)和1手纯碱(20吨,近似满足0.2的消耗比,因为玻璃和纯碱合约单位均为20吨/手)的价差组合。
    • 价差 = (玻璃期货价格 × 玻璃合约乘数 × 玻璃手数因子) - (纯碱期货价格 × 纯碱合约乘数 × 纯碱手数因子)

核心思想是:

  • 这个玻璃生产利润价差会受到玻璃、纯碱各自供需基本面以及房地产、汽车等下游行业整体状况的影响。策略通过计算该价差的标准化得分(Z-score)来衡量其偏离历史均值的程度。
  • 预期价差扩大(做多玻璃生产利润): 当玻璃生产利润价差的Z-score显著为负(例如,低于一个设定的负阈值),表明当前(期货市场反映的)玻璃生产利润远低于其历史平均水平。策略预期此价差会向均值回升(即利润改善),因此会买入玻璃期货,同时卖出相应配比的纯碱期货。
  • 预期价差缩小(做空玻璃生产利润): 当玻璃生产利润价差的Z-score显著为正(例如,高于一个设定的正阈值),表明当前玻璃生产利润远高于其历史平均水平。策略预期此价差会向均值回落(即利润压缩),因此会卖出玻璃期货,同时买入相应配比的纯碱期货。
  • 该策略的平仓逻辑则基于价差(或其Z-score)向其历史均值区域回归。即,入场后,当价差的偏离程度减小,Z-score的绝对值回落到某个较小的阈值内时,头寸会被平掉。

理论基础

  • 产业链的直接联系: 纯碱是玻璃生产不可或缺的原料,其成本是影响玻璃生产企业利润的关键因素之一。

  • 供需基本面的驱动:

    • 纯碱端 (SA): 国内纯碱产能(包括新增、检修、淘汰情况)、开工率、库存水平、下游需求(玻璃、氧化铝、洗涤剂等行业)、进出口情况等。
    • 玻璃端 (FG): 玻璃生产线(浮法玻璃)的冷修、复产情况、产能利用率、厂家库存、下游需求(主要是房地产竣工和新开工、汽车产销)、季节性因素等。
    • 这些因素的变化导致两者价格波动,进而影响玻璃生产利润价差。
  • 均值回归特性 (Mean Reversion of Production Profit): 历史数据显示,玻璃生产利润(无论是现货还是期货价差)在一定周期内具有围绕某个均值波动的特性。当利润过高时,可能刺激玻璃厂提高开工率或推迟冷修,增加玻璃供应,压低玻璃价格,同时增加对纯碱的需求,可能推高纯碱价格,从而压缩利润。当利润过低甚至亏损时,玻璃厂可能提前冷修或减产,减少玻璃供应,支撑玻璃价格,同时减少对纯碱的需求,可能压低纯碱价格,从而修复利润。策略的平仓和部分止损逻辑依赖于此特性。

  • 企业套期保值行为: 玻璃生产企业和纯碱生产/贸易企业可能会利用期货市场进行套期保值,例如在远期生产利润可观时,玻璃厂可能卖出玻璃期货、买入纯碱期货(锁定利润)。

策略适用场景

  • 玻璃生产利润价差显著偏离: 当计算出的玻璃生产利润价差的Z-score突破预设的阈值时,视为潜在的交易信号。

  • 预期均值回归(主要针对平仓和整体策略有效性): 策略的盈利平仓通常依赖于价差最终向其历史均值区域回归的预期。

  • 基本面分析辅助: 结合对纯碱和玻璃各自基本面的分析。如果Z-score发出信号,但基本面强烈指向相反方向的结构性变化(例如,重大的行业政策调整或技术革新),则可能需要谨慎。

  • 流动性充足: 策略涉及郑州商品交易所(CZCE)的玻璃期货(CZCE.FG)和纯碱期货(CZCE.SA),需要保证各合约均有良好的流动性。

二、天勤介绍

天勤平台概述

天勤(TqSdk)是一个由信易科技开发的开源量化交易系统,为期货、期权等衍生品交易提供专业的量化交易解决方案。平台具有以下特 点:

  • 丰富的行情数据 提供所有可交易合约的全部Tick和K线数据,基于内存数据库实现零延迟访问。
  • 一站式的解决方案 从历史数据分析到实盘交易的完整工具链,打通开发、回测、模拟到实盘的全流程。
  • 专业的技术支持 近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型保证性能。

策略开发流程

  • 环境准备
    • 安装Python环境(推荐Python 3.6或以上版本)
    • 安装tqsdk包:pip install tqsdk
    • 注册天勤账户获取访问密钥
  • 数据准备
    • 订阅近月与远月合约行情
    • 获取历史K线或者Tick数据(用于分析与行情推进)
  • 策略编写
    • 设计信号生成逻辑(基于价差、均值和标准差)
    • 编写交易执行模块(开仓、平仓逻辑)
    • 实现风险控制措施(止损、资金管理)
  • 回测验证
    • 设置回测时间区间和初始资金
    • 运行策略获取回测结果
    • 分析绩效指标(胜率、收益率、夏普率等)
  • 策略优化
    • 调整参数(标准差倍数、窗口大小等)
    • 添加过滤条件(成交量、波动率等)
    • 完善风险控制机制

三、天勤实现策略

策略原理

核心价差定义与计算(玻璃-纯碱利润价差组合价值):

该策略的核心是追踪一个基于特定比例调整后的玻璃期货价值与纯碱期货价值之间的差额。这个“利润价差组合价值”通过以下方式计算得出:

为玻璃(FG)和纯碱(SA)期货分别设定一个“生产比例因子”(代码中玻璃的FG_RATIO为1,纯碱的SA_RATIO为0.2)。这些因子旨在反映单位玻璃生产中纯碱的理论消耗比例(例如,1单位玻璃价值对应0.2单位纯碱价值的成本)。

将各期货品种的最新收盘价乘以其对应的合约乘数(代表每手合约的价值规模),再乘以各自的“生产比例因子”,得到它们在该价差定义中的调整后价值。

玻璃-纯碱利润价差组合价值被定义为:玻璃期货的调整后价值减去纯碱期货的调整后价值。 价差 = (玻璃收盘价 × 玻璃合约乘数 × FG_RATIO) - (纯碱收盘价 × 纯碱合约乘数 × SA_RATIO) 这个计算出的价差,反映了按照预设生产比例因子调整后的“原料成本”与“成品价值”之间的理论利润空间。

指标构建:

策略采用标准化得分(Z-score) 来衡量当前计算出的“玻璃-纯碱利润价差组合价值”与其近期历史水平的偏离程度。

  • 历史价差序列: 收集特定玻璃和纯碱期货合约在过去一段固定交易日(例如30天,即LOOKBACK_DAYS)内的每日收盘价。基于这些价格和上述价差计算方法,得到一个每日“玻璃-纯碱利润价差组合价值”的历史序列。
  • 计算历史均值: 对这个历史价差序列计算其算术平均值。
  • 计算历史标准差: 对同一个历史价差序列计算其标准差。
  • 计算标准化得分: 用最新的每日收盘价计算出当前的“玻璃-纯碱利润价差组合价值”,然后通过以下公式得到其标准化得分: 标准化得分 = (当前价差组合价值 - 历史均值) / 历史标准差。 (若历史标准差为零或极小,则此得分可能无意义或需特殊处理,但代码中未显式处理此情况)。

交易逻辑

开仓信号 (入场):

  • 做多利润价差 (预期价差维持高位或继续走高):

    • 信号: 当计算出的“标准化得分”显著高于一个预设的正阈值(例如,大于STD_THRESHOLD,如2.0)。这表明当前的利润价差远高于其历史平均水平。
    • 操作: 买入计算数量的玻璃期货,同时卖出计算数量的纯碱期货。此操作旨在从价差维持高位或进一步扩大中获利。
  • 做空利润价差 (预期价差维持低位或继续走低):

    • 信号: 当计算出的“标准化得分”显著低于一个预设的负阈值(例如,小于负STD_THRESHOLD,如-2.0)。这表明当前的利润价差远低于其历史平均水平。
    • 操作: 卖出计算数量的玻璃期货,同时买入计算数量的纯碱期货。此操作旨在从价差维持低位或进一步缩小(亏损扩大)中获利。

平仓信号 (止盈离场):

  • 信号: 当“标准化得分”的绝对值回落到一个较小的预设阈值以内(例如,小于CLOSE_THRESHOLD,如0.5)。这表示利润价差已经向其历史平均水平回归。
  • 操作: 将所有持有的期货头寸全部平掉(即目标持仓设为零)。

特定条件下的平仓信号 (类似止损或极端行情退出): 当策略当前已持有头寸时,若出现以下情况也会触发平仓:

  • 信号:
    • 若最初是因预期价差维持低位或走低而做空价差(即玻璃空头,纯碱多头,此时玻璃持仓为负),之后价差不仅未按预期向均值回归,反而“标准化得分”向负方向进一步极端偏离(例如,小于负STD_THRESHOLD的1.5倍)。
    • 若最初是因预期价差维持高位或走高而做多价差(即玻璃多头,纯碱空头,此时玻璃持仓为正),之后价差不仅未按预期向均值回归,反而“标准化得分”向正方向进一步极端偏离(例如,大于STD_THRESHOLD的1.5倍)。
  • 操作: 与均值回归离场操作类似,立即平掉所有持有的期货头寸。

回测

回测初始设置

  • 测试周期: 2023 年 11 月 1 日 - 2024 年 4 月 10 日
  • 交易品种: CZCE.FG409/CZCE.SA409
  • 初始资金: 1000万元

回测结果

上述回测累计收益走势图

完整代码示例

#!/usr/bin/env python
# coding=utf-8
__author__ = "Chaos"

from tqsdk import TqApi, TqAuth, TargetPosTask, TqBacktest, BacktestFinished
import numpy as np
from datetime import date

FG = "CZCE.FG409"  # 玻璃合约
SA = "CZCE.SA409"  # 纯碱合约
START_DATE = date(2023, 11, 1)
END_DATE = date(2024, 4, 10)
LOOKBACK_DAYS = 30
STD_THRESHOLD = 2.0
ORDER_VOLUME = 200  # 下单手数
CLOSE_THRESHOLD = 0.5

# 生产比例
FG_RATIO = 1
SA_RATIO = 0.2  # 生产1手玻璃需要0.2手纯碱

api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE), auth=TqAuth("快期账号", "快期密码"))

fg_quote = api.get_quote(FG)
sa_quote = api.get_quote(SA)
fg_klines = api.get_kline_serial(FG, 60*60*24, LOOKBACK_DAYS)
sa_klines = api.get_kline_serial(SA, 60*60*24, LOOKBACK_DAYS)
fg_pos = TargetPosTask(api, FG)
sa_pos = TargetPosTask(api, SA)

try:
    in_position = False
    while True:
        api.wait_update()
        if api.is_changing(fg_klines) or api.is_changing(sa_klines):
            # 计算历史利润价差
            spreads = []
            for i in range(len(fg_klines) - 1):
                fg_price = fg_klines.close.iloc[i] * fg_quote.volume_multiple * FG_RATIO
                sa_price = sa_klines.close.iloc[i] * sa_quote.volume_multiple * SA_RATIO
                spreads.append(fg_price - sa_price)
            mean_spread = np.mean(spreads)
            std_spread = np.std(spreads)
            fg_price = fg_klines.close.iloc[-1] * fg_quote.volume_multiple * FG_RATIO
            sa_price = sa_klines.close.iloc[-1] * sa_quote.volume_multiple * SA_RATIO
            current_spread = fg_price - sa_price
            z_score = (current_spread - mean_spread) / std_spread
            print(f"当前玻璃-纯碱利润价差: {current_spread:.2f}, Z-score: {z_score:.2f}")

            fg_position = api.get_position(FG)
            sa_position = api.get_position(SA)
            current_fg_pos = fg_position.pos_long - fg_position.pos_short
            current_sa_pos = sa_position.pos_long - sa_position.pos_short

            # 按生产比例计算纯碱下单手数
            sa_volume = int(ORDER_VOLUME * SA_RATIO / FG_RATIO)
            if sa_volume < 1:
                sa_volume = 1  # 至少1手

            if not in_position:
                if z_score < -STD_THRESHOLD:
                    print(f"做空利润:卖出玻璃{ORDER_VOLUME}手,买入纯碱{sa_volume}手")
                    fg_pos.set_target_volume(-ORDER_VOLUME)
                    sa_pos.set_target_volume(sa_volume)
                    in_position = True
                elif z_score > STD_THRESHOLD:
                    print(f"做多利润:买入玻璃{ORDER_VOLUME}手,卖出纯碱{sa_volume}手")

                    fg_pos.set_target_volume(ORDER_VOLUME)
                    sa_pos.set_target_volume(-sa_volume)
                    in_position = True
            else:
                if abs(z_score) < CLOSE_THRESHOLD:
                    print("利润价差回归,平仓所有头寸")
                    fg_pos.set_target_volume(0)
                    sa_pos.set_target_volume(0)
                    in_position = False
                # 止损逻辑
                if (z_score < -STD_THRESHOLD * 1.5 and current_fg_pos < 0) or \
                   (z_score > STD_THRESHOLD * 1.5 and current_fg_pos > 0):
                    print("止损:价差向不利方向进一步偏离")
                    fg_pos.set_target_volume(0)
                    sa_pos.set_target_volume(0)
                    in_position = False

except BacktestFinished as e:
    print("回测结束")
    api.close()