Untuk waktu yang lama, hedging berjangka dan spot umumnya dirancang untuk mendeteksi perbedaan harga. Ketika perbedaan harga terpenuhi, kita akan mengambil pesanan untuk hedge. Dapatkah itu dirancang sebagai maker hedging? Jawabannya benar-benar ya. Hari ini, saya akan membawa Anda ide desain dan prototipe kode untuk maker hedging.
Dalam pasar yang berbeda dari jenis subjek yang sama atau yang sama, peluang lindung nilai muncul ketika ada perbedaan besar antara pesanan jual dan pesanan beli dari dua pasar. Secara umum, kita akan melakukan pembuat yang memenuhi perbedaan harga dan memegang posisi lindung nilai. Oleh karena itu, ada dua tujuan untuk lindung nilai. Yang pertama adalah untuk lindung nilai posisi dan yang kedua adalah untuk memastikan bahwa perbedaan antara pesanan beli dan jual memenuhi harapan kita sejauh mungkin. Keuntungan dari perdagangan pembuat dalam hal ini adalah bahwa biaya komisi lebih rendah. Kelemahannya adalah tidak mudah untuk melakukan kesepakatan, dan mudah untuk melakukan kesepakatan pada satu posisi.
Ide trading yang kami rancang adalah untuk menempatkan order beli di buku order pasar A dan order jual di buku order pasar B. Kemudian kami memeriksa akun kami pending order, dan melakukan langkah selanjutnya untuk transaksi order pending yang diperiksa. Misalnya, jika kami menemukan perubahan dalam order pending, kami menyeimbangkan posisi lindung nilai spot dan futures segera, dan menutup atau menutup posisi overflow di posisi spot dan futures. Menurut peningkatan posisi lindung nilai, kami menyesuaikan jarak order pending pertama dalam order ke posisi berikutnya dalam order, dan lindung nilai untuk mendapatkan spread yang lebih besar secara bertahap.
Komentar ditulis dalam kode secara langsung. Contoh ini digunakan untuk desain referensi saja dan telah diuji pada demo OKEX V5. Contoh ini bukan strategi yang sempurna, silahkan gunakan untuk referensi saja.
// Temporary parameters
var fuContractType = "quarter" // Futures contracts
var fuSymbol = "ETH_USDT" // Futures trading pairs
var spSymbol = "ETH_USDT" // Spots trading pairs
var minAmount = 0.1 // Amount per transaction, minimum transaction amount, currency
var step = 40 // Difference step length
var buff = 5 // Buffer price difference
var balanceType = "open" // When the single position transaction is balanced, open the covering position and close the closing position
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) // Current one minus the initial one, positive number means going long
// Check 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 positive, long positions overflow, if negative, short positions overflow
if (fuPosAmount > 0 && self.spPos < 0) { // Reverse arbitrage
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") {
// If the spot long position is excessive, close the spot long position
spEx.Sell(-1, self.offset[1])
} else if (self.balanceType == "open") {
// If the spot long position is excessive, open the future short position
fuEx.SetDirection("sell")
fuEx.Sell(-1, self.coin2Piece(Math.abs(self.offset[1])))
}
} else if (self.offset[1] <= -self.minAmount) {
if (self.balanceType == "close") {
// If the future short position is excessive, close the future short position
fuEx.SetDirection("closesell")
fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1])))
} else if (self.balanceType == "open") {
// If the future short position is excessive, open the spot long position
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") {
// If the future long position is excessive, close the future long position
fuEx.SetDirection("closebuy")
fuEx.Sell(-1, self.coin2Piece(self.offset[1]))
} else if (self.balanceType == "open") {
// If the future long position is excessive, open the spot short position
spEx.Sell(-1, self.offset[1])
}
} else if (self.offset[1] <= -self.minAmount) {
if (self.balanceType == "close") {
// If the spot short position is excessive, close the spot short position
spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1]))
} else if (self.balanceType == "open") {
// If the spot short position is excessive, open the future long position
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) {
// Reset level
if (self.hedgePos == 0) {
self.level = 1
} else {
self.level = Math.max(1, _N(self.hedgePos / self.minAmount, 0))
}
// Limit the maximum position
if (Math.abs(self.hedgePos) > 1) {
return
}
// Pending orders
var fuDepth = depth.fuExDepth
var spDepth = depth.spExDepth
self.update()
if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // Positive arbitrage
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, "Price difference of makers:", 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) { // Reverse arbitrage
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, "Price difference of makers:", 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
// Judge the position
var isCancelAll = false
if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // Positive arbitrage
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) { // Reverse arbitrage
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()) // The status bar can be designed to output the data and information to be observed
}
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]
}
// Number of currency converted into contracts
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"
}
}
// Number of contracts converted into currency
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() {
// Close all positions
// Here we can realize one-click position closing
}
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)
}
// Print the initial information
Log("self.initSpAcc:", self.initSpAcc.Balance, self.initSpAcc.FrozenBalance, self.initSpAcc.Stocks, self.initSpAcc.FrozenStocks)
}
self.init()
return self
}
function main() {
_G(null) // Clear the persistent data
LogReset(1) // Reset logs
// The following code can be switchedto the OKEX Demo
// 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) {
// Handle interactions
Log("Interaction command:", cmd)
var arr = cmd.split(":")
if (arr[0] == "") {
pm.CoverAll()
}
}
pm.process(dm)
Sleep(5000)
}
}
Dari statistik sistem backtesting, akun bursa berjangka kehilangan -0.01666 ETH dan bursa spot menghasilkan keuntungan sebesar 842.23758 USDT. Harga spot ETH adalah 4252 USDT pada akhir backtest, -0.01666 * 4252 = -70.83832000000001.
Tapi itu hanya pada backtest, dan pasti ada lebih banyak detail untuk digarap di bot nyata.