大家好,我是easyquant实战。
做期货量化的小伙伴,一定对“期限结构”这个词不陌生。但你有没有想过:为什么主力合约和次主力合约的价差,能变成真金白银的策略? 🔍
国信金工2022年的一篇研报【国信金工】基于Carry的商品期货交易策略给出了漂亮的答案——基础年化11%,优化后直接干到26%,夏普1.73。但研报终归是纸面富贵,真要跑起来,还得靠代码说话。
今天,我就把这套策略用Python+tqsdk(天勤量化)完整复现出来,而且不止是复现——我在代码里偷偷加了不少“私货”优化,有些地方甚至和研报思路不太一样,但效果嘛……你懂的 😏
放心,我会把哪些是研报原版,哪些是我的“骚操作” 掰扯得清清楚楚。文末附完整源码获取方式,记得看到最后!🎁
⚠️ 风险提示:本文纯技术分享,不构成投资建议。历史业绩不代表未来,市场环境变化可能导致策略失效,请自主决策、谨慎参考。
假设你现在拿着一个下个月到期的螺纹钢合约,一个月后它就会变成“现货”。如果你觉得未来价格会涨,想继续持有,就得把它卖掉,换成两个月后到期的合约。
这一卖一买之间的价差,就是展期收益(Carry)。说白了,就是你从“近月换远月”这个动作里赚到的钱。
公式长这样(别怕,很简单):
Carry = (主力合约价格 / 次主力合约价格 - 1) / 交割月差再取个10日平均,把噪音磨平,就得到了我们每天看的Carry信号。
研报做了一个测试:把所有期货品种按Carry从高到低分成5组,然后看它们未来的表现。
结果发现一个神奇的规律:
Carry最高的一组(第5组),净值曲线一路向南(做空它就赚翻了)📉
Carry最低的一组(第1组),净值曲线稳步向北(做多它就美滋滋)📈
这背后是有金融学道理的:Carry本质是风险溢价。当大家都疯狂看空、大量做空时(Carry很高),价格可能已经跌过头了,未来反而容易反弹——这叫过度反应异象。我们反向操作,就是在赚“情绪回归理性”的钱。
光靠Carry分组就能年化11%,但研报还不满足,又加了两把火:
动量过滤:Carry信号出来,还要看看价格和10日均线的位置。如果方向一致(共振),就满仓干;如果方向相反但成交量缩量,就半仓试试水。这是让基本面逻辑和市场情绪“共振”,胜率更高。
吊灯止损:不止损开仓价,而是从持仓以来的最高点往下数,跌了2.5倍ATR就砍仓,还分三次减,平滑得很。
这两把火一烧,策略直接起飞:年化25.96%,夏普1.73,卡玛1.63!🚀
我写的 [CARRY增强策略.py] 严格遵循了研报的核心思路(Carry分组、信号过滤),但在品种筛选、次主力选择、止损逻辑、资金管理等方面,我结合实战经验做了自己的优化。下面逐个拆解。
研报要求:过去半年日均成交额 > 50亿。我的问题:天勤接口没有直接给成交额,咋办?我的优化:我用持仓量 × 价格 × 合约乘数 估算日均沉淀资金,选过去半年日均 > 2.5亿的品种(相当于成交额门槛的保守版)。
# 计算沉淀资金deposit = kline["close_oi"] * kline["close"] * volume_multiple# 取120日均值if deposit.rolling(120).mean().iloc[-2] >= 2.5e8:# 纳入交易池
为啥这么改? 流动性差的品种容易被操纵,冲击成本也高,咱普通交易者还是避开为妙。😤
研报的坑:只说了“主力合约”和“次主力合约”,但次主力到底是哪个? 如果简单选持仓量第二大的,万一它是快交割的老合约,流动性差不说,价格还容易抽风。
我的优化:在剩余天数大于主力的合约里,挑持仓量最大的。这样既保证了是远月合约,又保证了流动性。
def get_sub_symbol_from_cache(self, index):main = self.get_main_symbol_from_cache(index)all_symbols = self.api.query_quotes(...)# 选剩余天数大于主力的合约candidates = [s for s in all_symbols if expire_dict[s] > expire_dict[main]]sub = max(candidates, key=lambda x: oi_dict[x]) # 取持仓量最大return sub
每天收盘后算Carry,用的都是昨日收盘价(iloc[-2]),确保回测零作弊。
yield_series = (main_close / sub_close - 1) / month_diffroll_yield_dict[index] = yield_series.rolling(10).mean().iloc[-2]
研报:共振满仓,背离缩量半仓。我的加码:
开盘过滤:开盘后60秒内不开仓,避开集合竞价的妖气。
ATR资金管理:根据品种波动率算手数,单笔波动不超过总资金的4%,单品种保证金不超过20%。
# 基于ATR算手数vol_by_atr = int(balance * 0.04 / max(atr1, atr2) / 2 / multiplier)# 基于保证金算手数vol_by_margin = int(balance * 0.2 / margin_per_hand)open_volume = min(vol_by_atr, vol_by_margin)# 缩量时半仓if open_interest < oi_ma10:open_volume //= 2
思考:波动大的品种少买点,波动小的多买点——这不就是现代投资组合理论的朴素实践嘛!📚
这是我和研报分道扬镳的地方。
研报:被动等回撤2.5倍ATR止损。
我的逻辑:根据盈利倍数(浮盈/ATR)动态调整止损宽度,盈利越多,能容忍的回撤越大。而且分批减仓,不是一把梭。
# 计算盈利倍数profit_multi = (high - open_price) / atr# 盈利越多,止损系数越大(越宽松)if profit_multi < 2:stop_multi = 1.0elif profit_multi < 3:stop_multi = 0.6else:stop_multi = 0.3stop_price = high - stop_multi * atr# 触发减仓(每次减1/3)if close_price < stop_price:close_vol = pos_long // 3close_position(...)
为啥这么改?
行为金融学告诉我们:人赚了钱就想跑,亏了钱就死扛。我偏要反着来:赚得越多,越能扛回调;赚得少,赶紧落袋。这是反人性的理性设计。🧠
分批减仓:万一是虚晃一枪呢?减1/3既能锁定利润,又留了仓位让利润奔跑。
临近交割的合约容易出幺蛾子(逼仓、期现收敛),提前15天平仓换月,避开这些非市场因素。
账户级风控:大额亏损(-5%)、异常手续费(>0.5%)、频繁报撤单(>100次)、盈利回撤(>5%),触发了直接清仓并邮件报警。
异步日志:日志写入放后台线程,不卡主流程。
心跳检测:网络断了自动重连,并发邮件通知你。
Python 3.7+
装依赖:pip install tqsdk talib akshare pandas numpy
注册天勤账号(免费),在Config里填用户名密码
打开Config类,填上:
天勤账号密码
期货公司资金账号、密码
邮箱信息(收通知用)
想排除的品种(比如股指、国债)
python CARRY增强策略.py程序自动登录、初始化,然后进入主循环,你就可以喝茶看戏了。🍵
这份代码不是圣杯,但它是我对研报理解的结晶,也融入了自己的实战思考。你可以把它当做一个可运行的策略框架,跑模拟盘、观察信号、调整参数,慢慢打磨成你自己的武器。
如果你在复现中遇到问题,欢迎评论区留言,我会挑典型问题后续解答。
源码获取:关注公众号,后台回复“CARRY增强”领取下载链接。
互动:
你对“动态加减仓”的逻辑认可吗?有啥想法欢迎交流。
下期想看什么策略?股票多因子?期货套利?期权波动率?评论区告诉我!👇
风险提示:历史业绩不代表未来,市场环境会变化,策略也存在失效的可能。本文内容仅为技术交流与思路分享,不构成任何投资建议,请各位朋友自主决策、理性投资。