商品期货对冲 + 网格
传统的跨期对冲一般指统计套利, 用线性回归或者其它办法生成一个套利区间, 这样套利机会比较少, 而且有预测性, 未来价差很可能不是预计的那样回归
为了解决这种办法, 进而更频繁的进行套利操作, 我们把两个关联品种或者跨期品种的套利价差定义成一个网格, 每满足一定的价差就开一次仓, 做一次对冲
这样价差来回在我们设置的网格里进行波动,我们就能不断的开仓平仓实现盈利.
不善文案, 不做文字表达了, 具体看策略代码
- // 商品期货套利 - 多品种网格对冲模型 注释版
- function Hedge(q, e, positions, symbolA, symbolB, hedgeSpread) { // 对冲对象 生成函数, 参数q 为最开始调用 商品期货交易类库模板 的 导出函数 生成的 交易队列控制对象, 参数e 为交易所对象,positions 为初始仓位
- // symbolA 为第一个合约类型, symbolB 为第二个合约类型, hedgeSpread 为对冲 交易量控制表 字符串。
- var self = {} // 声明一个 空对象 用于给空对象 初始化 后返回 (即对冲对象)
- self.q = q // 给对冲对象 添加 属性 q ,并用参数的q 初始赋值。
- self.symbolA = symbolA // 给对冲对象self 添加属性 symbolA ,储存 第一个合约类型
- self.symbolB = symbolB // ...
- self.name = symbolA + " & " + symbolB // 对冲对象的 名称 即对冲组合
- self.e = e // 对冲对象 中储存交易所对象 的引用
- self.isBusy = false // 繁忙 标记 初始为 不繁忙。
- self.diffA = 0 // 差价A , 即 symbolA买一 - symbolB卖一
- self.diffB = 0 // 差价B , 即 symbolB卖一 - symbolA买一
- self.update = _D() // 记录 更新时间
- var arr = hedgeSpread.split(';') // 把传入的 交易量控制表 字符串 按照 ';' 字符 分割。
- self.dic = [] // 网格节点 对象 数组
- var n = 0 //
- var coefficient = 1 // 系数 初始1
- for (var i = 0; i < positions.length; i++) { // 遍历持仓信息。
- if (positions[i].ContractType == symbolA) { // 如果遍历当前的 持仓信息 的合约类型 和 当前对冲对象的 第一个合约类型 相同
- n += positions[i].Amount // 累计 类型为 symbolA 的合约的持仓量
- if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) { // 如果 持仓类型是 多仓 , coefficient 赋值为 -1 ,即 代表 反对冲: 多 symbolA 空 symbolB
- coefficient = -1
- }
- }
- }
- _.each(arr, function(pair) { // 把 控制表字符串 数组的 每一个单元 迭代传递到 匿名函数 作为参数 pair
- var tmp = pair.split(':'); // 把每个 网格节点的 单元 按照':'字符 分割成 参数 数组 tmp
- if (tmp.length != 3) { // 由于 格式 是 30:15:1 即 开仓差价: 平仓差价: 下单量 ,所以 用 ':' 分割后 tmp长度 不是3的 即 格式错误
- throw "开仓表不正确"; // 抛出异常 格式错误
- }
- var st = { // 每次迭代的时候 构造一个对象
- open: Number(tmp[0]), // 开仓 把 tmp[0] 即 30:15:1 按 ':' 分割后 生成的数组中的 第一个元素 30 , 通过 Number 函数 转换为数值
- cover: Number(tmp[1]), // 平仓..
- amount: Number(tmp[2]), // 量..
- hold: 0 // 持仓 初始为0
- }
- if (n > 0) { // 如果 n 大于0 ,即 开始的时候 有持仓。
- var m = Math.min(n, st.amount) // 取 当前合约组合中 symbolA的 持仓量 和 网格节点 中的开仓量 二者的 最小值 赋值给 m 变量
- n -= m // 在持仓累计数量 n 中 减去 m
- st.hold = m * coefficient // 正对冲 coefficient 这个系数 为1 , 如果 symbolA 为 合约的持仓 类型为 多仓, 则是反对冲 那么 coefficient 在之前 就会被赋值为 -1
- // 在迭代过程中 n 被 分散到各个 网格节点 恢复网格 持仓数据。
- Log("恢复", self.name, st) // 输出本次的恢复信息。
- }
- self.dic.push(st) // 把恢复好的节点 压入 dic数组 。
- });
- if (n > 0) { // 如果 迭代完成后 n 值 依然大于0 即 还有仓位没有分配恢复 , 抛出错误
- throw "恢复失败, 有多余仓位 " + n;
- }
- self.poll = function() { // 给 self 对冲对象 添加 属性poll 并用一个 匿名函数 初始化 赋值
- if (self.isBusy || (!$.IsTrading(self.symbolA))) { // 如果 self 对象的属性 isBusy 为true 即繁忙 ,或者 合约类型 symbolA 不在交易时间内 (通过调用$.IsTrading这个模板导出函数获取) ,则调用return返回
- return
- }
- var insDetailA = exchange.SetContractType(self.symbolA) // 设置 合约类型 symbolA
- if (!insDetailA) { // 返回 null 则 调用 return 返回
- return
- }
- var tickerA = exchange.GetTicker() // 获取 symbolA 合约的行情信息
- if (!tickerA) { // 获取失败 调用return
- return
- }
- var insDetailB = exchange.SetContractType(self.symbolB) // 设置 合约类型 symbolB
- if (!insDetailB) {
- return
- }
- var tickerB = exchange.GetTicker() // 获取 symbolB 合约的行情信息
- if (!tickerB) {
- return
- }
- self.update = _D(tickerA.Time) // 更新时间
- var action = null // 动作变量
- var diffA = _N(tickerA.Buy - tickerB.Sell) // A合约的 买一 减去 B合约的 卖一
- var diffB = _N(tickerA.Sell - tickerB.Buy) // A合约的 卖一 减去 B合约的 买一
- self.diffA = diffA // 赋值 给 对象 的 成员diffA
- self.diffB = diffB // 赋值 ..
- for (var i = 0; i < self.dic.length && !action; i++) { // 遍历 网格的 节点 ,直到 遍历结束 或者 有action 执行。
- if (self.dic[i].hold == 0) { // 如果 网格 节点的持仓量 为 0
- if (self.dic[i].open <= diffA) { // 如果 网格 节点的开仓价 小于等于 差价A (即 A合约买一 减去 B合约的卖一 的差价 突破 网格节点的 开仓价)
- action = [i, "sell", "buy", self.dic[i].amount] // action 记录下 网格节点 索引 、symbolA sell ,symbolB buy ,操作量 。
- } else if (self.dic[i].open <= -diffB) { // -diffB 实际就是 tickerB.Buy - tickerA.Sell ,也就是 B合约买一 减去 A合约的卖一 的差价 突破 网格节点的 开仓价
- action = [i, "buy", "sell", -self.dic[i].amount] // action 记录..
- }
- } else { // 网格节点的 持仓量 不为0
- if (self.dic[i].hold > 0 && self.dic[i].cover >= diffB) { // 如果 节点的持仓量 大于0 即 正对冲 持仓 A合约持空,B合约持多。 并且 平仓差价(合约A sell ,合约B buy)小于平仓线
- action = [i, "closesell", "closebuy", self.dic[i].hold] // action 记录下 网格节点 索引 , 合约A 平空仓 , 合约B 平多仓, 按照节点持仓量 平。
- } else if (self.dic[i].hold < 0 && self.dic[i].cover >= -diffA) { // 如果持仓量 小于0 , 并且 -diffA 即 tickerB.Sell - tickerA.Buy ,小于平仓线
- action = [i, "closebuy", "closesell", self.dic[i].hold] // action 记录下 ..
- }
- }
- }
- if (!action) { // 如果 action 为初始赋值的 null ,即没有 被 赋值操作。 调用 return 返回
- return
- }
- Log("A卖B买: " + _N(diffA) + ", A买B卖: " + _N(diffB), ", Action: " + JSON.stringify(action)) // 如果 action 有值 ,输出信息 差价A 差价B 和 action 储存的数据。
- self.isBusy = true // 有操作 即 锁定, 为繁忙状态, 处理完成前 在该函数开始处都会触发 return
- self.q.pushTask(self.e, self.symbolA, action[1], self.dic[action[0]].amount, function(task, ret) { // 调用 交易队列对象q 的成员函数 把 具体操作 参数传入 , 压任务进队列 等待处理。
- if (!ret) { // 回调函数, 如果完成的 返回值 ret 为false 即 操作失败,
- self.isBusy = false // 重新把 isBusy 赋值为 false ,解除锁定
- return // 返回
- }
- self.q.pushTask(self.e, self.symbolB, action[2], self.dic[action[0]].amount, function(task, ret) { // A 合约 操作 成功 则,压入B合约的 操作任务 进 任务队列 等待处理。
- if (!ret) { // 如果 A合约操作完成 B 合约 操作失败 则 抛出异常
- throw "开仓失败..."
- }
- self.isBusy = false // 解除锁定
- if (task.action != "buy" && task.action != "sell") { // 如果 调用该回调函数的 任务 不是开仓 操作
- self.dic[action[0]].hold = 0; // 当前 操作的网格 节点 的持仓量 重新赋值为 0
- } else { // 如果是 开仓操作
- self.dic[action[0]].hold = action[3]; // 把 当前任务 的下单量 参数 赋值给 当前网格节点的持仓量
- }
- })
- })
- }
- return self // 该函数 返回一个 对冲组合 对象
- }
原贴出处 BotVS 论坛,转载请注明出处,商业用途转载请与原文作者联系。
https://www.botvs.com/strategy/27799