铜铝比价套利策略
一、交易策略解释
核心思想
铜铝比价套利的核心在于监控和交易“铜/铝价格比率”。铜(CU)和铝(AL)都是全球大宗商品市场中重要的有色金属,广泛应用于电力、建筑、交通、家电等多个工业领域。
- 铜铝比 (Copper-Aluminum Ratio):比率 = 价格_铜 / 价格_铝
核心思想是:
- 尽管铜和铝各自有其独特的供需基本面,但由于它们在某些应用领域存在一定的替代性(尽管不完全),并且都受到宏观经济周期、工业生产活动景气度的共同影响,它们的长期价格走势在一定程度上具有相关性。 因此,铜与铝的价格比率(铜铝比)虽然会波动,但市场参与者通常预期它会围绕一个历史形成的“均衡区间”或“均值”波动。
- 做多铜铝比 (Long the Ratio): 当交易者认为当前铜相对于铝的价格过低(即铜铝比低于历史均值或预期水平),未来铜价涨幅将超过铝价,或者铜价跌幅小于铝价时,他们会买入铜期货,同时卖出铝期货。
- 做空铜铝比 (Short the Ratio): 当交易者认为当前铜相对于铝的价格过高(即铜铝比高于历史均值或预期水平),未来铜价涨幅将小于铝价,或者铜价跌幅大于铝价时,他们会卖出铜期货,同时买入铝期货。
该策略预期当此比率显著偏离其历史常态时,会向其“正常”水平回归。
理论基础
- 宏观经济同步性: 作为主要的工业金属,铜和铝的需求都与全球及主要经济体的GDP增长、工业产出(PMI指数)、固定资产投资等宏观经济指标密切相关。经济扩张期,两者需求通常同时上升;经济收缩期,需求同时下降。
- 替代性与互补性:
- 替代性: 在某些领域,如电力电缆(铜导线 vs. 铝导线,铝需要更大截面积以达到同等导电率但重量轻、成本低)、散热器等,铜和铝存在一定的价格敏感型替代关系。当一种金属价格远高于另一种时,下游用户可能会考虑转换。
- 互补性: 在许多应用中,两者并非直接竞争,而是服务于不同性能要求或成本考量的细分市场。
- 供给端因素:
- 铜: 全球铜矿供应集中度较高,受主要矿山生产状况、劳工问题、地缘政治、冶炼产能(TC/RC费用是重要指标)等影响。
- 铝: 电解铝生产是高耗能产业,受电力成本(尤其是煤炭价格)、氧化铝供应、环保政策(如碳中和目标下的产能限制)等影响显著。
- 各自供给端的冲击可能导致两者价格短期内出现分化,从而使比价偏离。
- 均值回归特性 (Mean Reversion): 历史数据显示,铜铝比在长期内表现出一定的均值回归特性。当比率极端偏高或偏低时,市场力量(如替代效应、供需调整、投机资金的套利行为)可能促使其向历史均值回归。
策略适用场景
- 铜铝比显著偏离: 当计算出的铜铝价格比率(经过适当的标准化处理后)显著高于或低于其历史均值或关键的支撑/阻力水平时。
- 预期均值回归: 交易者基于对历史数据统计特征的分析,预期比率将向更可持续的水平回归。
- 基本面分析辅助: 结合对铜和铝各自短期及长期基本面的分析。例如,如果铜铝比过高,同时基本面显示铝的供应趋紧或需求预期改善快于铜,则做空比率的逻辑更强。
- 流动性充足: 策略涉及上海期货交易所(SHFE)的沪铜(SHFE.cu)和沪铝(SHFE.al)期货合约,需要保证各合约均有良好的流动性。
二、天勤介绍
天勤平台概述
天勤(TqSdk)是一个由信易科技开发的开源量化交易系统,为期货、期权等衍生品交易提供专业的量化交易解决方案。平台具有以下特 点:
- 丰富的行情数据: 提供所有可交易合约的全部Tick和K线数据,基于内存数据库实现零延迟访问。
- 一站式的解决方案: 从历史数据分析到实盘交易的完整工具链,打通开发、回测、模拟到实盘的全流程。
- 专业的技术支持: 近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型保证性能。
策略开发流程
- 环境准备
- 安装Python环境(推荐Python 3.6或以上版本)
- 安装tqsdk包:pip install tqsdk
- 注册天勤账户获取访问密钥
- 数据准备
- 订阅近月与远月合约行情
- 获取历史K线或者Tick数据(用于分析与行情推进)
- 策略编写
- 设计信号生成逻辑(基于价差、均值和标准差)
- 编写交易执行模块(开仓、平仓逻辑)
- 实现风险控制措施(止损、资金管理)
- 回测验证
- 设置回测时间区间和初始资金
- 运行策略获取回测结果
- 分析绩效指标(胜率、收益率、夏普率等)
- 策略优化
- 调整参数(标准差倍数、窗口大小等)
- 添加过滤条件(成交量、波动率等)
- 完善风险控制机制
三、天勤实现策略
策略原理
核心价差定义与计算(铜铝价值比)):
该策略的核心是追踪铜期货合约与铝期货合约之间的“价值比率”。这个比率通过以下方式计算得出:
-
获取铜和铝各自期货合约的最新收盘价。
-
将各期货品种的收盘价乘以其对应的合约乘数(代表每手合约的价值规模),得到每手合约的当前价值。
-
铜铝价值比被定义为:一手铜期货合约的价值除以一手铝期货合约的价值。 这个计算出的比率,反映了在合约层面上,购买等手数(例如1手)的铜相对于铝的成本关系。
-
指标构建:
策略采用标准化得分(Z-score) 来衡量当前计算出的“铜铝价值比”与其近期历史水平的偏离程度。
- 历史比率序列: 收集特定沪铜和沪铝期货合约在过去一段固定交易日(例如30天)内的每日收盘价。基于这些价格和上述价值比率计算方法,得到一个每日“铜铝价值比”的历史序列。
- 计算历史均值: 对这个历史比率序列计算其算术平均值。
- 计算历史标准差: 对同一个历史比率序列计算其标准差。
- 计算标准化得分: 用最新的每日收盘价计算出当前的“铜铝价值比”,然后通过以下公式得到其标准化得分: 标准化得分 = (当前铜铝价值比 - 历史均值) / 历史标准差 (若历史标准差为零,则此得分无意义或需特殊处理)。
交易逻辑
开仓信号 (入场):
-
预期比率进一步走高:
- 信号: 当计算出的“标准化得分”显著高于一个预设的正阈值(例如,大于2个标准差)。这表明当前的铜铝价值比远高于其历史平均水平,策略预期此偏离趋势可能持续。
- 操作: 买入指定数量的铜期货,同时卖出相同数量的铝期货。此操作旨在从铜相对于铝的价值比率继续上升中获利。
-
预期比率进一步走低:
- 信号: 当计算出的“标准化得分”显著低于一个预设的负阈值(例如,小于负2个标准差)。这表明当前的铜铝价值比远低于其历史平均水平,策略预期此偏离趋势可能持续。
- 操作: 卖出指定数量的铜期货,同时买入相同数量的铝期货。此操作旨在从铜相对于铝的价值比率继续下降中获利。
平仓信号 (止盈离场):
- 信号: 当“标准化得分”的绝对值回落到一个较小的预设阈值以内(例如,小于0.5个标准差)。这表示铜铝价值比已经向其历史平均水平回归。
- 操作: 将所有持有的期货头寸全部平掉(即目标持仓设为零)。
特定条件下的平仓信号 (类似止损或极端行情退出): 当策略当前已持有头寸时,若出现以下情况也会触发平仓:
- 信号:
- 若最初因预期比率走低而建立空头铜、多头铝的头寸(即铜的持仓为负),之后比率不仅未按预期下降,反而大幅反向运动至“标准化得分”显著高于一个更极端的正阈值(例如,大于原始开仓阈值的1.5倍)。
- 若最初因预期比率走高而建立多头铜、空头铝的头寸(即铜的持仓为正),之后比率不仅未按预期上升,反而大幅反向运动至“标准化得分”显著低于一个更极端的负阈值(例如,小于原始开仓阈值的1.5倍的负值)。
- 操作: 与获利离场操作类似,立即平掉所有持有的期货头寸。
回测
回测初始设置
- 测试周期: 2023 年 11 月 1 日 - 2024 年 3 月 13 日
- 交易品种: DCE.c2409/DCE.m2409/DCE.lh2409
- 初始资金: 1000万元
回测结果
上述回测累计收益走势图
完整代码示例
from tqsdk import TqApi, TqAuth, TargetPosTask, TqBacktest, BacktestFinished
import numpy as np
from datetime import date
CU = "SHFE.cu2407"
AL = "SHFE.al2407"
START_DATE = date(2023, 11, 1)
END_DATE = date(2024, 4, 30)
LOOKBACK_DAYS = 30
STD_THRESHOLD = 2.0
ORDER_VOLUME = 30
CLOSE_THRESHOLD = 0.5
api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE), auth=TqAuth("快期账号", "快期密码"))
cu_quote = api.get_quote(CU)
al_quote = api.get_quote(AL)
cu_klines = api.get_kline_serial(CU, 60*60*24, LOOKBACK_DAYS)
al_klines = api.get_kline_serial(AL, 60*60*24, LOOKBACK_DAYS)
cu_pos = TargetPosTask(api, CU)
al_pos = TargetPosTask(api, AL)
try:
# 计算历史铜铝比
ratios = []
for i in range(len(cu_klines) - 1):
cu_price = cu_klines.close.iloc[i] * cu_quote.volume_multiple
al_price = al_klines.close.iloc[i] * al_quote.volume_multiple
ratios.append(cu_price / al_price)
mean_ratio = np.mean(ratios)
std_ratio = np.std(ratios)
print(f"历史铜铝比均值: {mean_ratio:.4f}, 标准差: {std_ratio:.4f}")
in_position = False
while True:
api.wait_update()
if api.is_changing(cu_klines) or api.is_changing(al_klines):
# 重新计算
ratios = []
for i in range(len(cu_klines) - 1):
cu_price = cu_klines.close.iloc[i] * cu_quote.volume_multiple
al_price = al_klines.close.iloc[i] * al_quote.volume_multiple
ratios.append(cu_price / al_price)
mean_ratio = np.mean(ratios)
std_ratio = np.std(ratios)
cu_price = cu_klines.close.iloc[-1] * cu_quote.volume_multiple
al_price = al_klines.close.iloc[-1] * al_quote.volume_multiple
current_ratio = cu_price / al_price
z_score = (current_ratio - mean_ratio) / std_ratio
print(f"当前铜铝比: {current_ratio:.4f}, Z-score: {z_score:.2f}")
cu_position = api.get_position(CU)
al_position = api.get_position(AL)
current_cu_pos = cu_position.pos_long - cu_position.pos_short
current_al_pos = al_position.pos_long - al_position.pos_short
if not in_position:
if z_score > STD_THRESHOLD:
# 做多铜铝比:买入铜,卖出铝
print(f"做多铜铝比:买入铜{ORDER_VOLUME}手,卖出铝{ORDER_VOLUME}手")
cu_pos.set_target_volume(ORDER_VOLUME)
al_pos.set_target_volume(-ORDER_VOLUME)
in_position = True
elif z_score < -STD_THRESHOLD:
# 做空铜铝比:卖出铜,买入铝
print(f"做空铜铝比:卖出铜{ORDER_VOLUME}手,买入铝{ORDER_VOLUME}手")
cu_pos.set_target_volume(-ORDER_VOLUME)
al_pos.set_target_volume(ORDER_VOLUME)
in_position = True
else:
if abs(z_score) < CLOSE_THRESHOLD:
print("比率回归正常,平仓所有头寸")
cu_pos.set_target_volume(0)
al_pos.set_target_volume(0)
in_position = False
# 止损逻辑
if (z_score > STD_THRESHOLD * 1.5 and current_cu_pos < 0) or \
(z_score < -STD_THRESHOLD * 1.5 and current_cu_pos > 0):
print("止损:比率向不利方向进一步偏离")
cu_pos.set_target_volume(0)
al_pos.set_target_volume(0)
in_position = False
except BacktestFinished as e:
print("回测结束")
api.close()