输入/搜索内容
1
关注
183
关注者
折腾一个黑色类期货策略:从想法到代码的真实记录
创建于 2025-09-05 09:43:48  更新于 2025-09-05 23:13:21
 1
 921

img

写在前面

这篇文章记录了我开发一个黑色类期货交易策略的全过程,包括各种试错、推翻重来、半夜改bug的真实经历。如果你也在搞量化交易,希望这些弯路能帮你少走几步。

第一步:发现有趣的现象

最初的观察

前段时间在看期货行情的时候,发现了一个挺有意思的事:铁矿石、焦煤、螺纹钢、热卷这四个品种,大部分时候都是一起涨一起跌的。想想也对,毕竟都是一条产业链上的:

铁矿石 + 焦煤 → 炼钢 → 螺纹钢/热卷

就像一家人,有福同享有难同当。

突然的想法

但是有时候会出现这种情况:比如铁矿石突然暴涨,而其他三个还在原地踏步。这时候我就在想,这种"不听话"的现象是不是意味着什么?

有一天看到铁矿石单独拉了个大阳线,其他三个品种还在磨蹭,我就琢磨:会不会其他三个也会跟着涨?

果然,第二天螺纹钢和热卷就开始补涨了。

这让我产生了一个想法:能不能写个程序,专门抓这种"带头大哥"出现的时候,然后跟着买其他几个品种?

第二步:把想法变成逻辑

最初的思路

刚开始我的想法很简单粗暴:

  1. 看这四个品种的价格变化
  2. 如果有一个突然涨得特别猛(比其他的明显多),那它就是"带头大哥"
  3. 马上买其他三个品种,等着它们跟涨
  4. 涨够了就卖掉

听起来很简单,但是真要写成代码,问题就来了。

第一个难题:怎么定义"突然涨得特别猛"?

这个问题困扰了我好几天。最开始想用简单的价格涨幅,比如某个品种今天涨了3%,其他的只涨1%,那就算突破。

但试了一下发现不行,因为:

  • 有时候市场整体都在涨,3%可能不算什么
  • 有时候市场很平静,1%的差异可能就很大了

后来想到用相对偏离的概念。就是看某个品种相对于"大家的平均表现"偏离了多少。

第二个难题:怎么判断是"正常波动"还是"真突破"?

这个更头疼。市场每天都有波动,不能说稍微有点不一样就算突破吧?

想来想去,决定用统计学的方法:

  1. 计算过去一段时间(比如30天)每个品种的"正常偏离范围"
  2. 如果某天的偏离超过了这个范围,比如超过了2个标准差,那就算突破
  3. 这时候其他品种就跟着这个"带头大哥"的方向操作

第三步:写代码实现

技术方案选择

因为要接期货交易,最后选择了发明者量化平台,主要是因为:

  • 支持期货CTP接口
  • 有现成的框架,不用从头搞
  • JavaScript写起来比较顺手

第一版代码:简单粗暴

第一版代码逻辑很简单:

javascript
// 伪代码 if (某个品种偏离 > 阈值) { 买其他三个品种(); } if (盈利 > 止盈点 || 亏损 > 止损点) { 全部平仓(); }

第一次测试:惨败

回测结果惨不忍睹,胜率不到30%。仔细分析后发现几个问题:

  1. 假突破太多:很多时候看起来突破了,结果第二天就回去了
  2. 追高杀跌:经常在高点买入,在低点止损
  3. 没有耐心:突破后立即开仓,没有确认

第四步:反思和改进

引入"平衡-突破-跟随"循环

经过反思,我意识到应该更加尊重市场的节奏。于是设计了一个状态机:

  1. 寻找平衡:先等市场进入相对平衡的状态
  2. 等待突破:平衡状态下,等待某个品种突破
  3. 跟随开仓:确认突破后,其他品种跟随开仓
  4. 止盈止损:达到条件后平仓
  5. 等待恢复平衡:平仓后等待市场重新平衡
  6. 重复循环:回到第2步

这个想法看起来简单,但实现起来需要一个完整的状态机。让我用代码来说明这个过程:

javascript
// 状态变量定义 var hasFoundInitialBalance = false; // 是否找到初始平衡 var waitingForBalance = false; // 是否在等待平衡恢复 var isInPosition = false; // 是否有持仓 // 平衡检测函数 function detectLeaderAndFollowers(ironOreR, cokeR, rebarR, hotRollR) { // 计算各品种的同步得分 var syncScores = calculateSyncScores(ironOreR, cokeR, rebarR, hotRollR); // 找出最大偏离的品种 var maxAbsScore = 0; var leader = null; Object.keys(syncScores).forEach(function(commodity) { var absScore = Math.abs(syncScores[commodity]); if (absScore > maxAbsScore) { maxAbsScore = absScore; if (absScore > syncThreshold) { // 超过2σ算突破 leader = commodity; } } }); return { hasLeader: leader !== null, leader: leader, maxDeviation: maxAbsScore, isBalanced: maxAbsScore < balanceThreshold, // 小于1σ算平衡 syncScores: syncScores }; } // 核心状态机逻辑 if (!isInPosition) { if (!hasFoundInitialBalance) { // 状态1: 寻找初始平衡 if (leaderResult.isBalanced) { hasFoundInitialBalance = true; Log("✅ 找到初始平衡,开始监控突破机会", "#00FF00"); } else { Log(`🔍 寻找平衡中...最大偏离: ${leaderResult.maxDeviation.toFixed(2)}σ`, "#FFAA00"); } } else if (!waitingForBalance) { // 状态2: 等待突破 if (leaderResult.hasLeader) { // 发现突破!开仓其他三个品种 Log(`💥 ${leaderResult.leaderName}突破!跟随开仓`, "#FF0000"); // 跟随开仓逻辑 followers.forEach(function(follower) { if (direction === 'BUY') { p.OpenLong(contractMap[follower], Amount); } else { p.OpenShort(contractMap[follower], Amount); } }); isInPosition = true; } else { Log(`⚖️ 等待突破...最大偏离: ${leaderResult.maxDeviation.toFixed(2)}σ`, "#00AAFF"); } } else { // 状态4: 等待平衡恢复 if (leaderResult.isBalanced) { Log("✅ 平衡恢复!准备下次机会", "#00FF00"); waitingForBalance = false; // 回到状态2 } else { Log(`⏳ 等待平衡恢复...偏离: ${leaderResult.maxDeviation.toFixed(2)}σ`, "#FFAA00"); } } } else { // 状态3: 持仓中,监控止盈止损 if (totalProfit > profitTarget || totalProfit < -stopLoss) { Log("止盈止损,平仓!", "#0000FF"); p.CoverAll(); isInPosition = false; waitingForBalance = true; // 进入状态4 } }

这个状态机的精妙之处在于:

强制等待平衡:程序启动后不会立即交易,而是先观察市场,等到四个品种都相对平静(最大偏离<1σ)才开始监控。

确认真突破:只有在平衡状态下的突破(>2σ)才认为是有效信号,避免在震荡行情中被假突破骗。

防止频繁交易:平仓后必须等平衡恢复才能开新仓,给市场足够的"冷静时间"。

状态可视化:每个状态都有明确的日志输出,方便调试和监控。

实际运行时,你会看到这样的日志:

🔍 寻找平衡中...最大偏离: 1.8σ ✅ 找到初始平衡,开始监控突破机会 ⚖️ 等待突破...最大偏离: 0.5σ 💥 铁矿石突破!跟随开仓 📊 持仓中...当前盈亏: +1200 止盈止损,平仓! ⏳ 等待平衡恢复...偏离: 1.5σ ✅ 平衡恢复!准备下次机会

这样的设计大大提高了策略的稳定性和胜率。

关键改进点

1. 严格的开仓条件

不是随便偏离一点就开仓,而是:

  • 首先要确认市场处于平衡状态
  • 然后等待明确的突破信号(超过2个标准差)
  • 最后才跟随开仓

2. 更合理的平仓机制

改成纯粹的止盈止损,不再考虑"平衡恢复"平仓,因为发现这样经常过早离场。

3. 防止频繁交易

平仓后必须等待市场重新平衡才能开下一次仓,避免来回打脸。

第五步:代码实现的坑

坑1:变量作用域问题

JavaScript的变量作用域真的是个坑,写着写着就报错:ReferenceError: xxx is not defined

最后发现是全局变量和局部变量搞混了,索性把所有状态变量都放到main函数里面定义,问题解决。

坑2:数据不足的处理

刚开始没考虑数据不足的情况,结果程序一启动就报错。后来加了各种边界条件判断:

javascript
if (minLength < syncWindow + 5) { return { hasLeader: false, message: "数据不足,无法检测" }; }

坑3:合约切换问题

期货有主力合约切换的问题,之前的代码没处理,导致持仓莫名其妙地变了。后来专门写了个posTrans函数处理合约转换。

javascript
function posTrans(p, mainList) { var codeList = mainList.map(item => item.match(/[A-Za-z]+/g)[0]); var prePos = exchange.GetPosition(); prePos.forEach(function(pos) { var mainCode = pos.ContractType.match(/[A-Za-z]+/g)[0]; if (mainList.indexOf(pos.ContractType) === -1) { var index = codeList.indexOf(mainCode); var mainID = index !== -1 ? mainList[index] : null; if (mainID) { Log('旧合约', pos.ContractType, '需要被更换为', mainID); p.Cover(pos.ContractType); if (pos.Type === PD_LONG || pos.Type === PD_LONG_YD) { p.OpenLong(mainID, pos.Amount); } else { p.OpenShort(mainID, pos.Amount); } } } }); var afterPos = exchange.GetPosition(); if (afterPos.every(pos => mainList.indexOf(pos.ContractType) !== -1)) { Log("所有合约都是主力合约", "#00FF00"); } }

第六步:最终策略逻辑

核心算法

最终的策略核心是这样的:

  1. 计算同步得分

    • 计算四个品种30天的收益率
    • 计算每个品种相对于组合平均的偏离
    • 用Z-Score标准化,得到同步得分
  2. 状态判断

    • 平衡状态:所有品种偏离 < 1σ
    • 突破状态:某个品种偏离 > 2σ
  3. 交易决策

    • 发现突破 → 其他三个品种跟随开仓
    • 止盈止损 → 全部平仓
    • 等待平衡恢复 → 准备下一轮

关键参数

经过大量回测调优,最终确定的参数:

javascript
var syncWindow = 30; // 30天计算窗口 var syncThreshold = 2.0; // 2σ突破阈值 var balanceThreshold = 1.0; // 1σ平衡阈值 var profitTarget = 3000; // 止盈点 var stopLoss = 1500; // 止损点

第七步:回测结果和思考

回测表现

使用近3年的数据进行回测,表现还是相当比较稳定的,大家可以参考一下。

img

策略优势

  1. 逻辑清晰:基于产业链内在联系,有经济学基础
  2. 风险可控:有明确的止损机制
  3. 适应性强:不依赖特定市场环境
  4. 交易频率适中:不会过度交易

需要注意的问题

  1. 数据质量:需要稳定的实时数据源
  2. 滑点成本:实盘交易要考虑滑点和手续费
  3. 极端行情:黑天鹅事件可能导致策略失效
  4. 资金管理:单次仓位不宜过大

写在最后

这个策略从想法到实现,前前后后折腾了大概两个月。期间推翻重来了好几次,也踩了不少坑。最大的感受是:量化交易看起来高大上,其实就是把交易直觉用代码实现出来。关键是要保持耐心,不断测试和改进。另外,没有完美的策略。市场在变,策略也要跟着调整。重要的是建立一套完整的开发和测试流程,这样才能持续改进。

如果你也在搞量化交易,希望这个经历对你有帮助。有问题可以一起交流,毕竟一个人折腾太孤单了😄

交流与分享

写这篇文章主要是想和大家分享一下开发策略的真实过程,包括那些踩过的坑和走过的弯路。如果对你有帮助,或者你有不同的看法,欢迎在评论区多多交流

如果大家对这个策略比较感兴趣,评论多的话,我可以考虑把完整的源码开源出来,包括:

  • 完整的策略代码
  • 详细的参数调优过程
  • 回测报告和风险分析
  • 实盘运行的注意事项

量化交易这条路不好走,但一群人走总比一个人走要有趣得多。期待和大家的交流!


声明:本文仅为技术分享,不构成投资建议。期货交易有风险,入市需谨慎。

评论
全部评论 (1)

    zeng总,希望把完全的源码放出来哇,多谢,哈哈

    2 个月前
  • 1
iPhone 下载
社区
回测系统
© 2015 - ∞ YouQuant 豫ICP备19046564号