多品种商品跨期套利

Author: 扫地僧, Date: 2021-11-04 17:10:11
Tags: 商品期货套利JavaScript

商品期货对冲 + 网格

传统的跨期对冲一般指统计套利, 用线性回归或者其它办法生成一个套利区间, 这样套利机会比较少, 而且有预测性, 未来价差很可能不是预计的那样回归

为了解决这种办法, 进而更频繁的进行套利操作, 我们把两个关联品种或者跨期品种的套利价差定义成一个网格, 每满足一定的价差就开一次仓, 做一次对冲

这样价差来回在我们设置的网格里进行波动,我们就能不断的开仓平仓实现盈利.

不善文案, 不做文字表达了, 具体看策略代码


/*backtest
start: 2017-03-07 09:00:00
end: 2017-04-05 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*/

function Hedge(q, e, positions, symbolA, symbolB, hedgeSpread) {
    var self = {}
    self.q = q
    self.symbolA = symbolA
    self.symbolB = symbolB
    self.name = symbolA + " & " + symbolB
    self.e = e
    self.isBusy = false
    self.diffA = 0
    self.diffB = 0
    self.update = _D()
    var arr = hedgeSpread.split(';')
    self.dic = []
    var n = 0
    var coefficient = 1
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType == symbolA) {
            n += positions[i].Amount
            if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
                coefficient = -1
            }
        }
    }
    _.each(arr, function(pair) {
        var tmp = pair.split(':');
        if (tmp.length != 3) {
            throw "开仓表不正确";
        }
        var st = {
            open: Number(tmp[0]),
            cover: Number(tmp[1]),
            amount: Number(tmp[2]),
            hold: 0
        }
        if (n > 0) {
            var m = Math.min(n, st.amount)
            n -= m
            st.hold = m * coefficient
            Log("恢复", self.name, st)
        }
        self.dic.push(st)
    });
    if (n > 0) {
        throw "恢复失败, 有多余仓位 " + n;
    }

    self.poll = function() {
        if (self.isBusy || (!$.IsTrading(self.symbolA))) {
            return
        }
        var insDetailA = exchange.SetContractType(self.symbolA)
        if (!insDetailA) {
            return
        }
        var tickerA = exchange.GetTicker()
        if (!tickerA) {
            return
        }
        var insDetailB = exchange.SetContractType(self.symbolB)
        if (!insDetailB) {
            return
        }
        var tickerB = exchange.GetTicker()
        if (!tickerB) {
            return
        }
        
        self.update = _D(tickerA.Time)

        var action = null
        var diffA = _N(tickerA.Buy - tickerB.Sell)
        var diffB = _N(tickerA.Sell - tickerB.Buy)
        self.diffA = diffA
        self.diffB = diffB

        for (var i = 0; i < self.dic.length && !action; i++) {
            if (self.dic[i].hold == 0) {
                if (self.dic[i].open <= diffA) {
                    action = [i, "sell", "buy", self.dic[i].amount]
                } else if (self.dic[i].open <= -diffB) {
                    action = [i, "buy", "sell", -self.dic[i].amount]
                }
            } else {
                if (self.dic[i].hold > 0 && self.dic[i].cover >= diffB) {
                    action = [i, "closesell", "closebuy", self.dic[i].hold]
                } else if (self.dic[i].hold < 0 && self.dic[i].cover >= -diffA) {
                    action = [i, "closebuy", "closesell", self.dic[i].hold]
                }
            }
        }

        if (!action) {
            return
        }
        
        Log("A卖B买: " + _N(diffA) + ", A买B卖: " + _N(diffB), ", Action: " + JSON.stringify(action))

        self.isBusy = true

        self.q.pushTask(self.e, self.symbolA, action[1], self.dic[action[0]].amount, function(task, ret) {
            if (!ret) {
                self.isBusy = false
                return
            }
            self.q.pushTask(self.e, self.symbolB, action[2], self.dic[action[0]].amount, function(task, ret) {
                if (!ret) {
                    throw "开仓失败..."
                }
                self.isBusy = false
                if (task.action != "buy" && task.action != "sell") {
                    self.dic[action[0]].hold = 0;
                } else {
                    self.dic[action[0]].hold = action[3];
                }
            })
        })
    }
    return self
}


function main() {
    SetErrorFilter("ready|login|timeout")
    Log("正在与交易服务器连接...")
    while (!exchange.IO("status")) Sleep(1000);
    Log("与交易服务器连接成功")
    var mode = exchange.IO("mode", 0);
    if (typeof(mode) !== 'number') {
        throw "切换模式失败, 请更新到最新托管者!";
    } else {
        Log("已切换到适合多品种价格查询的立即模式");
    }

    if (CoverAll) {
        Log("开始平掉所有残余仓位...");
        $.NewPositionManager().CoverAll();
        Log("操作完成");
    }
    LogStatus("尝试获取持仓状态")
    var positions = _C(exchange.GetPosition)
    LogStatus("Ready")

    if (positions.length > 0 && !AutoRestore) {
        throw "发现持仓, 请勾选自动恢复"
    }

    var pairs = []
    var q = $.NewTaskQueue(function(task, ret) {
        Log(task.desc, ret ? "成功" : "失败")
    })
    var arr = HedgeTable.split('(');
    var tbl = {
        type: 'table',
        title: 'Runtime',
        cols: ['Pair', 'Open', 'Cover', 'Hold', 'DiffA', 'DiffB', 'Time'],
        rows: []
    };
    _.each(arr, function(item) {
        if (item != '') {
            var tmp = item.split(')');
            var pair = tmp[0].replace('(', '').split('&');
            var symbolDetail = _C(exchange.SetContractType, pair[0])
            Log("合约", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下单量", symbolDetail.MaxLimitOrderVolume, "保证金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
            symbolDetail = _C(exchange.SetContractType, pair[1])
            Log("合约", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下单量", symbolDetail.MaxLimitOrderVolume, "保证金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
            pairs.push(Hedge(q, exchanges[0], positions, pair[0], pair[1], tmp[1]))
        }
    });

    var ts = 0
    var lastUpdate = 0
    while (true) {
        if (!exchange.IO("status")) {
            Sleep(1000)
            continue
        }
        
        var now = new Date().getTime()
        if (now - ts > (CalcPeriod * 60000)) {
            var account = exchange.GetAccount()
            if (account) {
                var obj = JSON.parse(exchange.GetRawJSON())
                $.PlotLine('账户权益', obj['Balance'] + obj['PositionProfit']);
                ts = now
            }
        }
        // IO("wait") 会一直等待收到任何一个品种的行情推送信息, 返回收到行情的真实时间
        var n = exchange.IO("wait_any")
        // 计算行情信息传到策略层花费的时间
        var idle = UnixNano() - n
        
        if (now - lastUpdate > 5000) {
            tbl.rows = []
            _.each(pairs, function(t) {
                for (var i = 0; i < t.dic.length; i++) {
                    tbl.rows.push([t.name, t.dic[i].open, t.dic[i].cover, t.dic[i].hold, t.diffA, t.diffB, t.update])
                }
            });
            LogStatus('`' + JSON.stringify(tbl) + '`\nUpdate: ' + _D() + ', Idle: ' + (idle/1000000) + ' ms')
            lastUpdate = now
        }
        
        _.each(pairs, function(t) {
            t.poll()
        });
        
        q.poll()
    }
}

相关内容

更多内容