Type/to search
8
Follow
1364
Followers
Исследования и примеры по разработке стратегии хеджирования фьючерсов с отложенными ордерами
Discussions
Created 2021-11-26 14:15:31  Updated 2023-09-20 09:24:03
 3
 2446

img

Исследования и примеры по разработке стратегии хеджирования фьючерсов с отложенными ордерами

Хеджирование фьючерсов и спотовых сделок всегда было направлено на выявление разницы в ценах и принятие заказов на хеджирование при достижении разницы в ценах. Можно ли его использовать в качестве хеджирования отложенных ордеров? Ответ — да. Сегодня я представлю читателям идею дизайна и прототип кода для хеджирования отложенных ордеров.

Идеи хеджирования отложенных ордеров

Когда на разных рынках наблюдается большая разница в ордерах на покупку и продажу одного и того же или одного и того же типа базового актива, появляется возможность для хеджирования. Как правило, мы принимаем рыночные ордера, которые покрывают разницу в ценах, и занимаем хедж-позицию. Таким образом, цели хеджирования двояки. Первая — хеджировать позиции ордеров, а вторая — гарантировать, что разница между ценой покупки и продажи в максимально возможной степени соответствует нашим ожиданиям. Преимущество торговли отложенными ордерами в этом отношении заключается в том, что ставка комиссии за транзакцию ниже. Недостатком является то, что заключить сделку непросто, а вот заключить сделку в одиночку легко.

Затем мы разрабатываем торговую идею, чтобы разместить ордер на покупку в ордере на покупку книги ордеров рынка A и разместить ордер на продажу в ордере на продажу книги ордеров рынка B. Затем мы проверяем отложенные ордера нашего счета и предпринимаем следующий шаг по обработке обнаруженных отложенных ордеров. Например, при обнаружении изменения в отложенных ордерах фьючерсные и спотовые хеджирующие позиции немедленно балансируются, а переполненные фьючерсные и спотовые позиции покрываются или закрываются. По мере увеличения хеджируемых позиций расстояние между следующим отложенным ордером и первым ярусом рынка корректируется для постепенного хеджирования и получения большей разницы в цене.

Логика хеджирования img

Дизайн кода

Комментарии написаны непосредственно в коде. Этот пример используется только для справочного проектирования и был только кратко протестирован на диске моделирования OKEX V5. Этот пример не является полной стратегией и приведен только для справки.

javascript
// 临时参数 var fuContractType = "quarter" // 期货合约 var fuSymbol = "ETH_USDT" // 期货交易对 var spSymbol = "ETH_USDT" // 现货交易对 var minAmount = 0.1 // 每次交易量、最小交易量,币数 var step = 40 // 差价步长 var buff = 5 // 缓冲差价 var balanceType = "open" // 对于单腿成交平衡时, open 补仓 close 平仓 var depthManager = function(fuEx, spEx, fuCt, fuSymbol, spSymbol) { var self = {} self.fuExDepth = null self.spExDepth = null self.plusPrice = null self.minusPrice = null self.update = function() { spEx.SetCurrency(spSymbol) if (!IsVirtual()) { fuEx.SetCurrency(fuSymbol) } fuEx.SetContractType(fuCt) var fuRoutine = fuEx.Go("GetDepth") var spRoutine = spEx.Go("GetDepth") var fuDepth = fuRoutine.wait() var spDepth = spRoutine.wait() if (!fuDepth || !spDepth) { return false } self.fuExDepth = fuDepth self.spExDepth = spDepth if (fuDepth.Bids.length == 0 || fuDepth.Asks.length == 0 || spDepth.Bids.length == 0 || spDepth.Asks.length == 0) { return false } self.plusPrice = fuDepth.Bids[0].Price - spDepth.Asks[0].Price // futures Bid - spot Ask self.minusPrice = fuDepth.Asks[0].Price - spDepth.Bids[0].Price // futures Ask - spot Bid return true } self.getData = function() { return { "fuExDepth" : self.fuExDepth, "spExDepth" : self.spExDepth, "plusPrice" : self.plusPrice, "minusPrice" : self.minusPrice } } return self } var positionManager = function(fuEx, spEx, fuCt, fuSymbol, spSymbol, step, buffDiff, balanceType, initSpAcc) { var self = {} self.balanceType = balanceType self.depth = null self.level = 1 self.lastUpdateTs = 0 self.fuPos = [] self.spPos = [] self.initSpAcc = initSpAcc self.spAcc = null self.hedgePos = null self.hedgePosPrice = 0 self.minAmount = 0.01 self.offset = ["", 0] self.update = function() { spEx.SetCurrency(spSymbol) if (!IsVirtual()) { fuEx.SetCurrency(fuSymbol) } fuEx.SetContractType(fuCt) self.offset = ["", 0] var fuRoutine = fuEx.Go("GetPosition") var spRoutine = spEx.Go("GetAccount") var fuPos = fuRoutine.wait() var spAcc = spRoutine.wait() if (!fuPos || !spAcc) { return false } self.fuPos = fuPos self.spAcc = spAcc if (!self.initSpAcc) { return false } self.spPos = (spAcc.Stocks + spAcc.FrozenStocks) - (self.initSpAcc.Stocks + self.initSpAcc.FrozenStocks) // 当前减去最初,正数为做多 // 检测fuPos if (fuPos.length > 1) { return false } fuPosAmount = fuPos.length == 0 ? 0 : (fuPos[0].Type == PD_LONG ? fuPos[0].Amount : -fuPos[0].Amount) if ((fuPosAmount > 0 && self.spPos > 0) || (fuPosAmount < 0 && self.spPos < 0)) { return false } fuPosAmount = self.piece2Coin(fuPosAmount) self.hedgePos = (fuPosAmount == 0 || self.spPos == 0) ? 0 : (fuPosAmount < 0 && self.spPos > 0 ? Math.min(Math.abs(fuPosAmount), Math.abs(self.spPos)) : -Math.min(Math.abs(fuPosAmount), Math.abs(self.spPos))) var diffBalance = (spAcc.Balance + spAcc.FrozenBalance) - (self.initSpAcc.Balance + self.initSpAcc.FrozenBalance) if (self.hedgePos == 0) { self.hedgePosPrice = 0 } else { self.hedgePosPrice = fuPos[0].Price - (Math.abs(diffBalance) / Math.abs(self.spPos)) } self.offset[1] = fuPosAmount + self.spPos // 正数多头持仓溢出,负数空头持仓溢出 if (fuPosAmount > 0 && self.spPos < 0) { // 反套 self.offset[0] = "minus" } else if (fuPosAmount < 0 && self.spPos > 0) { self.offset[0] = "plus" } else if (fuPosAmount == 0 && self.spPos < 0) { self.offset[0] = "minus" } else if (fuPosAmount > 0 && self.spPos == 0) { self.offset[0] = "minus" } else if (fuPosAmount == 0 && self.spPos > 0) { self.offset[0] = "plus" } else if (fuPosAmount < 0 && self.spPos == 0) { self.offset[0] = "plus" } return true } self.getData = function() { return { "fuPos" : self.fuPos, "spPos" : self.spPos, "initSpAcc" : self.initSpAcc, "spAcc" : self.spAcc, "hedgePos" : self.hedgePos, "hedgePosPrice" : self.hedgePosPrice, } } self.keepBalance = function(depth) { var fuDepth = depth.fuExDepth var spDepth = depth.spExDepth if (self.offset[0] == "plus") { if (self.offset[1] >= self.minAmount) { if (self.balanceType == "close") { // 现货多头持仓多了,平现货多仓 spEx.Sell(-1, self.offset[1]) } else if (self.balanceType == "open") { // 现货多头持仓多了,开期货空头持仓 fuEx.SetDirection("sell") fuEx.Sell(-1, self.coin2Piece(Math.abs(self.offset[1]))) } } else if (self.offset[1] <= -self.minAmount) { if (self.balanceType == "close") { // 期货空头持仓多了,平期货空仓 fuEx.SetDirection("closesell") fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1]))) } else if (self.balanceType == "open") { // 期货空头持仓多了,开现货多头持仓 spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1])) } } return false } else if (self.offset[0] == "minus") { if (self.offset[1] >= self.minAmount) { if (self.balanceType == "close") { // 期货多头持仓多了,平期货多仓 fuEx.SetDirection("closebuy") fuEx.Sell(-1, self.coin2Piece(self.offset[1])) } else if (self.balanceType == "open") { // 期货多头持仓多了,开现货空头持仓 spEx.Sell(-1, self.offset[1]) } } else if (self.offset[1] <= -self.minAmount) { if (self.balanceType == "close") { // 现货空头持仓多了,平现货空仓 spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1])) } else if (self.balanceType == "open") { // 现货空头持仓多了,开期货多头持仓 fuEx.SetDirection("buy") fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1]))) } } return false } return true } self.process = function(depthManager) { var ts = new Date().getTime() var depth = depthManager.getData() var orders = self.getOrders() if (!orders) { return } self.depth = depth var fuOrders = orders[0] var spOrders = orders[1] if (fuOrders.length == 0 && spOrders.length == 0) { // 重置level if (self.hedgePos == 0) { self.level = 1 } else { self.level = Math.max(1, _N(self.hedgePos / self.minAmount, 0)) } // 限制最大持仓量 if (Math.abs(self.hedgePos) > 1) { return } // 挂单 var fuDepth = depth.fuExDepth var spDepth = depth.spExDepth self.update() if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // 正套 var distance = (step * self.level - (fuDepth.Asks[0].Price - spDepth.Bids[0].Price)) / 2 fuEx.SetDirection("sell") fuEx.Sell(fuDepth.Asks[0].Price + distance, self.coin2Piece(self.minAmount), fuDepth.Asks[0].Price, "挂单差价:", fuDepth.Asks[0].Price + distance - (spDepth.Bids[0].Price - distance)) spEx.Buy(spDepth.Bids[0].Price - distance, self.minAmount, spDepth.Bids[0].Price) } else if (self.hedgePos <= 0 && spDepth.Bids[0].Price - fuDepth.Asks[0].Price > 0) { // 反套 var distance = (step * self.level - (spDepth.Asks[0].Price - fuDepth.Bids[0].Price)) / 2 fuEx.SetDirection("buy") fuEx.Buy(fuDepth.Bids[0].Price - distance, self.coin2Piece(self.minAmount), fuDepth.Bids[0].Price, "挂单差价:", spDepth.Asks[0].Price + distance - (fuDepth.Bids[0].Price - distance)) spEx.Sell(spDepth.Asks[0].Price + distance, self.minAmount, spDepth.Asks[0].Price) } } else if (fuOrders.length == 1 && spOrders.length == 1) { var fuDepth = depth.fuExDepth var spDepth = depth.spExDepth // 判断位置 var isCancelAll = false if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // 正套 var distance = (step * self.level - (fuDepth.Asks[0].Price - spDepth.Bids[0].Price)) / 2 if (Math.abs(fuOrders[0].Price - (fuDepth.Asks[0].Price + distance)) > buffDiff || Math.abs(spOrders[0].Price - (spDepth.Bids[0].Price - distance)) > buffDiff) { isCancelAll = true } } else if (self.hedgePos <= 0 && spDepth.Bids[0].Price - fuDepth.Asks[0].Price > 0) { // 反套 var distance = (step * self.level - (spDepth.Asks[0].Price - fuDepth.Bids[0].Price)) / 2 if (Math.abs(spOrders[0].Price - (spDepth.Asks[0].Price + distance)) > buffDiff || Math.abs(fuOrders[0].Price - (fuDepth.Bids[0].Price - distance)) > buffDiff) { isCancelAll = true } } else { isCancelAll = true } if (isCancelAll) { self.cancelAll(fuEx, fuOrders) self.cancelAll(spEx, spOrders) self.lastUpdateTs = 0 } } else { self.cancelAll(fuEx, fuOrders) self.cancelAll(spEx, spOrders) self.lastUpdateTs = 0 } if (ts - self.lastUpdateTs > 1000 * 60 * 2) { self.update() self.keepBalance(depth) self.update() self.lastUpdateTs = ts } LogStatus(_D()) // 状态栏可以设计输出需要观察的数据、信息 } self.getOrders = function() { spEx.SetCurrency(spSymbol) if (!IsVirtual()) { fuEx.SetCurrency(fuSymbol) } fuEx.SetContractType(fuCt) var fuRoutine = fuEx.Go("GetOrders") var spRoutine = spEx.Go("GetOrders") var fuOrders = fuRoutine.wait() var spOrders = spRoutine.wait() if (!fuOrders || !spOrders) { return false } return [fuOrders, spOrders] } // 币转合约张数 self.coin2Piece = function(amount) { if (IsVirtual()) { if (fuEx.GetName() == "Futures_Binance") { return amount } else if (fuEx.GetName() == "Futures_OKCoin") { var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2 return _N(amount / (100 / price), 0) } else { throw "not support" } } if (fuEx.GetName() == "Futures_OKCoin") { if (fuEx.GetQuoteCurrency() == "USDT") { return _N(amount * 10, 0) } else if (fuEx.GetQuoteCurrency() == "USD") { var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2 return _N(amount / (100 / price), 0) } else { throw "not support" } } else { throw "not support" } } // 合约张数转币 self.piece2Coin = function(amount) { if (IsVirtual()) { if (fuEx.GetName() == "Futures_Binance") { return amount } else if (fuEx.GetName() == "Futures_OKCoin") { var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2 return amount * 100 / price } else { throw "not support" } } if (fuEx.GetName() == "Futures_OKCoin") { if (fuEx.GetQuoteCurrency() == "USDT") { return amount * 0.1 } else if (fuEx.GetQuoteCurrency() == "USD") { var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2 return amount * 100 / price } else { throw "not support" } } else { throw "not support" } } self.cancelAll = function(e, orders) { var isFirst = true while (true) { Sleep(500) if (orders && isFirst) { isFirst = false } else { orders = e.GetOrders() } if (!orders) { continue } else { for (var i = 0 ; i < orders.length ; i++) { e.CancelOrder(orders[i].Id, orders[i]) } } if (orders.length == 0) { break } } } self.CoverAll = function() { // 全部平仓 // 这里可以实现一个,一键平仓的功能 } self.setMinAmount = function(minAmount) { self.minAmount = minAmount } self.init = function() { while(!self.spAcc) { self.update() Sleep(1000) } if (!self.initSpAcc) { var positionManager_initSpAcc = _G("positionManager_initSpAcc") if (!positionManager_initSpAcc) { self.initSpAcc = self.spAcc _G("positionManager_initSpAcc", self.initSpAcc) } else { self.initSpAcc = positionManager_initSpAcc } } else { _G("positionManager_initSpAcc", self.initSpAcc) } // 打印初始信息 Log("self.initSpAcc:", self.initSpAcc.Balance, self.initSpAcc.FrozenBalance, self.initSpAcc.Stocks, self.initSpAcc.FrozenStocks) } self.init() return self } function main() { _G(null) // 清空持久化数据 LogReset(1) // 重置日志 // 以下代码可以切换OKEX模拟盘 // exchanges[0].IO("simulate", true) // exchanges[1].IO("simulate", true) var dm = depthManager(exchanges[0], exchanges[1], fuContractType, fuSymbol, spSymbol) var pm = positionManager(exchanges[0], exchanges[1], fuContractType, fuSymbol, spSymbol, step, buff, balanceType) pm.setMinAmount(minAmount) while (true) { if (!dm.update()) { Sleep(3000) continue } var cmd = GetCommand() if (cmd) { // 处理交互 Log("交互命令:", cmd) var arr = cmd.split(":") if (arr[0] == "") { pm.CoverAll() } } pm.process(dm) Sleep(5000) } }

Анализ бэктестинга

img

img

Видно, что заказы размещаются и отменяются очень часто. Судя по прибыли, рассчитанной системой бэктестинга, счет фьючерсной биржи потерял -0,01666 ETH, а спотовая биржа принесла прибыль в размере 842,23758 USDT. В конце бэктеста спотовая цена ETH составила 4252USDT.-0.01666 * 4252 = -70.83832000000001. В сочетании с прибылью от спотовых операций общая ситуация является прибыльной.

Однако это всего лишь бэктест, и более подробные данные должны быть получены в ходе реальной торговли.

Related Recommendations
Comment
All comments (3)

    梦总,支持okex统一账户模式不 现货买入ETH 期货直接当保证金做空

    4 years ago

    您好, 支持OKEX V5接口的,支持这个模式。

    4 years ago

    4 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)